DEV Community

loading...
Cover image for Rails: Pessimistic Locking

Rails: Pessimistic Locking

nodefiend profile image chowderhead ・3 min read

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:

Optimistic Locking

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

Official Docs

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/

http://blog.katpadi.ph/race-condition-and-rails-with_lock/

Discussion (2)

pic
Editor guide
Collapse
andy profile image
Andy Zhao (he/him)

Very cool, thanks for sharing! Never heard of the feature until now.

Collapse
nodefiend profile image
chowderhead Author

hey! thanks for reading! glad to help :D