Saturday, April 14, 2007

ActsAsWithReadonly to support read-only DB slave databases

Update: Released as ActsAsReadonlyable

In his latest post, DHH comments on scaling issues Twitter's team had with their rails application. One of the frequent mentioned problems with ActiveRecord is a weak support of multiple read-only slave databases. We were told by our DBAs some time ago that we needed to think how our rails application would utilize slaves DBs after the official release of our portal . Back then our teammate, Jeffrey Damick, came up with an idea of having an 'acts_as' plugin to support that. We have not yet started using slave DBs but some code has been written. We are releasing it on Rubyforge as soon as the project is approved.

The basic idea behind the plugin is that when an ActiveRecord model is marked with acts_as_with_readonly, most of AR finders are overloaded to run against a slave DB. It allows to do all reads from a read-only farm while saving updates to the read-write DB.

An example of usage:

*** database.yml


database: master_db
host: master-host

database: slave_db
host: slave-host

*** Sample Model

class ReadWriteModel < ActiveRecord::Base
acts_as_with_readonly :read_only

*** Code

record = ReadWriteModel.find(:first) # against slave_db
record.field = 'new value'! # against master_db


Dr Nic said...

That's great. We were throwing around some database.yml syntax ideas at the forum.

If you do a find/reload within the same action that does some CRUD, do you have discovered any issues with the slaves being out of sync with the master?

Tim Lucas said...

Nic has a point, which touches on another issue: how could we get this working with optimistic locking? You'd want to be performing the write *and* the read against the master database (for the new/edit/create/update actions, the show action could use the funky slave only distributed goodness).

I'd prefer to introduce some syntatic vinegar for the read from slave and write to master approach. That way all current code just uses the read/write master, and funky distributed slave goodness can use different syntax:

@contact = Contact.with_readonly_connection.find(1, :include => [:address, :company])

The objects returned by that connection should probably disallow write too.

Tim Lucas said...

make that, Contact.with_read_only_connection

Rahsun said...

I believe that's a great idea. I'm glad to see people with big applications thinking on problems and giving feedback into the community that only helps to reduce of the whole "Does Rails Scale?" fear.

Laurel said...

Have you been working on this or using it since the release? We've implemented this based on your plugin (but a different way because for some reason it was sending writes to the read only database). Interested in patches or bug reports?