Reputation: 5528
I have a reservation system where I need to lock a record so that two users cannot book it at the same time.
The model Seat
has three statuses: available, reserved and booked
.
While being reserved
the user has 5 minutes to complete the booking.
I can do this with a pessimistic locking. I add a lock_version
column to the table and do
def reservation
@seat = Seat.available.order(created_at: :desc).first
if @seat
@seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
rescue ActiveRecord::StaleObjectError
retry
end
end
Since in this system the possibility of conflicts is very likely, I'd like to use a pessimistic locking intead of an optimistic one, but I didn't succeed.
This is the code I tried (note the addition of lock
):
def reservation
@seat = Seat.available.order(created_at: :desc).lock.first # executes a SELECT FOR UPDATE
if @seat
@seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
end
The problem is that two records are still being selected at the same time even though the query is
SELECT * from seats where status = 'available' order by created_at desc limit 1 for update
.
I am not sure how to implement an optimistic locking for such case.
Upvotes: 0
Views: 176
Reputation: 5528
After some hours of research I found out what was the issue. Looking again at the optimistic code, the issue is that there's no transaction defined.
Adding a transaction fixes the problem. Here is the corrected code:
def reservation
Seat.transaction do
@seat = Seat.available.order(created_at: :desc).lock.first
if @seat
@seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
end
end
now the select for update
and update of the record itself are within the same transaction.
Upvotes: 2