ActiveRecord Optimistic Locking
Optimistic locking is an alternative to pessimistic locking except that it sort of "builds in" locking mechanisms to an entire table and its corresponding ActiveRecord model. In pessimistic locking, you have to manually call with_lock
on an ActiveRecord model while in optimistic locking, you introduce a column called lock_version
in your database table, which automatically enables optimistic locking.
The lock_version
column will be incremented every time a change is committed to the record in question. Thus, if there are two processes accessing the same record, and one process makes an update to the record, the second process won't be able to modify the record unless it re-retrieves the newly updated data. This can prevent problems that can come with concurrent access to the same data.
This is pretty much straight from the Rails docs but I can't think of any better way to explain some example usages of optimistic locking, so here goes.
Example 1: Optimistic locking preventing two instances of the same record overriding each other.
p1 = Person.find(1)
p2 = Person.find(1)
p1.first_name = "Michael"
p1.save
p2.first_name = "should fail"
p2.save # Raises an ActiveRecord::StaleObjectError
Example 2: Optimistic locking preventing deletion of the same record when lock_version is out of date.
p1 = Person.find(1)
p2 = Person.find(1)
p1.first_name = "Michael"
p1.save
p2.destroy # Raises an ActiveRecord::StaleObjectError
To add optimistic locking to an ActiveRecord model, add the column lock_version
to your database table with a datatype of integer
. Something like this.
class AddLockingToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :lock_version, :integer, default: 0, null: false
end
end
lock_version
is the default column name that Rails will look for, but you can also customize the column name by overriding an attribute called locking_column
within the ActiveRecord model like this.
class User < ActiveRecord::Base
self.locking_column = :custom_locking_column_name
end
Personally, I prefer pessimistic locking since I can choose specific instances where I want to lock records. To me, optimistic locking seems akin to the evil (in my mind) default_scope
that override default scoping behaviors in ActiveRecord.
I would say optimistic locking is a toolbox that one can grab for, but only do so when there's a really good reason to do it as it completely overrides out-of-box default ActiveRecord behavior.