冪等性は、同じ処理を何回実行しても結果が壊れないようにする考え方です。 Webhook の再送、API のリトライ、ジョブの再実行など、「2回来るかもしれない」場面すべてに関わります。
HTTP メソッドにも冪等・非冪等がある
HTTP の仕様では、GET PUT DELETE は冪等、POST は冪等ではないと定められています。
PUT /users/1で同じ内容を2回送っても、結果は1回送ったときと同じPOST /ordersを2回送ると、注文が2件できる
つまり POST を使う場面こそ、自前で冪等性を用意する必要があるということです。決済APIが Idempotency-Key のようなヘッダーを用意しているのは、非冪等な POST に冪等性を後付けするためです。
アプリの if 文でチェックすると抜ける
よくある実装はこうです。
if (処理済みか?) return; // ← ここと
処理する(); // ← ここの間に、もう1件が入り込む
保存する();
同じ通知がほぼ同時に2件届くと、両方が「未処理」と判定して両方が処理を進めます。負荷が上がったときだけ再現する、いちばん厄介なタイプのバグです。
確実なのはデータベースに判定させることです。イベントIDに UNIQUE 制約を張り、先に INSERT を試みて、重複エラーが返ったら「もう処理済み」と判断します。排他制御を自前で書くより短く、確実です。
何を鍵にするか
- 送信側がイベントIDを付けてくれるなら、それをそのまま使う
- なければ「注文番号+イベント種別」のように業務上一意になる組み合わせを作る
- 本文全体のハッシュは避ける(送信側が本文の書式を変えただけで別扱いになる)
実務で見るポイント
- 「たぶん1回しか来ない」で作らない。タイムアウトと再送は必ず起きる
- 記録は処理の前に残す。処理が途中で落ちたときの再開判断にも使える
- 二重送信の被害が大きい処理(決済・メール・在庫)から順に手を入れる