Ripple 0.8 is Full of Good Stuff

It’s been a while since I’ve blogged about a release of Ripple, in fact, it’s been a long time since I’ve released Ripple. So this post is going to dig into Ripple 0.8 (released today, August 31) and catch you up on what has happened since 0.7.1 (and 0.5 if you don’t follow the Github project).

The major features, which I’ll describe in more detail below, are:

  • Supports Riak 0.12 features
  • Runs on Rails 3 (non-prerelease)
  • Adds Linked associations
  • Adds session stores for Rack and Rails 3 apps

Riak 0.12 Features

The biggest changes here were some bucket-related features. First of all, you can define default quorum parameters for requests on a per-bucket basis, exposed as bucket properties. Riak 0.12 also allows you to specify “symbolic” quorums, that is, “all” (N replies), “quorum” (N/2 + 1 replies), or “one” (1 reply). Riak::Bucket has support for these new properties and exposes them as attr_accessor-like methods. This is a big time saver if you need to tune your quorums for different use-cases or N-values.

Second, keys are not listed by default. There used to be a big flashing warning sign on Riak::Client#bucket that encouraged you to pass :keys => false. In Ripple 0.8 that’s the default, but it’s also explicit so that if you use the latest gem on Riak 0.11 or earlier, you should get the same behavior.

Runs on Rails 3

I’ve been pushing for Rails 3 ever since Ripple was conceived, but now that the actual release of Rails 3 is out, it’s an easier sell. Thanks to all the contributors who helped me keep Ripple up-to-date with the latest prereleases.

Linked associations

These are HOT, and were the missing features that held me back from saying “Yes, you should use Ripple in your app.” The underlying concepts take some time to understand (the upcoming link-walking page to the Fast Track will help), but you actually have a lot more freedom than foreign keys. Here’s some examples (with a little detail of how they work):

You’ll notice only one and many in the above examples. From the beginning, I’ve eschewed creating the belongs_to macro because I think it has the wrong semantics for how linked associations work (links are all on the origin side). It’s more like you “point to one of” or “point to many of”. Minor point, but often it’s the language you choose that frames how you think about things.

Session stores

Outside the Ruby-sphere, web session storage is one of Riak’s most popular use-cases. Both Mochi and Wikia are using it for this. Now, it’s really easy to do the same for your Rails or Sinatra app.

For Sinatra, Padrino and other pure Rack apps, use Riak::SessionStore:

For Rails 3, use Ripple::SessionStore:

Roadmap

If you’re curious what’s coming next in Ripple, or to provide feedback on the direction, be sure to check out the issue tracker. As always, questions can be sent direct to me (sean AT basho DOT com) or to the riak-users mailing list. Documentation is available on Github Pages.

Cheers!

Slides from Boston.rb - "Introducing Riak and Ripple"

Introducing Ripple 0.5: "You Got your Riak in my Ruby"

I’ve teased and toyed with you over the last few days, and finally I’m proud to announce the release of Ripple, a client library and object-mapper for Riak, Basho Technologies’ distributed database.

Since this is a first release, there are a few rough edges, but I think it’s ready enough for the community to start using it.

Acknowledgments

Two awesome companies, Sonian and Basho, enabled me to spend most of 6 weeks building this library. Many kudos go to them for their financial support and technical direction on hard problems.

The Goods

For the impatient (or ecstatic) here’s all the Ripple resources:

How this project started

You’ve already read roughly how I came to know about Riak in my first post. Here’s the rest of the story.

In November or December, Tim Dysinger, VP Engineering at Sonian and all-around awesome guy, started expressing his interest in using Riak to back their Ruby apps. Since I was already interested in Riak, I pressed him for a contract to build the Ruby library now known as Ripple, and later Basho signed on as a co-sponsor.

More about the library

The feature list:

  • A robust basic client, Riak, with:
    • multiple HTTP backends (curb, net/http)
    • sensible client defaults (local, default port)
    • bucket access and manipulation, including key-streaming
    • object reading, storing, deleting and reloading
    • automatic de-serialization of JSON, YAML, and Marshal (when given the right content type)
    • streaming POST/PUT bodies (when given an IO)
    • method-chained map-reduce job construction
  • A document-style modeling library, Ripple, with:
    • ActiveModel 3.0 compatibility
    • Property/attribute definition with automatic type-casting
    • Bucket selection based on class name, with single-bucket inheritance (configurable)
    • Validations
    • Dirty-tracking
    • Simple finders – all documents, by key
    • Reloading

To use Ripple, you need Ruby 1.8.7 or later, and the ActiveModel and ActiveSupport gems from the Rails 3 beta release. Rails 3 will be out very soon, so it might be good to go ahead and get your app ready for it. More details about development and dependencies can be found in the YARD documentation

Usage notes

  1. Ripple has two namespaces. require 'riak' to get just the basic client – this should give you parity with the old jiak.rb included in the Riak distribution, plus a lot more niceties. require 'ripple' to get both the basic client and the (functional but incomplete) object-mapper library.
  2. You’ll need a working developer setup of Riak 0.8 or later. If you have trouble installing, please ask for help on the mailing list.
  3. Some of the specs are failing on Ruby 1.9.

Known Issues

  1. The object-mapper library is incomplete. Most notably missing are associations, which is first on the list to be completed. Work-in-progress is available in the associations branch.
  2. Using a streaming all finder on a Ripple::Document when using the curb backend will render the client unusable because it attempts to request the key reusing the Curl::Easy handle that is doing the streaming. This also affects Riak::Bucket#keys with a streaming block, if the user decides to make requests to the server based on the yielded key.

Roadmap and future directions

The heat really turned up this week, partly thanks to my blog posts over the weekend and the changelog podcast, so I felt compelled to release in an incomplete state. In addition to addressing the known issues above, these features are on the docket (all can be seen on the Github issues tracker):

  1. Identity cache for buckets and keys – think ActiveRecord’s query cache and you’ll know what I mean
  2. ActiveSupport::Cache::Store implementation. This is mostly completed in a branch.
  3. ActionController session store implementation. Just like the cache store, this should be simple to complete.
  4. Wide-coverage integration tests and a testing harness. Some cucumber stories would fit the bill here.
  5. Support parallel backends like Curl::Multi or Typhoeus.

Thanks!

I really enjoyed building this and I hope you enjoy using it in your next Ruby project! Contributions are welcomed!

Wrap your SQL head around Riak's Map-Reduce

UPDATE 2010-03-05: There were some major conceptual errors in the code and design of this post, mainly that map phases must return bucket/key pairs if followed by another map phase. This has been fixed.

One of the conceptual issues you’ll face when switching your persistence layer from SQL over to Riak is grokking map-reduce, so let’s take a pretty predictable query from SQL and convert it to Riak’s Javascript map-reduce.

SELECT addresses.state, COUNT(*)
  FROM people INNER JOIN addresses ON people.id = addresses.person_id
  WHERE people.age < 18
  GROUP BY addresses.state

This query finds how many minors are in each state, using a join and a grouping. Now, assuming you stored this data as JSON in Riak, you’d probably denormalize that belongs_to :person relationship by storing the address(es) in the person record, effectively removing the join. Let’s build a plan for this query:

  1. Query over all the objects in the ‘people’ bucket (input)
  2. Filter out the people 18 years or older (map)
  3. Add a counter for the person’s state (map)
  4. Sum those counts for each state (reduce)

Now, you could easily collapse those map phases, but for clarity I’m going to keep them separate. Since each map-reduce job is submitted as a JSON object, I’ll build that object in a couple of snippets which we’ll join together at the end. First the input, which should either be a list of bucket/key pairs or in our case, the name of a bucket:

{"input":"people",

Riak lets you specify each map or reduce phase as a Javascript function literal, the name of a built-in, a bucket-key pair where the function is stored, or the module-function pair of an Erlang function. For our first phase, we’ll return only the objects for minors (line-breaks are added for clarity — you’d need to escape them for it to be valid JSON).

{"map":{
   "language":"javascript",
   "source":"function(value, keyData, arg){ 
     var data = Riak.mapValuesJson(value)[0]; // extract the object data as JSON
     if(data.age && data.age < 18) 
       return [[value.bucket, value.key]]; 
     else 
       return []; 
    }"
  }
},

This code might seem familiar if you’ve done CouchDB views, except that you don’t call emit() with key/value data, you just return a list of the values you want. Since we have another map phase following this one, we need to return bucket/key pairs. Now let’s extract the state name and count:

{"map":{
   "language":"javascript",
   "source":"function(value, keyData, arg){
     var data = Riak.mapValuesJson(value)[0];
     if(data.address && data.address.state) 
       return [{data.address.state: 1}]; 
     else 
       return []; 
    }"
  }
},

Map functions only work on one item at a time, so we only add one to the count. Now, the output from this phase will look something like: [{"AL":1},{"CO":1},{"NY":1}...]. Unlike our previous phase, since the results of this go directly to a reduce phase, we can return anything. Our final reduce phase will merge those JSON objects into a single one, summing the counts.

{"reduce":{
   "language":"javascript",
   "source":"function(values, arg){ 
     return values.reduce(function(acc, item){
       for(state in item){
         if(acc[state])
          acc[state] += item[state];
         else
          acc[state] = item[state];
       }
       return acc;
     });
    }"
  }
}]}

Now if we submit a POST request to the map-reduce resource (/mapred by default), we can get the results of this query. Here’s the complete POST request body (with line-breaks maintained for clarity):


{ "input":"people",
  "query":[
    {"map":{"language":"javascript", "name":"Riak.mapValuesJson"}},
    {"map":{
       "language":"javascript",
       "source":"function(value, keyData, arg){ 
         var data = Riak.mapValuesJson(value);
         if(data.age && data.age < 18) 
           return [[value.bucket, value.key]]; 
         else 
           return []; 
        }"
      }
    },
    {"map":{
       "language":"javascript",
       "source":"function(value, keyData, arg){ 
         var data = Riak.mapValuesJson(value);
         if(data.address && data.address.state) 
           return [{data.address.state: 1}]; 
         else 
           return []; 
        }"
      }
    },
    {"reduce":{
       "language":"javascript",
       "source":"function(values, arg){ 
         return values.reduce(function(acc, item){
           for(state in item){
             if(acc[state])
              acc[state] += item[state];
             else
              acc[state] = item[state];
           }
           return acc;
         });
        }"
      }
    }
  ]
}

This is just a taste of the kinds of queries and data workflows you can create inside Riak. I hope it’s obvious that it will be easy to generalize the Javascript map and reduce functions we created above, and to build up a library of them that can tackle many problems in a composable, modular way.

Why Riak should power your next Rails app

Last fall, I heard about Riak and thought it sounded awesome, and it boasts a lot of neat features that I’ll go into detail about below. I was further impressed when I met the Basho team at nosqleast in Atlanta. They really seem to know what they’re doing.

In December, John Nunemaker wrote a post entitled Why I think MongoDB is to Databases what Rails was to Frameworks. I commented on twitter:

If as @jnunemaker says, #mongodb is like Rails for databases, then #riak is like Merb.

His reply was quite humorous (no, John, Riak won’t merge into MongoDB), but it got me thinking. So here are my…

7 Reasons why Riak is the Merb of databases (or better)

…and why it should be the database for your next Rails app.

Scales horizontally without pain

Riak runs just about in the same way on 1 node as it does 100. This is because, in short, it is an implementation of Amazon’s Dynamo. Need more storage capacity, throughput and availability? Add more nodes.

Now, a lot of people throw around the word “scalability” without really knowing what it means. It’s really a ratio of the desired properties of your system to the cost inherent in achieving those goals. High scalability means you have a low cost in proportion to your ability to grow. It has nothing to do with performance (although good performance can be a side-effect of good scalability). By this measure, actually, Rails scales well (shared nothing) — but that doesn’t mean it’s always performant (slow Ruby implementations, large memory consumption, etc). Despite the trolls, it’s pretty well known that Twitter’s scaling problems were not really caused by Rails.

So what makes Riak more capable of handling growth than MongoDB, CouchDB or MySQL? It’s built with master-less replication in mind from the beginning. When you grow a MongoDB or MySQL database, you typically add a slave server which receives and replays the transaction logs of the master server. Ideally, this slave can take over if the master goes down. There are also master-master setups and other ways of configuring replication, but in general it’s a thing you add on, not built-in.

Instead, Riak has no notion of a master database node. Every node participates equally in the cluster and spreads the load in a predictable way using consistent hashing (another feature of Dynamo). So yes, increasing capacity and throughput and all those other things you expect from your database is as easy as starting up new nodes and having them join the ring. Data will be replicated in the background. You can even run Riak nodes on less-powerful machines (think EC2) and get decent results. Large, monolithic DB servers are the last vestige of the mainframe era, so democratize your database layer!

HTTP-Compliant REST interface

Rails nerds like me love REST, but most don’t really understand what it means, or rather think that it is a one-to-one mapping to CRUD. Rails’ concept of REST is at best incomplete and potentially misleading. (See also Scott Raymond’s RailsConf 2007 talk)

The Riak developers spent a lot of time creating an awesome toolkit in Erlang for building RFC-compliant HTTP resources, and Riak has several of them. This means that when interacting with Riak, you use HTTP in a natural way and get predictable responses — even the hard stuff like content-negotiation, entity tags and conditional methods.

The side effect of composing Riak of well-behaved HTTP resources is that it plays very nicely with other HTTP-related infrastructure, including proxies, load balancers, caches, and clients of all stripes.

Robust failure recovery

One of Riak’s most compelling features is that it handles node failure robustly. If a node goes down in your cluster, its replicas will take over for it until it comes back, a feature known as hinted handoff. If it doesn’t come back — as happens all too often on EC2 — you can add a new node and the cluster will rebalance. The failure recovery story for MySQL, even in a replicated scenario, is much more difficult, time-consuming, and costly.

Riak has fine-grained robustness as well. It was built primarily in Erlang and with the OTP Design Principles in mind. One aspect of the OTP design is that the processes in the system are organized in a supervision tree, so that when one crashes, it will be restarted by its supervisor process (which is in turn supervised). Send some bad data to the web interface and get a 500 error? That internal error doesn’t crash the whole system, bringing your database node down.

Justin Sheehy, Basho’s CTO, tells a story of a customer whose cluster had two nodes go down late one night. The Basho team looked for less than 10 minutes at the cluster, verified that it was still going, and then decided to wait until the morning to fix the lost nodes. As Joe Armstrong, Father of Erlang, says (paraphrased), “In order to have fault tolerance, you need more than one computer.” Riak embraces that philosophy.

Links make graph-like structures possible

Most Dynamo implementations are simple key-value stores. While that’s still pretty useful — Dynomite is really awesome at storing large binary objects, for example — sometimes you need to find things without a priori knowledge of the key.

The first way that Riak deals with this is with link-walking. Every datum stored in Riak can have one-way relationships to other data via the Link HTTP header. In the canonical example, you know the key of a band that you have stored in the “artists” bucket (Riak buckets are like database tables or S3 buckets). If that artist is linked to its albums, which are in turn linked to the tracks on the albums, you can find all of the tracks produced in a single request. As I’ll describe in the next section, this is much less painful than a JOIN in SQL because each item is operated on independently, rather than a table at a time. Here’s what that query would look like:

GET /raw/artists/TheBeatles/albums,_,_/tracks,_,1

“/raw” is the top of the URL namespace, “artists” is the bucket, “TheBeatles” is the source object key. What follows are match specifications for which links to follow, in the form of bucket,tag,keep triples, where underscores match anything. The third parameter, “keep” says to return results from that step, meaning that you can retrieve results from any step you want, in any combination. I don’t know about you, but to me that feels more natural than this:

SELECT tracks.* FROM tracks
  INNER JOIN albums ON tracks.album_id =  albums.id
  INNER JOIN artists ON albums.artist_id = artists.id
  WHERE artists.name = "The Beatles"

The caveat of links is that they are inherently unidirectional, but this can be overcome with little difficulty in your application. Without referential integrity constraints in your SQL database (which ActiveRecord has made painful in the past), you have no solid guarantee that your DELETE or UPDATE won’t cause a row to become orphaned, anyway. We’re kind of spoiled because ActiveRecord handles the linkage of associations automatically.

The place where the link-walking feature really shines is in self-referential and deep transitive relationships (think has_many :through writ large). Since you don’t have to create a virtual table via a JOIN and alias different versions of the same table, you can easily do things like social network graphs (friends-of-friends-of-friends), and data structures like trees and lists.

Powerful Map-Reduce and soon, Lucene search

The second way to find data stored in Riak is using Map-Reduce. Unlike links, where you have to start with a known key, you can run map-reduce jobs over any number of keys or an entire bucket.

One thing that might not be immediately obvious when using an SQL database is that the declarative nature of the query language hides the imperative nature of performing the query. SQL databases have sophisticated query planners that decompose your query into several steps (see EXPLAIN command on MySQL) and attempt to optimize the order of those steps based on known table properties, like indices, keys, and row counts. Riak’s map-reduce, while still largely declarative, lets you decide which order to run steps in. You trade a little abstraction for a lot of power.

Riak’s map-reduce is also quite different from CouchDB’s views:

  1. You’re not trying to build an index which you’ll query later, you’re performing the query.
  2. You can have any number and combination of map, reduce and link phases (link is actually a special case of map).
  3. Since it’s not an index, your query need not be contiguous across the keyspace.
  4. There’s no concept of re-reduce because reduce phases are only run once.

Up until recently, you could only write map-reduce jobs in Erlang, but thanks to Kevin Smith’s awesome work, you can now write jobs in Javascript and submit them over the HTTP interface. This opens up a lot of doors to developers who aren’t familiar with Erlang but use Javascript every day.

Although not complete or released, Basho also has an awesome Lucene-compatible search system in the pipeline (already in use at Collecta, or so I hear). I saw it in action this past week while in Boston and was impressed. It’s fairly comparable in performance to Solr for large datasets, but scales across your Riak cluster.

Beyond schema-less: Content-type agnostic

One thing that may seem unintuitive at first is that Riak doesn’t care what type of content you put in it. CouchDB and MongoDB use JSON and BSON (Binary JSON), respectively, to store objects. Instead of forcing a format, Riak lets you store pretty much anything. There’s no concept of “attachments” or “GridFS” (unless you add it yourself), so just store the file directly in a bucket/key with a PUT or POST request. As long as you specify the “Content-Type” header, Riak will remember it and give you back the right thing when you access it the next time. No need to do base 64 encoding to store a picture, PDF, Word document, podcast, or whatever. This could enable you to replace a distributed filesystem like GFS, or even S3, with a Riak cluster. Bonus: you get replication and fail-over for no extra cost.

There are some minor kinks with large files (>100MB), but I’ve been assured by Basho that they are addressing the issue. In the meantime, you could chunk the file manually and follow links to get the pieces and put them back together.

Tunable levels of consistency, durability, and performance

The knee-jerk argument against Dynamo-like data stores is that they don’t have ACID properties and that “eventual consistency” is too eventual. In practice, especially in large deployments and high throughput scenarios, ACID breaks down because it requires actions to be “all or nothing”, effectively creating bottlenecks while clients wait for transaction handles. If you’ve done any distributed or concurrent computing, you know that contention for shared resources is a primary cause of failure, including problems like deadlock, starvation and race conditions. For the sake of reducing single points of failure (bottlenecks), Riak implements eventual consistency — where storage operations are accepted immediately, and then propagated across the cluster in an asynchronous fashion. This makes it nearly always available for writes and reads.

In order to deal with inconsistency, Riak tags each datum with a vector clock that internally reveals the datum’s lineage (who modified what version). When there are conflicts — that is, two parallel versions of the same datum — Riak returns you both versions so that your application can decide how to resolve it. In an SQL database with this kind of conflict, your transaction might fail and rollback, forcing you to resolve it and retry anyway.

Beyond just eventual consistency, Riak lets you tune the amount of consistency, durability, and availability you want, even somewhat at request-time. If you need more read availability and replication, you can increase the N value for a bucket (number of replicas, default 3). If you want to be sure the data you’re reading is consistent across the cluster, increase the R value at request time (R, read quorum, is always <= N). If you want high assurance that your data is stored, increase the W and DW values (W = write quorum, DW = durable-write quorum, both always <= N). If you want better performance, e.g. to use Riak as a persistent cache, keep the R, W, and DW values low. You also have the choice of which backend to use when storing your data on the replicas (anything from in-memory to on-disk, or combinations), including the recently released Innostore. Although in many cases the defaults are good enough, you have the flexibility to choose a model that fits your application.

(Note: currently, the N value must be set before you insert any data into the bucket).

Why Riak for Rails?

Ok, so those are the reasons why Riak is awesome in my mind. Why should you use it in your Rails app (or other Ruby application)? Here are some possibilities:

  1. Scale up (or down) cheaply: Riak can grow horizontally with your app, either in lock-step (have database nodes on your app servers!) or independently. A colleague described it as a “Christmas tree” — you could have a load-balanced cluster of Ruby processes and behind that a load-balanced cluster of Riak nodes.
  2. Ease of deployment: No longer do you need to break out or specialize nodes to be database-only, unless you want to. The homogeneity will make your sysadmins happy, and HTTP-friendliness means they already know how to grow the infrastructure.
  3. Flexibility: You can use Riak as a document database, a cache store, a session store, a log server, or a distributed filesystem, with custom settings for each scenario. Soon you’ll also be able to use it as a Solr search replacement.
  4. Powerful modeling: Use links to create relationships in a natural fashion, reducing the number of JOIN-like operations, and then discover data using arbitrary-depth link-walking, or create custom queries with map-reduce.
  5. Background processing and analytics, a.k.a. Big Data: With high write availability and the powerful map-reduce framework, you could defer complicated calculations and later run them in parallel across the Riak cluster. Think data warehouses or Hadoop, but with 1/10 the LoC and no Java.

Where Riak has room to grow

I love Riak’s capabilities, but let’s face it — no system is perfect. Here’s where it could be improved:

  1. Library support: Although it’s REST so you can theoretically use any HTTP client, the included client libraries are pretty basic and use the soon-to-be-deprecated “jiak” interface. I expect that this will improve real soon — we’ve already seen an open-source Java client released.
  2. Efficient ad-hoc lookup: If you only know the bucket of an object but not its key, it’s potentially expensive to find it, since Riak has no concept of indices other than the key (for obvious reasons – it doesn’t know the structure of your content). In the meantime you have two options – eagerly create and maintain them yourself by adding objects with meaningful keys that link back to your original object; or create a map-reduce job to achieve the same result.
  3. Reduce-phase bottleneck: Map and link phases happen with great data-locality, on the nodes where the data is stored, but reduce phases happen in a single node. This is logically simple, but introduces a potential bottleneck and point of failure. If reduce phases are also referentially transparent and idempotent, they could be federated as well, and only incur the single-node hit at the very end.
  4. Large files: Due to some YAGNI-related issues in Webmachine and Riak’s object storage, you can’t load big files into Riak. Break your “l33t xv1dz” into chunks or store them in the filesystem for now.

What to do next

I have lots more to say about Riak (hard to believe, right?), so watch for more blog posts in the future. If you’re ready to dive in, check out the Riak dev site, sign up for the mailing list, or join in the discussion in the IRC channel on Freenode.

smerl: for awesome!

Here's a quick hack with smerl to turn a fun() into a module function:

1> Fun = fun() -> ok end.
#Fun<erl_eval.20.67289768>
2> {env, [_,_,_,Forms]} = erlang:fun_info(Fun, env).
{env,[[],
      {value,#Fun<shell.7.115410035>},
      {eval,#Fun<shell.24.81953817>},
      [{clause,1,[],[],[{atom,1,ok}]}]]}
3> smerl:new(foo).
{meta_mod,foo,undefined,[],[],false}
4> Mod = smerl:new(foo).
{meta_mod,foo,undefined,[],[],false}
5> {ok, Mod2} = smerl:add_func(Mod, {function,1,bar,0,Forms}, true).
{ok,{meta_mod,foo,undefined,
              [{bar,0}],
              [{function,1,bar,0,[{clause,1,[],[],[{atom,1,ok}]}]}],
              false}}
6> smerl:compile(Mod2, [{outdir, "ebin"}]).
ok
7> l(foo).
{module,foo}
8> foo:bar().
ok