Tyrant

Key-Value Stores For Ruby Part 2: Tokyo Cabinet

A few weeks ago I started a series of posts on Key-Value Stores with a general piece on the Key-Value Store concept, and why you should be using one. This week, I’m following it up with a focus on one of our favorite tools for the job here at Engine Yard: Tokyo Cabinet. Tokyo Cabinet was written by Mikio Hirabayashi, and was originally created for mixi — the most popular social networking site in Japan. So it’s proven in production already, and as far as we’ve seen — nice and mature.

Syndicate content
More MongoMapper Awesomeness | Rails Fire

More MongoMapper Awesomeness

September was a month of craziness and for the first month in quite a while I did not post here. I promise it hurt me as much as it hurt you. In an effort to get back in the rhythm, I am going to start with an easy article. MongoMapper has been getting a lot of love lately and I thought I would mention some of the awesomeness.

Dynamic Finders

Dynamic finders are so darn handy in ActiveRecord. How many times have you used User.find_by_email and the like? Thankfully David Cuadrado took a stab at it. I took what he started, tested it a bit harder and added it onto document associations as well. This means when you have a document with a many documents association, you can now use dynamic finders that are scoped to that association.

class User
  include MongoMapper::Document

  many :posts
end

class Post
  include MongoMapper::Document
  key :user_id, String
  key :title, String
end

user = User.create
user.posts.create(:title => 'Foo')

# would return post we just created
user.posts.find_by_title('Foo')

Document associations now also have all the normal Rails association methods such as build, create, find, etc.

Logging

The mongo ruby driver added logging support so a few days ago, I added some basic support for accessing and using that logger from within MongoMapper. When you pass a logger instance to the ruby driver, you can access that connections logger instance from MongoMapper.logger like so:

logger = Logger.new('test.log')
MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => logger)
MongoMapper.logger # would be equal to logger

Tailing the log would give you output like the following:

MONGODB db.$cmd.find({"count"=>"statuses", "query"=>{"project_id"=>"4aceaabed072c4745f0003ca"}, "fields"=>nil})
MONGODB db.$cmd.find({"count"=>"statuses", "query"=>{"project_id"=>"4aceaabed072c4745f0003ce"}, "fields"=>nil})

The nifty part about this is you can setup your Mongo::Connection to use Rails.logger and then all your mongo queries show up in your Rails logs if you have your log level set low enough. This has been very handy for me working on MongoMapper because I can see exactly what MM is sending to Mongo behind the scenes.

Because of this addition, I noticed that every find(:first) was using :order => ‘$natural’ which doesn’t allow using indexes and leads to slow queries. I removed the default order so instead it is just a find with a limit of 1, which should help make a few parts perform better.

Dirty Attributes

ActiveRecord’s dirty attributes is such a cool feature that yesterday, I spent a few hours porting it to MongoMapper::Document. Now you can do things like:

class Foo
  include MongoMapper::Document
  key :phrase, String
end

foo = Foo.new
foo.changed? # false
foo.phrase_changed? # false

foo.phrase = 'Dirty!'

foo.changed? # true
foo.phrase_changed? # true
foo.phrase_change # [nil, 'Dirty!']

I’m sure there will be edge cases, but as we find them we can fortify the tests and go from there.

Custom Data Types

With the 0.4 release came the transition from typecasting to custom data types. Now, instead of natively defining typecasting for “allowed” data types, you can have any data type that you like. You just have to do the conversion to and from mongo yourself. Making your own data types is as simple as:

class Foo
  def self.to_mongo(value)
    # convert value to a mongo safe data type
  end
  
  def self.from_mongo(value)
    # convert value from a mongo safe data type to your custom data type
  end
end

class Thing
  include MongoMapper::Document
  key :name, Foo
end

This means each time the name of Thing is saved to mongo or pulled out of mongo it will be ran through the Foo#to_mongo and Foo#from_mongo to make sure it is exactly what you want it to be.

Out of the box, MongoMapper supports Array, Binary, Boolean, Date, Float, Hash, Integer, String, and Time. You can check out the support file and tests to see how this works.

Time Zones

One not on times, since I mentioned it above is that all times are stored in the datbase as utc now. Also, if you have Time.zone set, all times are converted to the current time zone going to and from the database. This actually turned out to be really easy. We’ll see if I did it all correctly once people start pounding on it I guess. :)

Lazy Loading

One thing that I’ve been working on in between other features is making MongoMapper more lazy. I have already made connection, database and collection lazy so MM doesn’t actually create the connection or connection to the database until needed which makes MM work a lot better with Rails.

I still need to make indexes lazy, so that is the next thing to tackle. I’m thinking once that is in, I’ll have something like MongoMapper.ensure_indexes!, similar to DataMapper.auto_migrate!, which actually ensures the indexes exist rather than doing that the second a class loads.

Internal Improvements

Along with all the public features, I have been working on the internals of MM whenever I get a chance. They still need cleaning up, but things are getting better. Along with some refactoring, I did some work to speed the tests up.

The tests were starting to creep up to around 40 seconds which was driving me nuts. I did a bit of work and realized that clearing every collection before every test was causing most of the slowdown so I pruned the functional tests to only clear the collections that were actually used in that test. This cut the time from around 40 seconds to 10. Yep, huge!

Conclusion

There are still rough parts and I would recommend MongoMapper for beginners, but if you can troubleshoot not only your own code but others, MM is in a good place for you. Up until now, I’ve been working on adding features that I needed similar to ActiveRecord, but I am almost to a place where I am going to start adding features to MM that can literally only exist because of MongoDB.

The next month is going to see some really cool things like upserts, modifiers ($set, $inc, $dec, $push, $pull, etc.) and the like make their way into MM. I also have some plans for an identity map implementation. Oooohs and aaaaaahs abound!