Photo cred: Benjamin Bortels
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.
- multiple users will not be able to read while others are reading
- 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 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.
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
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
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!)