プレイブック

API (アプリケーション プログラミング インターフェイス) リクエストを超えた冪等性 (反復可能で安全なトランザクション) (API)

冪等性は単一のヘッダーではありません。これは、API キーからステップ ポインターまで、5 つの異なるレイヤーで個別に設定する必要がある防御スタックです。

分散型決済エンジン

一部 5 の 22

取得と完了の間のギャップを埋める一連の分散型支払いアーキテクチャ。

Distributed payment engine architecture diagram

冪等性はヘッダーではなくスタックです

ほとんどのチームは冪等性を「API リクエストに Idempotency-Key ヘッダーを追加する」こととして学習し、そのままにしておきます。これは決済システムにおける氷山の一角にすぎません。同じプロセスが、少なくとも 5 つの異なる時点でシステム内で「繰り返される」可能性があります。クライアントの再試行、PSP の Webhook の再試行、メッセージ キューの少なくとも 1 回の配信、ワーカーのジョブの取得、サガ ステップの再実行です。```text Client retry → API idempotency key PSP retry → Gateway event id Broker retry → Inbox kaydı Worker retry → Job uniqueness Saga retry → Step marker


## 最初に説明した概念```text
📦 API Idempotency Key
İstemcinin gönderdiği, aynı isteğin tekrar gönderilmesi halinde aynı sonucu garanti eden benzersiz anahtar.

📦 Gateway Event ID
PSP'nin her webhook veya bildirime verdiği benzersiz kimlik; aynı olayın birden fazla teslimatını ayırt etmeye yarar.

📦 Inbox
Gelen bir olayın işlenmeden önce kaydedildiği, tekrarları filtreleyen dayanıklı bir tablo.

📦 Job Uniqueness
Bir arka plan işinin (job) aynı iş anahtarıyla ikinci kez kuyruğa girmesini önleyen kısıt.

📦 Step Marker
Bir saga adımının tamamlandığını kalıcı olarak işaretleyen, yeniden çalışmayı önleyen kayıt.
```これら 5 つの概念は互換性がありません。 API 冪等性キーは、ユーザーとクライアントの間での重複を防ぎます。ただし、PSP の Webhook、キューの配信、ワーカーのジョブにはまったく影響しません。

## レイヤ 1: API リクエスト

クライアントが「Pay」ボタンを 2 回押すと (ネットワーク遅延、ダブルクリック)、クライアントは同じ `Idempotency-Key` を使用してリクエストを送信します。サーバーがこのキーを認識した場合、プロセスは再度実行されません。最初の実行の結果を返します。この層は完全に API とクライアント間の契約です。```text
POST /payments  Idempotency-Key: abc123
  → ilk çağrı: işlem çalışır, sonuç kaydedilir
  → aynı key ile ikinci çağrı: kayıtlı sonuç döner, işlem tekrar çalışmaz
```## レイヤ 2: PSP からのイベント

PSP は、ネットワークの問題または独自の再試行ポリシーにより、同じイベント (「キャプチャ成功」など) を複数回送信することがあります。これらの各イベントには、PSP によって与えられる一意のイベント ID があります。この ID が表示された場合は、イベントを再度処理しないでください。ただし、処理しなかったことを PSP に明確に通知 (ACK) する必要もあります。```text
Webhook #1: event_id=evt_001, type=payment.captured
Webhook #2: event_id=evt_001, type=payment.captured  (tekrar teslim)
  → aynı event_id görülmüşse, işlem atlanır, 200 OK döner
```## レイヤ 3: メッセージ キュー / 受信箱

イベントを直接処理するコード内でイベント ID チェックが行われる場合、競合状態で 2 つの異なるワーカーによって同じイベントが処理される危険があります。これが、受信ボックス パターンが使用される理由です。イベントは、最初に一意の制約を使用して受信ボックス テーブルに書き込まれます。この書き込みが失敗した場合 (すでに存在している場合)、イベントはすでに認識されており、操作は安全にスキップされます。```text
INSERT INTO inbox (event_id, ...) VALUES ('evt_001', ...)
  → başarılı: ilk kez görülüyor, işlem kuyruğuna eklenir
  → unique constraint hatası: zaten görülmüş, sessizce atlanır
```## レイヤ 4: ジョブ自体

受信トレイの後、ジョブはバックグラウンド ジョブに引き渡されます。このジョブは、それ自体で複数回キューに入れられる場合もあります (たとえば、再試行メカニズムまたは再デプロイメント中)。ジョブ キューは、同じジョブ キー (`payment_id + step_name` など) を持つ 2 番目のジョブを拒否する必要があります。```text
Job key: payment_id=pay_42, step=finalize_stock
  → aynı key ile ikinci job denemesi: kuyruk seviyesinde reddedilir
```## レイヤー 5: サーガ ステップ自体

ジョブ自体はクラッシュ後に再起動できるため、ジョブの実行中であっても、ステップ自体は冪等である必要があります。この最後の層は、第 3 章で紹介したステップ マーカーです。各ステップはその完了を永続的にマークし、ステップが再度実行されるとそのマークをチェックして、実際の作業 (在庫の差し引き、財務記録のオープン) を再度実行しないようにします。```text
Step marker: finalize_stock=DONE (payment_id=pay_42)
  → adım tekrar çağrılırsa, marker kontrol edilir, iş tekrar yapılmaz
```## これら 5 つのレイヤーが個別に必要な理由

各層は、クライアント、PSP、キュー、ワーカー、またはジョブ自体など、異なる「誰が再送信するか」という質問に答えます。 API レイヤーに冪等性のみをインストールし、他の 4 つをスキップした場合、システムではファイナライズ処理が「API で 1 回、バックグラウンドで 3 回」実行される可能性があります。このバグに気づくのは、通常、顧客からの苦情があった場合のみです。

## このエピソードで最も混乱を招く対戦```text
❌ Idempotency-Key header'ı eklemek, idempotency problemini çözer
✓ Bu header sadece istemci-API katmanındaki tekrarı çözer

❌ PSP event id kontrolü tek başına yeterlidir
✓ Event id kontrolü, race condition'a karşı bir inbox/unique constraint ile desteklenmelidir

❌ Job kuyruğu at-least-once teslimat yaparsa, iş otomatik olarak idempotent olur
✓ At-least-once teslimat + idempotent olmayan iş = güvenli değil; iş kendi başına idempotent olmalıdır

❌ Bir saga adımı başarıyla tamamlandıysa tekrar çağrılması zararsızdır
✓ Adım işaretçisi yoksa tekrar çağrı, yan etkiyi (stok düşme, ödeme) ikinci kez tetikler

冪等性スタックのチェックリストを作成します

  1. API には冪等キーがありますか?もしそうなら、結果はどのくらいの期間保存されますか?
  2. PSP Webhook のイベント ID を確認しますか、それとも各 Webhook を直接処理しますか?
  3. 受信トレイ テーブルのイベント ID に一意の制約がありますか、それともアプリケーション コードでチェックを実行しますか (競合状態のリスク)。
  4. ジョブ キューは、同じジョブ キーを持つ 2 番目のジョブを拒否しますか?
  5. 各サガ ステップは、それ自体の完了をチェックするマーカーをチェックしますか? それともジョブを実行するたびに再実行しますか?

これら 5 つのレイヤーのうち 2 つが欠落している場合、システムは「まれではあるが繰り返し発生する」ダブル コミット エラーが発生する可能性があります。

このセクションで覚えておくべきこと

  1. 冪等性は単一のヘッダーや単一のコントロールではありません。これは、少なくとも 5 つの独立したレイヤーに個別にインストールする必要があるスタックです。
  2. 各レイヤーは異なるリプレイ ソース (クライアント、PSP、キュー、ワーカー、サガ ステップ) に応答します。一方にはもう一方は含まれません。
  3. 受信箱パターンは、イベント ID チェックを競合状態に対して安全にする構造的なソリューションであり、単なる「if」チェックではありません。
  4. 少なくとも 1 回の配信モデルでは、非冪等ジョブ ステップは遅かれ早かれ 2 回実行されます。

冪性をヘッダーに減らすことは、5 階建ての建物に 1 つのドアを置き、他の 4 階の窓を開けたままにするようなものです。

FAQ

よくある質問

API冪等性キーとは何ですか?

同じリクエストが再度送信された場合に同じ結果を保証する、クライアントによって送信される一意のキー。

ゲートウェイイベントIDとは何ですか?

PSP が各 Webhook または通知に与える一意の ID。これは、同じイベントの複数の配信を区別するために役立ちます。

「Idempotency-Keyヘッダーを追加すると冪等性の問題が解決する」というのは本当ですか?

このヘッダーはクライアント API 層での繰り返しのみを解決します。

このセクションでは何を修正しますか?

このセクションでは、単一点ではなく、これら 5 つの層のそれぞれで冪等性を個別に設定する方法について説明します。冪等性は単一のヘッダーや単一のコントロールではありません。これは、少なくとも 5 つの独立したレイヤーに個別にインストールする必要があるスタックです。ほとんどのチームは冪等性を「API リクエストに `Idempotency-Key` ヘッダーを追加する」こととして学習し、そのままにしておきます。これは決済システムにおける氷山の一角にすぎません。同じプロセスが、少なくとも 5 つの異なる時点でシステム内で「繰り返される」可能性があります。クライアントの再試行、PSP の Webhook の再試行、メッセージ キューの少なくとも 1 回の配信、ワーカーのジョブの取得、サガ ステップの再実行です。

学んだエンジニアリング原則

  • 冪等性は単一のヘッダーではありません。これは、API、ゲートウェイ、受信箱、ジョブ、および saga ステップ層で個別に設定する必要があるスタックです。
  • 各層は異なる反復ソースに応答します。 1 つをインストールして他のものをスキップすると、システムは半保護されたままになります。
  • 少なくとも 1 回の配信モデルでは、非冪等ステップは遅かれ早かれ 2 回実行されることになります。

続きを読む

続きを読む

シリーズの次のシリーズ

シリーズの次のシリーズ

同じシリーズ

エッセイ

決済システムにおける送信箱/受信箱のパターン

データベースへの書き込みとイベントのパブリッシュが同じトランザクション内にない場合、そのうちの 1 つが失われるか、繰り返されます。送信トレイのブロードキャスト、コンシューマでの受信トレイの重複排除。

Paylaş