如何在Sidekiq Worker中重新加载模型解决快速查询记录失败问题
解决Rails Worker无法找到刚创建记录的问题
这个问题其实是Rails开发里很常见的事务竞态场景:当用户创建Schedule的请求还处于数据库事务未提交的状态时,Worker就已经被触发执行查询,这时候自然找不到刚创建的记录。你的临时重试方案能解决问题,但确实有更优雅、更可靠的替代方案,我给你整理几个思路:
方案1:用Sidekiq内置重试替代手动递归触发
Sidekiq本身自带成熟的重试机制,完全不需要自己手动捕获异常再调用perform_async。你可以直接配置重试次数和间隔,让Sidekiq自动帮你处理:
class TestWorker include Sidekiq::Worker sidekiq_options queue: Rails.env.to_sym, retry: 3, # 最多重试3次 retry_in: ->(count) { 5 * count } # 第n次重试间隔5*n秒(第一次5s,第二次10s...) def perform(schedule_id) # 直接查询,找不到会抛出RecordNotFound,Sidekiq会自动按配置重试 schedule = ScheduledTest.find(schedule_id) # 这里写后续的处理逻辑 end end
这种方式比手动递归更可靠,Sidekiq会帮你管理重试状态,不会出现无限递归的风险,也更符合Sidekiq的设计规范。
方案2:从根源消除竞态——事务提交后再触发Worker
这是最推荐的方案,因为它直接解决了问题的本质:不要在事务未提交时触发Worker。你可以在ScheduledTest模型里用after_commit回调,确保只有当创建记录的事务成功提交到数据库后,才触发Worker:
class ScheduledTest < ApplicationRecord # 仅在创建记录的事务提交后触发Worker after_commit :trigger_test_worker, on: :create private def trigger_test_worker TestWorker.perform_async(self.id) end end
这样Worker执行时,记录已经稳稳地存在于数据库中,完全不会出现找不到的情况,从根源上避免了竞态条件。
方案3:在Worker内部重试查询(不重启Worker)
如果因为某些原因你无法修改Worker的触发时机,也可以在同一个Worker进程内多次重试查询,而不是重新启动一个Worker实例:
class TestWorker include Sidekiq::Worker sidekiq_options queue: Rails.env.to_sym def perform(schedule_id) schedule = nil max_retries = 3 max_retries.times do |attempt| begin schedule = ScheduledTest.find(schedule_id) break # 查询成功就跳出循环 rescue ActiveRecord::RecordNotFound # 最后一次重试不等待,直接抛出异常 sleep(5 * (attempt + 1)) unless attempt == max_retries - 1 end end # 如果多次重试还是找不到,抛出异常让Sidekiq处理(或者按你的需求处理) raise ActiveRecord::RecordNotFound, "Schedule #{schedule_id} not found after #{max_retries} retries" unless schedule # 后续处理逻辑 end end
这个方案在同一个Worker进程内完成多次查询重试,不需要重新触发Worker,也能解决问题,但本质还是“等待数据库同步”,不如方案2彻底。
内容的提问来源于stack exchange,提问作者LewlSauce
相关产品推荐
相关产品推荐

