Friday, January 11, 2008

Locking

Usually in an application with more than 1 user, there are several resources that will be shared. And whenever there are shared resources, there are usually race conditions. Most web applications are not exceptions. Shared resources are a very old set of problems that have been solved many times in many different areas, not only for computers.

Luckily, in RoR, it provides a way to handle shared resources: locking. Locking comes in 2 flavours: optimistic and pessimistic.

Usually, optimistic locking would be used for critical sections where race conditions are not expected to happen very often (due to the abortion of the transaction if a conflict occurs), whereas pessimistic locking would be used for critical sections where race conditions are frequent.

There is, however, a huge advantage that optimistic locking has over pessimistic locking, and that is scalability. The fact that pessimistic locking for update locks the row so that no read/update/destroy can be performed on that row until it is released means that users may have to wait indefinitely. Especially when race conditions are frequent, a long queue could develop with arbitrarily long waits.

Unfortunately, with optimistic locking, there is the problem of abortion and requiring the user to redo what they just did. This is obviously not very user-friendly, as the user expects their changes to be made the first time. If the UI is thought out carefully though, the impact of this may be minimal.

RoR, being superly fantastic, supports both! And both are very simple to implement.

With optimistic locking, all you have to do is add a column to your table you want to optimistically lock. Using migrations, it would look like this:

t.column :lock_version, :integer, :null => false, :default => 0

It is important that the column name is lock_version, and the default value is 0. Now, whenever an update occurs, the lock_version is compared to the one in the database. If there are any differences, then a StaleObjectError is raised and must be handled in any fashion in which you deem appropriate. More information about RoR's optimistic locking can be found here.

With pessimistic locking, all you do is lock it down when you do a find:

Person.find(:all, :lock => true)

This will pessimistically lock any rows returned. If you already have an object, you can use the method object#lock!:

person = Person.find(1)
person.lock!

More information about RoR's pessimistic locking can be found here.

Well, hopefully that was interesting to you. One more tip about locking is to watch out for deadlocks! Good luck!


W

No comments: