我們如何以冪等的方式批量創建記錄?
在下面的示例中,如果一切按預期運行,則應創建 100,500 個工單。但是,假設至少有一個作業由于某種未知原因運行了兩次。
- 我們如何保證作業只創建請求的確切數量的票,而不是更多?
- 我們可以在沒有任何競爭條件風險的情況下做到這一點嗎?
語境
我正在嘗試快速批量創建 100k 條記錄,Sidekiq 最佳實踐建議作業應該是冪等的,即它們應該能夠運行多次并且最終結果應該相同。
就我而言,我正在執行以下操作:
- 我正在使用
insert_all(Rails 6 )能夠非常快速地進行批量創建(它跳過 Rails 驗證)。 - 如果任何批處理創建作業未能為其批處理創建所有記錄,則該嘗試以原子方式回滾并且作業失敗(并且稍后重試)。
例子
我們有一張raffles桌子:
id number_of_tickets_requested
創建新raffle記錄后,我們要在tickets表格中批量創建抽獎票:
id code raffle_id
假設我們剛剛創建了一個新的抽獎活動number_of_tickets_requested: 100500。
(免責宣告:我在示例中對內容進行了硬編碼,以使其更易于理解。)
到目前為止我的嘗試
在抽獎模式中:
MAX_TICKETS_PER_JOB = 1000
after_create :queue_jobs_to_batch_create_tickets
def queue_jobs_to_batch_create_tickets
100.times { BatchCreateTicketsJob.perform_later(raffle, 1000) }
BatchCreateTicketsJob.perform_later(raffle, 500)
end
在 BatchCreateTicketsJob 中:
def perform(raffle, number_of_tickets_to_create)
BatchCreateTicketsService.call(raffle, number_of_tickets_to_create)
end
在 BatchCreateTicketsService 中:
def call
Raffle.transaction do
# Uses insert_all to create all tickets in 1 db query
# It skips Rails validations so is very fast
# It only creates records that pass the db validations
result = Ticket.insert_all(tickets)
unless result.count == number_of_tickets_to_create
raise ActiveRecord::Rollback
end
end
end
private
def tickets
result = []
number_of_tickets_to_create.times { result << new_ticket }
result
end
def new_ticket
{
code: "#{SecureRandom.hex(6)}".upcase,
raffle_id: raffle.id
}
end
uj5u.com熱心網友回復:
作為參考,我最終選擇了:
with_lock防止競爭條件;- 確保原子性的事務;
tickets_count抽獎臺上的一個新的計數器列,以確保冪等性。
class BatchCreateTicketsService < ApplicationService
attr_reader :raffle, :num_tickets
def initialize(raffle, num_tickets)
@raffle = raffle
@num_tickets = num_tickets
end
def call
raffle.with_lock do
Raffle.transaction do
create_tickets
end
end
end
private
def create_tickets
result = Ticket.insert_all(tickets)
raise StandardError unless result.count == num_tickets
raffle.tickets_count = result.count
raffle.save
end
def tickets
result = []
num_tickets.times { result << new_ticket }
result
end
def new_ticket
{
code: "#{SecureRandom.hex(6)}".upcase,
raffle_id: raffle.id
}
end
end
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/446680.html
