When several people touch a database simultaneously, software conflicts can occur when more than one user tries to update the same record. The most sought-after way to avoid problems from this point of view is to lock the record the first user needs to change. Such conflicts are dealt with in Ruby on Rails using two locking strategies: Optimistic Locking and Pessimistic Locking. This article discusses these strategies, their differences, and how they can be implemented in Ruby on Rails.
Optimistic Locking
Optimistic locking assumes that conflicts are rare. It allows multiple users to read the same record without restrictions but ensures that updates are safe by verifying the record's version before saving.
How it Works
- A
version
column is added to the table. - Each time a record is updated, the
version
is incremented. - When a user tries to save changes, Rails checks if the
version
in the database matches theversion
in the user's object. - If the versions do not match, Rails raises an error (e.g.,
ActiveRecord::StaleObjectError
), indicating the record has been updated by someone else.
Example in Rails
# Migration to add version column
class AddLockVersionToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :lock_version, :integer, default: 0, null: false
end
end
Model setup
class User < ApplicationRecord
end
Usage in code
user1 = User.find(1)
user2 = User.find(1)
user1.update(name: "Alice")
user2.update(name: "Bob") # Raises ActiveRecord::StaleObjectError
Pessimistic Locking
Pessimistic locking prevents conflicts by locking a record for a single user during an operation. Other users cannot modify or even read the record (depending on the lock type) until the lock is released.
How it Works
- A database-level lock is applied when a record is accessed.
- Other transactions must wait for the lock to be released.
- This prevents conflicts but can lead to performance issues if locks are held for too long.
Example in Rails
# Usage with find and lock
user = User.lock.find(1) # Applies a "FOR UPDATE" lock in SQL
user.update(name: "Charlie") # Other transactions must wait
Usage in a transaction
User.transaction do
user = User.lock.find(1)
user.update(name: "Dana")
end
Differences Between Optimistic and Pessimistic Locking
Feature | Optimistic Locking | Pessimistic Locking |
---|---|---|
Assumption | Conflicts are rare. | Conflicts are likely. |
Implementation | Uses a version column to detect conflicts. |
Uses database locks to prevent conflicts. |
Performance | No waiting for locks, better for high-read apps. | Can cause delays and deadlocks if overused. |
Conflict Resolution | Raises an error when a conflict is detected. | Prevents conflicts by locking the record. |
Use Case | Best for apps with frequent reads and rare updates. | Best for critical operations needing strict control. |
Choosing the Right Strategy
Use Optimistic Locking when:
- The application has high read-to-write ratios.
- Conflicts are rare but need to be detected.
- Users need to resolve conflicts manually.
Use Pessimistic Locking when:
- Conflicts are common or critical.
- Data integrity is more important than performance.
- Transactions are short and well-managed.
Summary
Optimistic locking is simple and lightweight, while pessimistic locking offers strict control at the cost of possible performance problems. Which one to use depends on the particular needs of an application and the pattern of usage.
Top comments (0)