Wisper发布时出现ActiveRecordNotFound错误的原因排查
问题:发布
:message_created事件后订阅者报ActiveRecordNotFound错误 我们有ChatRoom模型和Message模型,两者是一对多关联。ChatRoom中实现了add_message方法,用于创建消息,同时接收布尔型参数notify来控制是否为新创建的消息发送邮件。创建消息后会发布:message_created事件,但订阅者执行时会抛出Couldn't find Message with 'id'=xxx错误——明明对应的消息ID存在,且消息已经持久化到数据库。
相关代码
ChatRoom模型代码
class ChatRoom < ApplicationRecord has_many :messages, -> { order(:created_at) } def add_message(args) content = args.fetch(:content) creator = args.fetch(:creator) notify = args.fetch(:notify, false) message = self.messages.create!(content: content, creator: creator) publish(:message_created, message_id: message.id, notify: notify) end end
事件订阅者代码
class MessageCreated::MailChatSubscriber < ApplicationSubscriber def message_created(args) message = Message.find(args.fetch(:message_id)) notify = args.fetch(:notify) Messages::Organizers::SendChatResponseMail.call( message: message, notify: notify ) end end
错误信息
Couldn't find Message with 'id'=575565
问题成因
核心原因是数据库事务的延迟可见性:
- 如果
add_message方法是在一个未提交的外层数据库事务中被调用的(比如控制器动作、上层服务类开启了事务),哪怕create!已经生成了消息ID并写入数据库,只要外层事务没提交,这条消息的数据变更就处于未提交状态。 - 事件订阅者如果是异步执行的(比如用Sidekiq等后台队列处理),会在独立线程中查询数据库,此时无法读取到未提交事务中的数据,就会触发ActiveRecordNotFound错误。
- 哪怕
create!本身是立即提交的小事务,只要外层存在未提交的大事务,这条消息的变更依然对其他线程不可见。
修复方案
方案一:在事务提交后发布事件
利用Rails的after_commit回调,确保事件在消息的事务完全提交后再发布。修改add_message方法:
def add_message(args) content = args.fetch(:content) creator = args.fetch(:creator) notify = args.fetch(:notify, false) message = self.messages.create!(content: content, creator: creator) # 等待事务提交后再发布事件 message.run_after_commit do publish(:message_created, message_id: message.id, notify: notify) end end
方案二:确保事件订阅同步执行(仅适合特定场景)
如果业务允许事件订阅同步执行(即发布事件后立即在当前线程处理),那么不会出现事务可见性问题,但这种方式可能影响主请求的响应速度,仅适合轻量级处理逻辑。
内容的提问来源于stack exchange,提问作者Brandon Teixeira
相关产品推荐
相关产品推荐

