使用并发的Rails和Sidekiq作业的结果

时间:2018-06-21 19:30:26

标签: ruby-on-rails postgresql concurrency sidekiq

在我们的方案中,Sidekiq将运行25个并发作业。我们需要获得一个整数作为每个作业的结果,并将所有结果汇总在一起。在这种情况下,我们正在查询外部API并返回计数。我们希望所有API请求的总数。

Report对象存储最终总数。 PostgreSQL是我们的数据库。

在每项工作结束时,我们将使用发现的其他记录来增加报告的数量。

Report.find(report_id).increment(:total, api_response_total)

这是跟踪运行总额的好方法吗?会有Postgresql并发问题吗?有没有更好的方法?

1 个答案:

答案 0 :(得分:1)

increment不应导致并发问题,在sql级别,它会使用COALESCE(total, 0) + api_response_total进行原子更新。竞争条件只有在您手动添加然后保存对象后才能出现。

report = Report.find(report_id)
report.total += api_response_total
report.save # NOT SAFE

注意:即使使用increment!,Rails级别的值也可能是陈旧的,但在数据库级别是正确的:

# suppose initial `total` is 0
report = Report.find(report_id) # Thread 1 at time t0
report2 = Report.find(report_id) # Thread 2 at time t0
report.increment!(:total) # Thread 1 at time t1
report2.increment!(:total) # Thread 2 at time t1
report.total #=> 1 # Thread 1 at time t2
report2.total #=> 1 # Thread 2 at time t2
report.reload.total #=> 2  # Thread 1 at time t3, value was stale in object, but correct in db
  

这是跟踪运行总额的好方法吗?会有Postgresql并发问题吗?有更好的方法吗?

我将更喜欢使用Sidekiq Batches进行此操作。它允许您运行一批作业并为该批次分配一个回调,该回调将在处理完所有作业后执行。示例:

batch = Sidekiq::Batch.new
batch.description = "Batch description (this is optional)"
batch.on(:success, MyCallback, :to => user.email)
batch.jobs do
  rows.each { |row| RowWorker.perform_async(row) }
end
puts "Just started Batch #{batch.bid}"
  

我们需要获得一个整数作为每个作业的结果,并将所有结果相加。

请注意,Sidekiq作业doesn't do anything with the returned value的值已被GC'ed并被忽略。因此,在上述批处理策略中,回调中将没有作业数据。您可以量身定制该解决方案。例如,在Redis中有一个LIST,键为批处理ID,然后推送每个完整作业的值(在perform中)。在回调中,只需使用列表并将其累加即可。