Rails“每用户”互斥锁

Rails "per-user" mutex

提问人:Zack 提问时间:11/12/2023 更新时间:11/13/2023 访问量:37

问:

有没有办法在 Rails 中配置互斥锁,只阻止同一用户同时运行该代码(而不是阻止该代码部分中的所有并发)?

例如,我有以下方法:

  def self.update(user, day, segment, category_id)
    entry = find(user, day, segment)
    
    if entry.nil?
      entry = Entry.new(user: user, date: day, segment: segment)
    end

    entry[key_for_id(category_id)] = category_id
    entry.save!
  end

问题在于,如果同一个用户以某种方式同时执行此方法,则将创建两个单独的 Entry 对象(而不是第一次执行创建 Entry,第二次更新它)

创建一个基本的互斥锁将解决这个问题;但是,我不需要禁止关键部分的所有并发执行(我认为这可能会对性能产生重大影响),我只需要确保同一用户不能同时进入关键部分(这相对较少;但已经发生)。

有没有办法在 Rails 中做到这一点?

Ruby-on-Rails 互斥

评论

1赞 dbugger 11/13/2023
更快的方法是简单地在入口表上为 :user_id、:d ate 和 :segment_id 添加唯一约束。如果用户以某种方式发出了两个请求,则不会创建第二个请求。
0赞 Zack 11/13/2023
但是第二个请求不会失败,直到 ,对吗?save!
0赞 dbugger 11/13/2023
这是正确的。

答:

0赞 Zack 11/13/2023 #1

正如@dbugger在评论和另一篇文章中所建议的那样:ActiveRecord 的 find_or_create* 方法从根本上存在缺陷吗?

解决方案是添加一个唯一索引和一个 .retry

class AddUniqueIndexToEntries < ActiveRecord::Migration[7.0]
  def change
    add_index :entries, [:user_id, :date, :segment], unique: true
  end
end


    begin 
      entry = find(user, day, time)
      if entry.nil?
        entry = Entry.new(user: user, date: day, segment: segment)
      end
    
      entry[key_for_id(category_id)] = category_id
      entry.save!
    rescue ActiveRecord::RecordNotUnique
      retry
    end