ActiveRecord touch causing locks

My application is widely used touchto take advantage of the Rails template caching system. There is a certain work that my application does when many relationships are created between many different objects in a package. Sometimes some of this work leads to a cascading touches resulting in a dead end.

I can create code around this script where I see this happening often, but seeing that it has revealed a big problem that can happen in other scripts, although this is very unlikely.

To understand this, think of two people following each other on Twitter at exactly the same moment. They both click "Follow", which creates interconnected objects between them, and then each of their touched entries . If these strokes are intertwined:

  • process 1 concerns user A
  • process 2 concerns user B
  • process 1 concerns user B
  • process 2 concerns user A

Each process uses a database transaction, so this will lead to a deadlock.

Am I really mistaken that this can happen in the normal operation of an application outside of my strange batch scenario? If I am not mistaken, is there a solution? Can I move touches out of a transaction in any way ? (Last Write Wins is great for updating updated_at anyway ...)

update -

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)
+1
1

, , , , , ActiveRecord .

User.transaction do
  @u1, @u2 = User.lock.where(id: [1,2])
  # Those two records are now locked, other transaction instances
  # can't proceed till this transaction block is exited 
  Follow.create!(follower: @u1, followee: @u2)
end
# lock is released here

: id: [2,1] , .

2: , , , , , , .


: , , , , after_create
class Follow
  belongs_to :follower
  belongs_to :followee

  after_create :touch_users

  def touch_users
    # no locking and direct database update
    User.where(id: [follower.id, followee.id]).update_all(updated_at: :Time.now)
  end
end

,

Follow.create!(follower: @u1, followee: @u2)

. #update_all activerecord , - after_update, .

+2

Source: https://habr.com/ru/post/1531645/


All Articles