Blog

Archive for 2009

Introducing FasterScripts.com! Share Your Performance Experiences

By | December 3rd, 2009 at 4:12PM

FasterScripts.com home page

Today, we’re introducing fasterscripts.com, a site for web producers and developers to share information on poor performance by third party services that they have to embed in their web applications. We wanted to create a nice simple application that would allow people to band together to ask third party services (like ad serving networks, click counters, marketing trackers etc.) to optimize their performance. Fasterscripts.com allows you to report specific services that are performing slowly (with an uploaded screen shot of the slow load from firebug or pagespeed), and tag it for specific performance problems (like not gzipping content or having broken eTags.)

Why does it matter?

Fast sites matter a lot. Faster sites get more visits, more page-views per visit and generate more revenue. Above a certain threshold, slow sites pay more for Google Adwords, and there’s a very good possibility that slow sites will soon be deranked in Google’s Search Engine rankings. (Google, in particular, seems to be on a campaign to speed up web-site performance to native client levels.) So if you’re someone who makes their living developing web applications, you should care very much about performance, and you should care about the speed of third party services.

Where did this come from?

When we were doing the performance research for our Rails Performance in the Cloud Roadshow, we took a sample of 100 Rails sites and ran them through firebug and the YSlow front-end performance analyzer. What we found about average response times was not that surprising: 3.2 seconds for cold-start home page loads.

What we found about the poor performers—the sites that were taking 10 or 20 seconds to load—was very interesting. In pretty much every case, these super-slow response times were caused by poorly responding third party services. And it wasn’t just the small guys: in many cases it was big services like Facebook connect, Google Analytics and Doubleclick that were serving up content slowly, and sub-optimally.

Thankfully, at least Google Analytics shouldn’t be a problem soon. Yesterday, it announced that it was adding an asynchronous option for Google Analytics. This will provide a way to avoid page load stutter caused by its previous synchronous-only “document.write” implementation. But there are still lots of third party services out there that cause performance problems for otherwise well-optimized sites. (Our own favorite sub-optimal bugbear here at Engine Yard is the trackalyzer script from LeadLander, which we use for tracking site visits.)

This sounds neat, how do I get involved?

Go to fasterscripts.com and report slow services. If a service has already been reported, you can “vote it up” in the rankings. Once we have a certain number of reports for a particular service, our plan is to email the company with a list of reporters, and ask them to optimize their service, with tips on how to do it. If this gets momentum, we’ll probably be looking for volunteers to help manage the site, so let us know if you’d be interested in helping out. If you have suggestions on how to improve the site, let us know as well.

Popularity: 2% |

December Events

By | December 3rd, 2009 at 2:12PM

December is a short holiday month, and things tend to quiet down a bit. Still, we’ve got three upcoming events to let you know about, to be sure you can get your periodic dose of Ruby and Rails love between parties and vacations.

SD Ruby

December 3 | San Diego, CA

Yehuda Katz will be in San Diego talking about Rails 3; he’s been all over the map this year, so if you haven’t had a chance to catch a Rails 3 talk yet and are in the San Diego area, be sure to stop in!

Silicon Valley Ruby User Group

December 7 | Moffett Field, CA

In addition to being our resident expert on Agile methodologies, Dave Astels is Cucumber contributor, and co-author of The RSpec Book. Dave will be talking about how Cucumber can make your testing life better, complete with the full scoop on recent developments. There are only a few spots open, so grab yours quickly if you can make it.

Velocity Online Conference

December 8 | Virtual Conference

There have been lots of experiments with online conferences as of late, and the feedback has been great; with belts tightening everywhere this season, it’s a great and affordable way to get access to top-notch content. Yehuda Katz will be speaking about how Rails 3 is even faster by default; tickets are on sale now!

That’s it for December—hope to see some of you, and to all the rest, have a great holiday season!

Popularity: 1% |

J is for JVM: Why the ‘J’ in JRuby?

By | November 30th, 2009 at 10:11AM

The current JRuby team members are all passionate hackers with intimate knowledge of Ruby, Java, and of course JRuby. That said, none of us were on the team at the project’s original inception. I assume the JRuby pioneers thought JRuby would be a good idea—I know I did, when I first heard about it. For a lot of folks though, it’s somewhat less obvious. Why is writing JRuby on top of the JVM a good idea, they ask. Are we nuts, evil geniuses, or is using the JVM just a solid pragmatic decision?

When Java first came out it was a nice incremental improvement over languages at the time. Not only was Java a reasonably simple, yet evolutionary language (vs. C or C++) but it was also a virtual machine that ran a fake non-hardware-specific bytecode (e.g. hardware-neutral). It was garbage collected, so memory management and core dumps suddenly became a minor concern. It also had a reasonably comprehensive class library. No new unique features, but a good combination of ideas packaged into something useful.

In an attempt to market Java as a simple single-named entity, though, I think Sun made a long-term marketing mistake. It co-mingled the language and the runtime under the same name. This did a huge injustice to the runtime because the Java Virtual Machine (JVM) is remarkable on its own merits. It has also made it difficult for alternative JVM language projects like Jython or JRuby to explain the virtues of the JVM without confusing it with the Java language.

This post will focus on the good and the bad of the JVM, and by the end of this entry, I’m hoping you’ll appreciate all the JVM has to offer.

One final note before I delve further: this will just cover what I know about Sun’s JVM. There are a few other JVMs out there and they’re all remarkable in their own right. Many of my points no doubt also apply to those VM implementations, but my lack of intimate knowledge of them compels me to disclaim :)

The Bad

No technology is perfect. The JVM is certainly no exception. If you work on a non-Java language implementation you realize that you’re not quite in Eden. It provides a great base for other languages, but the VM wasn’t originally designed with other languages in mind. This forces us to get creative at times.

For example, we maintain our own frame stack as heap-allocated objects. If we had our way, we would manipulate the real frame stack directly and add the additional fields we need to maintain. It’s too bad we cannot have better control over the framestack. Bummer.

It’s also a bummer that every time we create a Ruby Fixnum value, we’re boxing those values in a Java object. The C implementations of Ruby are just passing tagged ints. If only we had faster fixnums. Java primitives do not cut it since they will not fit into heterogeneous lists without boxing.

Aside: you may have also noticed that the JVM takes some time to start up :)

People who have to write high-performance code for the JVM will also find themselves frequently frustrated by the black box nature of the VM. Once you load your bytecode, you turn a crank and hope you come up a winner. You’re never sure how much of a winner you will be… black boxes, by definition, are a mystery.

With all of that said (and believe me we have a laundry list of features not mentioned that we would like to see), I am super happy that our implementation is on top of the JVM. Why?  Read on…

The Good

Hotspot

For starters, I’ll say that although Hotspot is a mystery, it does a generally great job at performance. The truth is that dynamic profiling is the path to excellent performance. HotSpot is much more clever than we are. It has the benefit of profile data from the running application to inform itself.

It also has the ability to de-optimize code. As strange as it sounds, this detail ends up being the catalyst to great performance.  When Hotspot does an optimization it puts a cheap guard in front of the optimization to make sure the rationale for the optimization still holds true. If the guard ever fails then it de-optimizes back to a slow path. The reason why this rocks, is that Hotspot can then be super speculative with aggressive optimizations. If the informed bet pays off, it pays off huge. If it doesn’t pay off, it gets reset with additional knowledge for a future potential optimization and some extra cost for trying out the not-quite-right optimization.

Here’s a demonstration of Hotspot in action:

In this graph we’re running a mandelbrot generator many times and plotting the amount of time it takes to generate it each time. If you look at JRuby 1.4.0 on the right side JRuby is clearly smoking Ruby 1.8.7 and doing very well against 1.9.2preview2. If you look at the progression as JRuby starts, you can see that we start slower than 1.8.7 but rapidly pick up steam as Hotspot kicks in.

There’s one interesting slowdown which occurs in iteration 6; this is actually a Hotspot deoptimization occurring. You’ll also notice that performance returns to the same level. Later optimizations end up getting that performance back.

I cannot imagine writing something as good as Hotspot. It has been worked on by a lot of smart folks for close to a decade now. The improvements between Java 4, 5, and 6 are pretty impressive. As JRuby, we not only get Hotspot for free with no work on our part, but we also get improvements every time it’s upgraded. This is a pretty nice perk to using the JVM. As I said nothing is perfect, but this really is an easy decision… and we’ve not even begun to talk about Garbage Collection (GC).

Garbage Collection (GC)

The Java folks have literally spent centuries worth of hours debugging, testing, and improving their VM. Think about a building full of engineers working on just garbage collection for a decade and a half! The JVM ships with multiple garbage collectors, so even in cases where workloads confound one GC in the JVM, the JVM actually allows you to use one of the other garbage collectors. On top of that, you can tune any of the collectors you use. You can tailor the collector for your application.

(more…)

Popularity: 2% |

The State of XML Parsing in Ruby (Circa 2009)

By | November 20th, 2009 at 11:11AM

It’s almost the end of 2009, and I have to ask: are we through dealing with XML yet?

Although many of us wish we could consume the web through a magic programmer portal that shields us and our code from all the pointy angle brackets, the reality that is the legacy of HTML, Atom and RSS on the web leaves us little choice but to soldier on. So let’s take a look at what Ruby-colored armor is available to us as we continue our quest to slay the XML dragons.

Background

Historically, Ruby has had a number of options for dealing with structured markup, though oddly none have reached a solid consensus among Ruby developers as the “go to” library. The earliest available library seems to be Yoshida Masato’s XMLParser, which wraps Expat and was first released around the time that Expat itself was released, back in 1998. A pure Ruby parser by Jim Menard called NQXML appeared in 2001, though it never matured to the level of a robust XML parser.

In late 2001, Matz expressed his desire for out of the box XML support, but sadly, nothing appeared in Ruby’s standard library until 2003, when REXML was imported for the 1.8.0 release. After reading bike-shed discussions like this one on ruby-talk in November 2001, or this wayback-machine page from the old RubyGarden wiki, it’s not hard to see why. Meanwhile, other language runtimes, such as Python and Java, moved along and built solid, acceptable foundations, making Ruby’s omission seem more glaring.

But all was not lost: Ruby has always had a quality without a name that made it a great language for distilling an API. All that was needed was an infusion of interest and talent in Ruby, and a few more experiments and iterations.

Fast forward to the present time, and all those chips have fallen. We’ve seen evolution from REXML to libxml-ruby to Hpricot, and finally to Nokogiri. So, is the XML landscape on Ruby so dire? Certainly not, as you’ll see by the end of this article! While the standard library support for XML hasn’t progressed beyond REXML yet, state-of-the-art solutions are a few keystrokes away.

XML APIs

A big part of what makes XML such a pain to work with is the APIs. We Rubyists tend to have an especially low tolerance for friction in API design, and we really feel it when we work with XML. If XML is just a tree structure, why isn’t navigating it as simple and elegant as traversing a Ruby Enumerable?

The canonical example of API craptasticism is undoubtedly the W3C DOM API. For proof, observe the meteoric rise of jQuery in the JavaScript world. While it would be easy to fill an entire article with criticisms regarding the DOM, it’s been done before. (Incidentally, read the whole series of interviews with Elliotte Rusty Harold for a series of insights on API design, schema evolution, and more.)

Instead, we’ll take a brief exploratory tour of some Ruby XML APIs using code examples. Though some of the examples may seem trivially short, don’t underestimate their power. Conciseness and readability are Ruby’s gifts to the library authors and they’re being put to good use.

The libraries we’ll use for comparison are REXML, Nokogiri, and JAXP, Java’s XML parsing APIs (via JRuby).

Parsing

The simplest possible thing to do in XML is to hand the library some XML and get back a document.

REXML

require 'rexml/document'
document = REXML::Document.new(xml)

Nokogiri

require 'nokogiri'
document = Nokogiri::XML(xml)

Both REXML and Nokogiri more or less get this right. What’s also nice is that they both transparently accept either an IO-like object or a string. Contrast this to Java:

JAXP/JRuby

factory = javax.xml.parsers.DocumentBuilderFactory.newInstance
factory.namespace_aware = true # unfortunately, important!
parser = factory.newDocumentBuilder
# String
document = parser.parse(xml)
# IO
document = parser.parse(xml.to_inputstream)

In that familiar Java style, the JAXP approach forces you to choose from many options and write more code for the happy path. JRuby helps you a little bit by converting a Ruby string into a Java string, but needs a little help with intent for converting an IO to a Java InputStream.

XPath

Now that we’ve got a document object, let’s query it via XPath, assuming the underlying format is an Atom feed. Here is the code to grab the entries’ titles and store them as an array of strings:

REXML

elements = REXML::XPath.match(document.root, "//atom:entry/atom:title/text()",
                              "atom" => "http://www.w3.org/2005/Atom")
titles = elements.map {|el| el.value }

Nokogiri

elements = document.xpath("//atom:entry/atom:title/text()",
                          "atom" => "http://www.w3.org/2005/Atom")
titles = elements.map {|e| e.to_s}

Again, both REXML and Nokogiri clock in at similar code sizes, but subtle differences begin to emerge. Nokogiri’s use of #xpath as an instance method on the document object feels more natural as a way of drilling down for further detail. Also, note that both APIs return DOM objects for the text, so we need to take one more step to convert them to pure Ruby strings. Here, Nokogiri’s use of the standard String#to_s method is more intuitive; REXML::Text’s version returns the raw text without the entities replaced.

Unfortunately, doing XPath in Java gets a bit more complicated. First we need to construct an XPath object. At least JRuby helps us a bit here–we can create an instance of the NamespaceContext interface completely in Ruby, and omit the methods we don’t care about.

JAXP/JRuby

xpath = javax.xml.xpath.XPathFactory.newInstance.newXPath
ns_context = Object.new
def ns_context.getNamespaceURI(prefix)
  {"atom" => "http://www.w3.org/2005/Atom"}[prefix]
end
xpath.namespace_context = ns_context

Next, we evaluate the expression and construct the array titles:

JAXP/JRuby

nodes = xpath.evaluate("//atom:entry/atom:title/text()",
                       document, javax.xml.xpath.XPathConstants::NODESET)
titles = []
0.upto(nodes.length-1) do |i|
  titles << nodes.item(i).node_value
end

That last bit where we need to externally iterate the DOM API is particularly un-Ruby-like. With JRuby we can mix in some methods to the NodeList class:

JAXP/JRuby

module org::w3c::dom::NodeList
  include Enumerable
  def each
    0.upto(length - 1) do |i|
      yield item(i)
    end
  end
end

And replace the external iteration with a more natural internal one:

JAXP/JRuby

titles = nodes.map {|e| e.node_value}

This kind of technique tends to become a fairly common occurrence when coding Ruby to Java libraries in JRuby. Fortunately Ruby makes it simple to hide away the ugliness in the Java APIs!

Walking the DOM

Say we’d like to explore the DOM. Both REXML and Nokogiri provide multiple ways of doing this, with parent/child/sibling navigation methods. They also each sport a recursive descent method, which is quite convenient.

REXML

titles = []
document.root.each_recursive do |elem|
  titles << elem.text.to_s if elem.name == "title"
end

Nokogiri

titles = []
document.root.traverse do |elem|
  titles << elem.content if elem.name == "title"
end

Needless to say, Java’s DOM API has no such convenience method, so we have to write one. But again, JRuby makes it easy to Rubify the code. Note that our #traverse method makes use of our Enumerable-ization of NodeList above as well.

JAXP/JRuby

module org::w3c::dom::Node
  def traverse(&blk)
    blk.call(self)
    child_nodes.each do |e|
      e.traverse(&blk)
    end
  end
end

titles = []
document.traverse do |elem|
  titles << elem.text_content if elem.node_name == "title"
end

Pull parsing

All three libraries have a pull parser (also called a stream parser or reader) as well. Pull parsers are efficient because they behave like a cursor scrolling through the document, but usually result in more verbose code because of the need to implement a small state machine on top of lower-level XML events. They are best employed on very large documents where it’s impractical to store the entire DOM tree in memory at once.

REXML

parser = REXML::Parsers::PullParser.new(xml_stream)
titles = []
text = ''
grab_text = false
parser.each do |event|
  case event.event_type
  when :start_element
    grab_text = true if event[0] == "title"
  when :text
    text << event[1] if grab_text
  when :end_element
    if event[0] == "title"
      titles << text
      text = ''
      grab_text = false
    end
  end
end

Nokogiri

reader = Nokogiri::XML::Reader(xml_stream)
titles = []
text = ''
grab_text = false
reader.each do |elem|
  if elem.name == "title"
    if elem.node_type == 1  # start element?
      grab_text = true
    else # elem.node_type == 15  # end element?
      titles << text
      text = ''
      grab_text = false
    end
  elsif grab_text && elem.node_type == 3 # text?
    text << elem.value
  end
end

(Aside to the Nokogiri team: where are the reader node type constants?)

JAXP/JRuby

include javax.xml.stream.XMLStreamConstants
factory = javax.xml.stream.XMLInputFactory.newInstance
reader = factory.createXMLStreamReader(xml_stream.to_inputstream)
titles = []
text = ''
grab_text = false
while reader.has_next
  case reader.next
  when START_ELEMENT
    grab_text = true if reader.local_name == "title"
  when CHARACTERS
    text << reader.text if grab_text
  when END_ELEMENT
    if reader.local_name == "title"
      titles << text
      text = ''
      grab_text = false
    end
  end
end

Not surprisingly, all three pull parser examples end up looking very similar. The subtleties of the pull parser APIs end up getting blurred in the loops and conditionals. Only write this code when you have to.

Performance

At the end of the day, it comes down to performance, doesn’t it? Although the topic of Ruby XML parser performance has been discussed before, I thought it would be instructive to do another round of comparisons with JRuby and Ruby 1.9 thrown into the mix.

System Configuration

  • Mac OS X 10.5 on a MacBook Pro 2.53 GHz Core 2 Duo
  • Ruby 1.8.6p287
  • Ruby 1.9.1p243
  • JRuby 1.5.0.dev (rev c7b3348) on Apple JDK 5 (32-bit)
  • Nokogiri 1.4.0
  • libxml2 2.7.3

Here are results comparing Nokogiri and Hpricot on the three implementations  along with the JAXP version which only runs on JRuby (smaller is better).

The REXML results were over an order of magnitude slower, so it’s easier to view them on a separate graph. Note the number of iterations here is 100 vs. 1000 for the results above.

While these results don’t paint a complete picture of XML parser performance, they should give you enough of a guideline to make a decision on which parser to use once you take portability and readability into account. In summary:

  • Use REXML when your parsing needs are minimal and want the widest portability (across all implementations) with the smallest install footprint.
  • Use JRuby with the JAXP APIs for portability across any operating system that supports the Java platform (including Google AppEngine).
  • Use Nokogiri for everything else. It’s the fastest implementation, and produces the most programmer-friendly code of all Ruby XML parsers to date.

(As a footnote, we on the Nokogiri and JRuby teams are looking for community help to further develop the pure-Java backend for Nokogiri so that AppEngine and other JVM deployment scenarios that don’t allow loading native code can benefit from Nokogiri’s awesomeness. Please leave a comment or contact the JRuby team on the mailing list if you’re interested.)

The source code for this article is available if you’d like to examine the code or run the benchmarks yourself. Keep an eye on the Engine Yard blog for an upcoming post on Nokogiri, and as always, leave questions and thoughts in the comments!

Popularity: 7% |

Key-Value Stores in Ruby: The Wrap Up

By | November 17th, 2009 at 11:11AM

This last article in our key-value series will briefly cover a few interesting topics that could each have had full articles of their own. This means that if they seem interesting to you, follow the links that I provide to get more information on them. Lastly, I’ll wrap up by introducing Moneta, written by Yehuda Katz, which provides a unified API for a wide variety of different Key-Value Stores. If you want to write code that allows the user to choose the store to use, you’ll want to pay attention to Moneta.

The difficult part of discussing Key-Value Stores stores today is that it’s a product area seeing rapid development and constant evolution. There are more interesting stores and libraries available than can easily be covered, even in a series like this. I could probably be writing posts every two weeks into next year without running out of subjects. So, alas, many things must be left undiscussed or underdiscussed. But let’s move on to the topics we can cover…

CouchDB

The first great Key-Value Store that isn’t going to get its own article is CouchDB. Apache’s CouchDB is a document-oriented database, like MongoDB. It, however, exposes a RESTful JSON based API that you address with a built in HTTP interface. Like MongoDB, it offers a schema free data store. CouchDB offers solid, built-in replication, and uses JavaScript as its query language. It is a powerful tool.

There are several Ruby libraries which can be used to facilitate using CouchDB. In the examples below, I have used CouchRest, which is based on CouchDB’s own couch.js library:

require 'rubygems'
require 'couchrest'
require 'yaml'
 
DBH = CouchRest.database!('exercise-log')
 
response = DBH.save_doc({
  :date => Time.now,
  :activity => ARGV[0],
  :duration => ARGV[1]})
 
stored_record = DBH.get(response['id'])
puts "Stored:\n#{stored_record.to_yaml}"
wyhaines$ ruby /tmp/couch1.rb
Stored:
--- !map:CouchRest::Document
duration: "97:34"
_rev: 1-eb6f6e3a3e2eae0cd99f3fcbc63d29d6
_id: 0d9e71f44b3e0d3a2013c282bbccb5a0
activity: pedaling
date: 2009/11/12 21:07:45 +0000

Like MongdoDB, one can store any set of keys/values together as a document in CouchDB, and then retrieve it later. CouchRest returns a response from the server that contains an id field, which can be used to retrieve the record that was just stored.

For more complex queries of the document store, one can use views. Views have a lot of power, because they are ultimately defined using JavaScript, but they don’t lend themselves to easy ad-hoc manipulation of the database.

DBH.save_doc({
  "_id" => "_design/query",
  :views => {
    :allkeys => {
      :map => "function(doc) { " \
              "for (var word in doc) { " \
              "if (!word.match(/^_/)) emit(word,doc[word])}}"
    }
  }
})

That inserts a view into the database that will be identified by query/allkeys. What a view does is defined by the JavaScript code it contains. Once a view is inserted into CouchDB, using it is simple:

puts DBH.view('query/allkeys').to_yaml

That particular function was lifted shamelessly from the CouchRest README, and just has a couple terms renamed to make it a little more clear. The output:

---
total_rows: 3
rows:
- id: 0d9e71f44b3e0d3a2013c282bbccb5a0
  value: pedaling
  key: activity
- id: 0d9e71f44b3e0d3a2013c282bbccb5a0
  value: 2009/11/12 21:07:45 +0000
  key: date
- id: 0d9e71f44b3e0d3a2013c282bbccb5a0
  value: "97:34"
  key: duration
offset: 0

This is really just the tip of the iceberg with CouchDB/CouchRest; there’s a wealth of functionality. CouchDB views are implemented with map/reduce capability, which means you can use them to crunch some pretty complex problems on your data. Additionally, CouchRest provides a CouchRest::ExtendedDocument, which your own classes can inherit from. This lets you easily create a Ruby model for your data, which is then transparently stored inside CouchDB.

class Exercise  "running", :date => Time.now, :duration => "23:44")

Dig into the CouchDB and CouchRest documentation if this looks interesting to you.

S3

I just wanted to briefly mention Amazon’s Simple Storage Service. It is, fundamentally, a simple HTTP accessible Key-Value Store that Amazon has turned into a service. Requests to S3 will have higher latency than requests to a locally hosted data store (and its response latency can be high too), but if you want a simple, robust store that will scale to as much data as you have to push at it, you might seriously consider S3.

Moneta

Moneta is a unified interface to a variety of different key-value type data stores. That is, the same code can be run against a variety of different backing stores, and it will just work. Moneta supports the following stores as of this posting:

  • Basic File Store
  • BerkeleyDB
  • CouchDB
  • DataMapper
  • File store for xattr
  • In-memory store
  • Memcache store
  • Redis
  • S3
  • SDBM
  • Tokyo
  • Xattrs in a file system

Consider this example, which, again, uses CouchDB:

require 'moneta/couch'
require 'rubygems'
require 'yaml'
require 'moneta'
require 'moneta/couch'
 
cache = Moneta::Couch.new(:db => 'football')
 
cache['1a_final'] = {
  :where => 'Laramie; War Memorial Stadium',
  :when => "11:30 MST",
  :who => "Southeast Cyclones &amp; Lingle-Ft. Laramie Doggers",
  :prediction => "SE Cyclones by 14"}
 
puts cache['1a_final'].inspect
wyhaines$ ruby /tmp/moneta1.rb
---
- prediction: SE Cyclones by 14
  when: 11:30 MST
  who: Southeast Cyclones &amp; Lingle-Ft. Laramie Doggers
  where: Laramie; War Memorial Stadium

It works, very simply. If I want to change the code to use something else, like a file based store, it’s as simple as changing one line:

--- couch.rb    2009-11-19 15:00:07.000000000 -0700
+++ file.rb     2009-11-19 15:01:12.000000000 -0700
@@ -1,9 +1,9 @@
 require 'rubygems'
 require 'yaml'
 require 'moneta'
-require 'moneta/couch'
+require 'moneta/file'
 
-cache = Moneta::Couch.new(:db => 'football')
+cache = Moneta::File.new(:path => '/tmp/football')
 
 cache['1a_final'] = {
   :where => 'Laramie; War Memorial Stadium',

The rest of the code works without alteration. The Moneta API is designed to be very similar to that of Hash. It has a limited feature set, but the features it provides work identically across all of the supported platforms. For example, it doesn’t currently support iteration or partial matches. If your Key-Value Store needs are simple and you want something that can work with whatever store your users want to use, definitely check out Moneta; it’s a well written tool.

With that, we’ve reached the end of this series. It’s been fun to explore the unique features, as well as the threads that unify each of these different approaches to the problem, on a non-SQL key-value type data store. I hope that I’ve exposed you to new and useful tools.

The landscape of Key-Value Stores is changing rapidly, so it is difficult to stay fully informed all the time. For instance, just a couple days ago there was a blog post implementing a SQL front end for CouchDB. It’s done in Perl, but all it would take is an interested person and a little time, and you could have it in Ruby, too.

If you use a Key-Value Store system, or plan to, keep your eyes open for new developments, because you can bet that someone else will have something interesting next week or next month that may change the landscape again. As always, leave feedback in the comments, and thanks for reading!

Popularity: 3% |