coderrr

February 8, 2009

Updating an account balance with optimistic locking in Rails

Filed under: concurrency, rails, ruby — Tags: , , — coderrr @ 1:41 pm

Here’s an example of how to use Rails’ optimistic locking to protect against concurrency issues while updating a user’s account balance.

First thing you want to do is isolate the column you want to lock. The reason for this is that Rails will raise a StaleObjectError whenever there are simultaneous updates to a table with a lock_version column. So if you smack this column onto your users table it means you have to rescue that error wherever you update a user record. Instead we break off the locked column into a new table and only do updates to that table from a one or a few small pieces of code. This makes handling the errors much easier.

So say we have:

class User < ActiveRecord::Base
  has_one :account
  after_create :create_account
end

class Account < ActiveRecord::Base
  # add_column :lock_version, :integer, :default => 0
  # add_column :balance, :integer, :default => 0
  belongs_to :user
end

To deal with the actual updates to the balance we add a single method to User to handle them:

class User
  def update_balance(amount)
    account ||= self.account

    account.balance += amount
    account.save

    account.balance
  rescue ActiveRecord::StaleObjectError
    account.reload
    retry
  end
end

This will correctly handle any number of simultaneous updates to the user’s balance. But now every time we want to query the user along with his balance we need to do a JOIN on the accounts table. Let’s denormalize. We can add a before_save callback on Account to perform the denormalization.

class User
  # add_column :account_balance, :integer, :default => 0
end

class Account
  before_save :denormalize_balance
  def denormalize_balance
    user.update_attribute :account_balance, balance
  end
end

Now we can safely update the user’s account balance in parallel from multiple threads/processes and have denormalized the data so we can easily order by or access the balance directly from the users table.

1 Comment »

  1. hey steve, useful tip. hope all is well.

    Comment by ben — February 10, 2009 @ 2:44 am


RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.