繰り返し戦略とidempotency
1)なぜそれを必要とします
ネットワークでは、失敗は標準です:タイムアウト、過渡エラー、ネットワークフラッピング、オーバーロード。次の場合にのみ、信頼性を向上させます:1.安全を繰り返します(idempotent)、
2.繰り返しの間の遅延が観察され、
3.制限/クォータと中毒「健康」が尊重されます。
目標は、誤ったテイクや人種のない事業運営のレベルで効果的に一度の行動です。
2)配信セマンティクスの分類
At-once:反復なし、損失のリスク(ロギング、火災と忘れ)。
少なくとも一度:重複が可能→消費者のアイデンポテンスが必要(ほとんどのキュー、webhook)。
効果的に一度:重複は可能ですが、正しく重複しません(キー、トランザクション、アウトボックス)。
3)いつ、ときに引き込めないために
リトリートの意味:'408'、'429' ('Retry-After')、 '425' (Too Early)、 '499' (client closed on the perimeter)、 '5xx'、 '504'、ネットワークのタイムアウト/ブレーク、ゲートウェイの'502'、 「connection reset」。
クエリ'400/ 401/403/404/422'を変更せずにリトラクトしないでください。
物議を醸す事例:「409紛争」(通常はretrayim;最初に操作の状態を読み、意図を再確認します)。
4)タイムアウト、バックオフ、ジッタ
4.1ルール
最初のタイムアウト、次にレトロ:各リクエストには「締め切り」が必要です。
指数関数バックオフ:'delay_n=base 2^n'、 'max_delay'を制限します。
ジッタが必要です:「鈍い同期波」を分離するためにランダム性を追加します。
4.2ジッターパターン
フルジッタ:'sleep=rand (0、 base2^n)'が最適です。
装飾されたジッタ:'sleep=min (max_delay、 rand (base、 sleep_prev3))'-長いダイアログ用。
等しいジッタ:'sleep=base2^n/2+rand (0、 base2^n/2)'-ソフトバリエーション。
4.3再試行予算
リトレイの割合を制限する:- 'retry_budget_per_min=max (α success_rps、 floor β)';通常は'α=0である。1–0.2`.
- 予算が使い果たされた場合は、フェイルファスト/サーキットブレーカ「open」に切り替えます。
5)速度制限および遮断器との相互作用
'Retry-After'、 'RateLimit-Reset'を尊重し、バックオフにカウントします。
高い'5xx'/タイムアウトでは、リトレイ周波数と全体の同時性が低下します。
- ハーフオープン:制限されたサンプリングを可能にします。
- オープン:即座に拒否します(リソースを保存します)。
- 定休日:通常業務。
- 書き込み操作では、アグレッシブなレトレイよりも明確なヒントで409/503を返すことをお勧めします。
6)書き込み操作のIdempotency
6.1一般的なアイデア
同じ意図→1つの結果。基本はidemotenceキーと実行レコードの保存です。
6.2 HTTP契約
クライアントはヘッダーを送信します:
Idempotency-Key: 7a6b7f9e-2a46-4d0b-9c3a-2b30e1c3c9e3
Idempotency-Key-Expiry: 24h # optional
サーバー:
- 最初の成功時に保存(キー、結果→ステータス、ボディハッシュ)
- 繰り返されると、古いレスポンスとヘッダ'Idempotency-Replay: true'を返します。
- ボディの競合(同じキーが異なるペイロード)の場合-'409競合'。
6.3ストレージとTTL
テーブル/値キー:'idempotency_key'、 'request_hash'、 'result'、 'status'、 'expiry_at'。
TTL=可能なリプレイと遅延のウィンドウ(通常、支払いのための24-72時間)。
'idempotency_key'によるインデックス;高い負荷-ハッシュシャーディングのため。
6.4スキーマ例(SQL)
sql
CREATE TABLE idempo_store (
key UUID PRIMARY KEY,
req_hash BYTEA NOT NULL,
status INT NOT NULL,
response JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expiry_at TIMESTAMPTZ NOT NULL
);
6.5ハンドラ擬似コード
pseudo handle_write(req):
k = req. headers["Idempotency-Key"]
h = hash(req. body)
rec = idempo_store. get(k)
if rec and rec. req_hash == h:
return rec. status, rec. response, {"Idempotency-Replay": "true"}
if rec and rec. req_hash!= h:
return 409, problem("IDEMPOTENT_CONFLICT")
begin tx result = apply_business_mutation (req) # change status upsert once (idempo_store, key = k, req_hash=h, status = 201, response = result, expiry = now () + 2d)
commit
return 201, result
7)「効果的に一度」パターン
Transactional Outbox:ビジネスイベントを記録し、バックグラウンドリレーを通じて同じデータベーストランザクションからメッセージを送信します。消費者は馬鹿げています。
コンシューマの受信トレイ/処理テーブル:重複を無視するために'event_id'を保存します。
Kafkaで正確に1回≠正確に1回のビジネスで:生産者/消費者EOSであっても、適用されたロジックはまだidempotentでなければなりません。
トランザクションの補償(Saga):ステップが引き込み、副作用を引き起こす場合、システムを不変量に戻します。
8)特別な場合: 支払および金融取引
強力なidempotency: キーは操作ロジックにバインドされます(例:'external_payment_id')
PSPでの重複排除-'merchant_reference'→を繰り返した場合、PSPは同じ結果を返します。
「from the client」: 「Idempotency-Key」の場合にのみ許可します。そうでなければ二重書き込みのリスクがあります。
競争:実行期間中は「アカウント/ツール/契約」をロックします。繰り返されたら409/423を返して下さい。
オブザビリティ:メトリック'idempo_replay_total'、 'idempo_conflict_total'。
9) Webhookと外部の課題
HMAC署名とタイムウィンドウ;最初に検証してから処理します。
送信者リトレイ:指数関数backoff+jitter、 'max_attempts'、およびDLQ。
消費者-idempotent: 'event_id'→table/in-memoryキャッシュ;「整頓」注文は保証されていません。
コード:2xx=successful、 4xx=do not repeat、 5xx/timeout=repeat。
10)キューとバックグラウンドタスク
デフォルトで最低1回→重複することは避けられません。
'task_id'/'event_id'と実行ステータスを保存します。重複-ショートパス「リプレイ」。
DLQと毒メッセージ:試行カウンター、検疫、手動解析。
競争限界(セマフォー)と偶像労働者。
11)バージョン管理と「自然な」キー
自然キー(口座番号+日付+文書番号)は反復に対する抵抗を増加させます。
スキーマ/バージョンを変更する場合は「、Idemotency-Key」またはクエリハッシュにバージョンキーを含める。
12) HTTPヘッダーとクライアントへのプロンプト
'Idempotency-Key'、 'Idempotency-Replay'、 'Retry-After'、 'Prefer: wait=<sec>'(長い操作で)、'If-Match'/'ETag'(楽観的ロック)。
有効な'Retry-After'との主要な競合425/429/503の409。
「長い」操作の場合-非同期ステータスの受信(ステータスリソースごとに'202 Accepted'+'Location')。
13)テストとカオスシナリオ
否定的なテスト:二重送信、別のボディとの反復、時計の非同期。
順不同:'t2'は't1'の前にあります。
タイムアウト/'RST'/'EOF'の注入、半分のリクエスト(slow-POST)。
Fallen idempotency storage→fail-closed動作(ダブルライトオフよりも優れた障害)。
14)メトリックとアラート
'retries_total {reason}'、 'retry_budget_used {route}'、 'backoff_seconds_bucket'。
'idempo_replay_total'、 'idempo_conflict_total'、 'duplicate_detected_total'
ルートによって409/425/429/5xxを共有して下さい;p95/p99「成功への時間」とリトリート。
アラート:バーンレートのリトレイ予算、idempotence競合の急増、DLQの増加。
15) Antipatterns
すべての間違いを連続して引き戻します。
ジッタ→リトレースの同期波の欠如。
TTLとクリーニングなしの長寿命のキー。
副作用コミット後の結果の保存(アウトボックス違反)。
'trace_id'/'idempotency_key'のないログは→生成できません。
書き込み操作のアグレッシブな並列リトレイ。
16) Prod Readinessチェックリスト
- 統一されたポリシー:何retrayim、何ではない;コードおよび顧客のプロンプト。
- 指数関数バックオフ+フルジッタ;'retry_budget'を指定します。
- 契約'Idempotency-Key'+結果をTTLで保存します。
- イベントの受信トレイ/受信トレイ;DLQ;競争の限界。
- サーキットブレーカとの統合、尊重'Retry-After'。
- メトリック/リトレイによるアラート/重複/競合。
- カオステストとネットワーク障害エミュレーションのセット。
- 顧客ドキュメント-バックアップとステータスの例。
17) TL;DR(ドクター)
リトリートは、idempotencyと一緒にのみ有用です。「Idempotency-Key」と結果ストレージを入力し、ジッタと再試行予算で指数関数的なバックオフを適用し「、Retry-After」を尊重し、サーキットブレーカと統合します。イベントの場合-outbox/inbox;支払、厳密な重複除外およびロックのため。リトレイと競合を測定し、重複とタイムアウトをテストします。