Sidekiq首次执行无法找到ID问题排查与解决需求
问题:Sidekiq订阅者首次接收
:message_created事件时无法找到对应记录 问题场景
现有代码中,ChatRoom类的add_message方法创建Message后立即发布:message_created事件,但Sidekiq订阅者Messages::Notify首次接收事件时,无法通过传入的message_id找到对应记录;任务重试时则能正常工作。即使给Message的create!单独包裹事务,问题依然存在。由于发布事件需要ChatRoom类中的额外信息,无法将发布操作移到Message类的after_commit回调中。
相关代码:
class ChatRoom < ApplicationRecord #... def add_message(args) #... message = self.messages.create!( #... ) publish(:message_created, message_id: message.id, notify: notify ) end end class Messages::Notify < ApplicationSubscriber include Wisper::Publisher def message_created(args) internal_note = InternalNote.find(args[:message_id]) #... end end
尝试过的无效方案:
class Message < ApplicationRecord def create!(args) self.transaction do super(args) end end end
原因分析
这是典型的事务未提交就触发异步任务的数据库隔离问题:
- 即使
Message.create!本身执行完成,但如果ChatRoom#add_message被包裹在某个外层事务(比如调用add_message的代码处于ChatRoom的事务操作中)里,此时Message的记录仅在当前数据库连接可见,Sidekiq工作进程使用独立连接,在事务提交前无法读取到这条未提交的记录。 - 给
Message.create!单独加事务无济于事——内层事务提交后,外层事务未结束的情况下,其他连接依然看不到这条记录,数据库默认的READ COMMITTED隔离级别会限制未提交事务的可见性。
解决方案
方案1:利用ChatRoom的事务回调延迟发布事件
将事件发布逻辑放到ChatRoom的after_commit回调中,确保在整个事务(包括ChatRoom和关联Message的操作)提交后再触发事件,此时记录对Sidekiq进程完全可见:
class ChatRoom < ApplicationRecord #... def add_message(args) #... message = self.messages.create!( #... ) # 暂存发布参数,等待当前ChatRoom的事务提交后执行发布 after_commit do publish(:message_created, message_id: message.id, notify: notify ) end end end
这里的after_commit是ActiveRecord的实例方法,会绑定到当前ChatRoom实例的事务生命周期,确保所有关联操作的事务都提交后再发布事件。
方案2:主动在订阅者中添加存在性检查与短延迟重试
如果无法修改发布时机,可以在订阅者中主动检查记录是否存在,通过短时间重试避免事务未提交的问题:
class Messages::Notify < ApplicationSubscriber include Wisper::Publisher def message_created(args) internal_note = nil # 最多重试3次,每次间隔0.5秒 3.times do internal_note = InternalNote.find_by(id: args[:message_id]) break if internal_note sleep 0.5 end # 多次重试后仍找不到则抛出异常,交给Sidekiq自动重试 raise ActiveRecord::RecordNotFound, "InternalNote with id #{args[:message_id]} not found" unless internal_note #... 后续业务逻辑 end end
这种方法属于临时缓解,适合快速降低错误日志量,但不如方案1从根源解决问题。
方案3:配置Wisper与Sidekiq的适配机制
如果使用wisper-sidekiq适配器,可以结合其提供的事务后触发能力,确保事件在事务提交后再被Sidekiq入队。例如可以自定义发布方法,将事件入队逻辑绑定到事务回调中。
内容的提问来源于stack exchange,提问作者Toma
相关产品推荐
相关产品推荐

