Rails: Pessimistic Locking
chowderhead
Posted on May 25, 2019
Photo cred: Benjamin Bortels
Problem
What happens when more then 1 user tries to update a table in our database at the exact same moment? you guessed it ! RACE CONDITION - as a programmer, you must hate those, maybe after reading this we can avoid at least one thing that causes them.
Definition
pessimistic database locking:
- multiple users will not be able to read while others are reading
optimistc database locking:
- multiple users can read the same resource at the same time but if more then one tries to modify the database , then we prevent it
In optimistic locking, we only lock it when updating the data. Other requests can still read the subject data. Pessimistic locking, on the other hand, locks all other access to the record. Even the read access is not allowed. With this type of locking, while the first request to the object is updating, all other requests will have to wait for their turn.
In this article we will cover how to handle Pessimistic Locking in rails, which is easier, for optimistic locking check this out:
Imagine:
imagine a like button -
def like(id)
message = Message.find(id)
message.like_count += 1
message.save!
end
if one user press the like button , and another user presses the like button at the same time then instead of the like_count
of that message going up to 2, it will only increment to 1, because both users pressed increment from 0 to 1 at the same time.
with_lock
What with_lock does is a few things. First, it starts a database transaction. Second, it acquires a pessimistic database lock. Once the lock is acquired the record is reloaded in memory so the values on the record match those in the locked database row. The lock will prevent others from reading or writing to that row and anyone else trying to acquire a lock will have to wait for the lock to be released.
def like(id)
message = Message.find(id)
message.with_lock do
message.like_count += 1
message.save!
end
end
Now imagine that we want to lock this users account while this lock transaction is taking place , so that no updates can be made to the user model in the scope of this transaction.
what we can do is call account.lock!
def like(id)
message = Message.find(id)
message.with_lock do
account.lock!
message.like_count += 1
message.save!
end
end
since we are already in a database transaction (the first lock), we cannot use another with_lock
block because it itself is a transaction. what we can do inside of this with_lock
block is call .lock!
.
This works very similarly to the with_lock method with two exceptions. First, it just acquires the lock at the time of calling and then releases it whenever the surrounding transaction completes rather than managing its own transaction internally. Second, it does absolutely nothing if not called inside of a transaction. To repeat, don’t use lock! outside of a transaction! Besides that, though, it will ensure the same type of data integrity that the with_lock method does.
Thanks for reading! Happy locking!
sources: (thank you!)
https://www.peterdebelak.com/blog/pessimistic-locking-in-rails-by-example/
Posted on May 25, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.