如何让一个job每月幂等1运行(Rails5)?
How to make a job idempotent for 1 run every month (Rails 5)?
我需要根据 EST/EDT 时区每月生成一次发票(客户遍布全国,但在这个行业中,账单发生在同一时区)。
我正在创建一个 GenerateInvoicesJob
,但我无法推理出 100% 完美的发票生成方式,因此没有任何可能 duplicate/confusion 关于:
- 每月只生成一次发票
- 让工作运行天天
- 使作业幂等
那么最后一点对我来说是最难的一点,我如何确保EST/DST和1小时的时间没有错误
这是我的 clock.rb:
every(1.day, 'GenerateInvoicesJob', tz: 'America/New_York', at: '04:00') do
Delayed::Job.enqueue GenerateInvoicesJob.new, queue: 'high'
end
这是我工作的重中之重:
Unit.where(enabled: true)
.joins(:user)
.where('last_invoice_generated_at <= ?', Time.now.utc.end_of_month)
.each do |unit|
ActiveRecord::Base.transaction do
unit.update_attributes(
last_invoice_generated_at: Time.now.utc
)
invoice = Invoice.create!(
...
)
line_item = LineItem.create!(
...
)
end
我意识到直接条件逻辑可能是错误的,所以这不完全是我的问题...我对这个问题的主要补充是总体上最好的方法是什么,这样我可以确保 EST 中的所有时间都是100% 已解决,包括奇怪的 1 小时故障错误等。这项工作非常重要,所以我正在犹豫如何让它变得完美。
最重要的是,我不确定是否应该将 UTC 存储在数据库中....通常我知道你总是应该存储 UTC,但我知道 UTC 没有夏令时,所以我担心如果我这样存储它,工作可能 运行 一次,发票不会 运行 正确
我会在 worker 中做这样的事情:
# `beginning_of_month` because we want to load units that haven't
# been billed this month
units_to_bill = Unit.where(enabled: true)
.where('last_invoice_generated_at < ?', Time.current.beginning_of_month)
# `find_each` because it needs less memory
units_to_bill.find_each do |unit|
# Beginn a transaction to ensure all or nothing is updated
Unit.transaction do
# reload the unit, because it might have been updated by another
# task in the meantime
unit.reload
# lock the current unit for updates
unit.lock!
# check if the condition is still true
if unit.last_invoice_generated_at < 1.month.ago
# generate invoices
# invoice = Invoice.create!(
# line_item = LineItem.create!(
# last step update unit
unit.update_attributes(
last_invoice_generated_at: Time.current
)
end
end
end
我需要根据 EST/EDT 时区每月生成一次发票(客户遍布全国,但在这个行业中,账单发生在同一时区)。
我正在创建一个 GenerateInvoicesJob
,但我无法推理出 100% 完美的发票生成方式,因此没有任何可能 duplicate/confusion 关于:
- 每月只生成一次发票
- 让工作运行天天
- 使作业幂等
那么最后一点对我来说是最难的一点,我如何确保EST/DST和1小时的时间没有错误
这是我的 clock.rb:
every(1.day, 'GenerateInvoicesJob', tz: 'America/New_York', at: '04:00') do
Delayed::Job.enqueue GenerateInvoicesJob.new, queue: 'high'
end
这是我工作的重中之重:
Unit.where(enabled: true)
.joins(:user)
.where('last_invoice_generated_at <= ?', Time.now.utc.end_of_month)
.each do |unit|
ActiveRecord::Base.transaction do
unit.update_attributes(
last_invoice_generated_at: Time.now.utc
)
invoice = Invoice.create!(
...
)
line_item = LineItem.create!(
...
)
end
我意识到直接条件逻辑可能是错误的,所以这不完全是我的问题...我对这个问题的主要补充是总体上最好的方法是什么,这样我可以确保 EST 中的所有时间都是100% 已解决,包括奇怪的 1 小时故障错误等。这项工作非常重要,所以我正在犹豫如何让它变得完美。
最重要的是,我不确定是否应该将 UTC 存储在数据库中....通常我知道你总是应该存储 UTC,但我知道 UTC 没有夏令时,所以我担心如果我这样存储它,工作可能 运行 一次,发票不会 运行 正确
我会在 worker 中做这样的事情:
# `beginning_of_month` because we want to load units that haven't
# been billed this month
units_to_bill = Unit.where(enabled: true)
.where('last_invoice_generated_at < ?', Time.current.beginning_of_month)
# `find_each` because it needs less memory
units_to_bill.find_each do |unit|
# Beginn a transaction to ensure all or nothing is updated
Unit.transaction do
# reload the unit, because it might have been updated by another
# task in the meantime
unit.reload
# lock the current unit for updates
unit.lock!
# check if the condition is still true
if unit.last_invoice_generated_at < 1.month.ago
# generate invoices
# invoice = Invoice.create!(
# line_item = LineItem.create!(
# last step update unit
unit.update_attributes(
last_invoice_generated_at: Time.current
)
end
end
end