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

Using the result of concurrent Rails & Sidekiq jobs

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

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

在每个作业结束时,我们都会使用找到的附加记录来增加报告。

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

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

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

Is this a good approach to track the running total? Will there be Postgresql concurrency issues? Is there a better approach?

我更喜欢 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}"

We need to get a single integer as the result of each job and tally all of the results together.

请注意,Sidekiq 作业 doesn't do anything with the returned value 和值已被 GC 处理并被忽略。所以,在上面的批处理策略中,回调中不会有作业数据。您可以量身定制该解决方案。例如,在 redis 中有一个 LIST,key 作为 batch id,并推送每个完整作业的值(在 perform 中)。在回调中,只需使用列表并对其求和即可。