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 touches 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 touched. if these touches become interweaved:

  1. process 1 touches user a
  2. process 2 touches user b
  3. process 1 touches user b
  4. 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 touches 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

Popular posts from this blog

node.js - Mongoose: Cast to ObjectId failed for value on newly created object after setting the value -

[C++][SFML 2.2] Strange Performance Issues - Moving Mouse Lowers CPU Usage -

javascript - React + webpack: 'process.env' is undefined -