危ない要求は最初のものではありません。送り手が最初の答えを聞かなかったか、聞いても再送したために、同じ要求がもう一度来ることです。決済事業者はそうします。キューもそうします。人がボタンを二度押すとブラウザもそうします。システムが二通目を新しい仕事として扱うと、二度請求し、二度送り、一つの賭けに台帳の二行を書きます。
重複は来る前提で
二度目の配送が、何もしない成功になるようにハンドラを設計します。一度目は仕事をして鍵を保存します。提供者のイベント id か、クライアントが送った鍵で、データベース内で一意です。二度目はその鍵を見つけ、同じ結果を返します。二度目の請求はありません。二度目のメールもありません。二行目もありません。
信頼するのは一意制約です。挿入の直前にアプリケーションコードで行う確認は、二つの worker が同時に起きたとき競合に負けます。データベースはその競合に負けません。負けた側は衝突を受け取り、「すでに済んだ」に変えます。
| 一回目の到着 | 二回目の到着 | |
|---|---|---|
| 照合 | キーは新しい | キーはすでに保存済み |
| 書く | 一行、副作用一つ | 新しい行はない |
| 返信 | 受理 | 同じ、すでに受理済み |
| 処理が途中で死んだら | 送信箱はまだそのメッセージを借りている | 二つ目は借りていない |
メッセージは書き込みと一緒に出る。そうでなければ出ない
リクエストの中、commit の前にメールやキューのメッセージを送ると、巻き戻った取引のメッセージが残ります。出ていくメッセージは業務の行と同じトランザクションに書き、別の worker が公開します。プロセスが死ねば、行とメッセージは両方あるか、両方ないかです。worker は公開を再試行できます。ハンドラは請求を再試行してはいけません。
取消は新しい仕訳であり、書き換えではない
取り消す必要があるとき、元の行は上書きしません。相殺の記入が何が変わったかを示し、最初の行は読めるままにします。一週間後でも、14:02 に何が信じられていたか答えられます。行を書き換えると、今何が信じられているかしか分からず、証人もいません。
再試行で操作が二度起こり得るなら、その操作は終わっていない。
ハンドラを書く前にキーに名前を付ける
再送のあいだに送り手が何を変えるか確かめていないなら、鍵は「リクエスト全体のハッシュ」ではありません。決済事業者は同じイベント id を、新しい配送時刻つきで再送します。その時刻が鍵の中にあると、二度目は新しく見え、防ぎたかった不具合を自分で作ります。送り手が安定だと約束する id を使います。約束がなければ、呼び出す側に一つ送るよう求め、それなしの呼び出しは拒みます。
それから、失敗を成功と同じ場所に書きます。衝突は、送り手にとってのエラーページではありません。再送への、予期された答えです。送り手は止まれるべきです。500 を返すと、三度目、四度目と試し、インシデントは同じ作業の待ち行列になります。
順調な道が 200 を返したからといって、終わりとはみなしません。テストは同じペイロードを再送し、副作用が一つだと確認します。それから commit と公開のあいだで worker を殺し、メッセージがなお一度だけ出ることを確認します。この二つのテストは、置き換えるインシデントより短いです。そこから始めます。
