Table of contentsClick link to navigate to the desired location
This content has been automatically translated from Ukrainian.
If you are working with Rails and need to avoid simultaneous modifications of a single record by multiple processes, you should pay attention to the record locking mechanism in the database. One of the most reliable ways to do this is pessimistic locking.
What is a pessimistic lock?
A pessimistic lock means that a record in the database is physically locked for changes from other threads or processes until the current operation is completed. That is, if one process has acquired a lock on a record, others must wait until it is released.
In ActiveRecord, pessimistic locking is implemented through the lock method, which adds SELECT ... FOR UPDATE, holding the lock until the end of the transaction.
Usage in Rails
Here is a simple example of using lock in Rails:
ActiveRecord::Base.transaction do order = Order.lock.find(order_id) order.status = "processed" order.save! end
In this case, if another process tries to get an Order with the same order_id, it will have to wait until the current process completes the transaction.
Why is this needed?
Imagine we are processing an order. When a user clicks the "Pay" button, we need to decrease the quantity of the product in stock. If several people are trying to buy the last unit of the product at the same time, without locking, a race condition is possible:
- Process 1 reads the quantity of the product (1 unit).
- Process 2 reads the same quantity (1 unit).
- Both processes attempt to write a new value (0 units).
- One of the processes loses the update of the other.
However, if we use a lock, the second process will have to wait for the first to finish before deciding what to do.
Alternatives to pessimistic lock
Pessimistic locking is not always the best choice, as it can block other processes, slowing down the application. Sometimes it is better to use:
- Optimistic Locking — checks if the record has changed between reading and writing.
- Atomic operations — uses UPDATE ... WHERE or increment! without reading values in Ruby.
- Queue-based approach — distributes updates through a queue (Sidekiq, RabbitMQ).
Optimistic Locking in Rails
Optimistic locking works through the lock_version column. If two processes read a record, then when trying to write changes, the system checks if the record has changed in the meantime:
class Order < ApplicationRecord attr_accessor :lock_version end
Then, if one process makes changes while another tries to write an outdated version, it will receive ActiveRecord::StaleObjectError.
In simple terms:
- Pessimistic locking locks the record to avoid conflicts.
- Optimistic locking allows working without locking but requires conflict handling.
- Atomic operations reduce the risk of race conditions without locks.
Choose the right approach depending on the task. If it is critical to avoid conflicts at all costs — use a lock. If performance is important and conflicts can be tolerated — better to use lock_version or atomic updates.
This post doesn't have any additions from the author yet.