ruby on rails - ActiveRecord touch causing deadlocks -
my app uses touch
extensively in order take advantage of rails' template caching system. there's type of work app when many relationships created between many different objects in batch. sometimes, of work results in resulting cascading touch
es causing deadlock.
i can code around 1 scenario seeing happen often, seeing has brought light larger problem, happen in other scenarios, albeit it's unlikely.
to understand this, think 2 people following 1 on twitter @ same moment. both click "follow", resulting in relationship objects being created between them , each of records being touch
ed. if these touches become interweaved:
- process 1 touches user a
- process 2 touches user b
- process 1 touches user b
- process 2 touches user a
each process using database transaction, result in deadlock.
am wrong happen in normal app operation outside of weird batch job scenario? if i'm not wrong, there solution? can somehow move touch
es outside of transactions? (last write wins fine updating updated_at anyway...)
update - more explanation of data models
class follow belongs_to :follower, touch: true belongs_to :followee, touch: true end @u1 = user.find(1) @u2 = user.find(2) # background job 1 follow.create!(follower: @u1, followee: @u2) # background job 2 follow.create!(follower: @u2, followee: @u1)
not sure makes deadlock add pessimistic lock on both records while you're handling them, prevent request handling them until lock released, activerecord
wait lock release before proceeding.
user.transaction @u1, @u2 = user.lock.where(id: [1,2]) # 2 records locked, other transaction instances # can't proceed till transaction block exited follow.create!(follower: @u1, followee: @u2) end # lock released here
note: passing id: [2,1]
won't return them in order, you'll need handle condition.
note 2: locking might affect overall app performance, since user model heavily used model, guess depends on how these follows happen.
update: here's second way might work, first follow model, no touches, instead
after_create
class follow belongs_to :follower belongs_to :followee after_create :touch_users def touch_users # no locking , direct database update user.where(id: [follower.id, followee.id]).update_all(updated_at: :time.now) end end
then controller normal transaction, or not @ all, cause don't need it
follow.create!(follower: @u1, followee: @u2)
note: #update_all
doesn't fire activerecord call backs , queries done directly on database, if have after_update
methods might want avoid method.
Comments
Post a Comment