Skip to content

リトライ、冪等性、補償

この記事は英語版から翻訳されました。最新版は英語版をご覧ください。

ワークフローとジョブシステムは「もう一度試す」と「二重に実行してはいけない」の間にあります。ネットワーク、ワーカー、依存先は失敗するためリトライは必要です。しかし外部副作用がすでに起きている可能性があるため危険です。設計の中心は、再試行に対する冪等性と、コミット済みステップに対する補償です。

リトライは契約

リトライポリシーは次を定義します。

  • どのエラーがretryableか
  • 最大attempt数または最大経過時間
  • backoffとjitter
  • cancellation後のattempt可否
  • retry exhaustion後の状態
  • どの冪等キーで副作用を守るか

Backoff

text
delay = min(max_delay, base_delay * 2 ** attempts)
delay = random_between(delay * 0.5, delay * 1.5)

jitterは依存先障害後の同期retry stormを防ぎます。

冪等キー

操作良いkey
顧客に課金payment:{order_id}:{payment_attempt_id}
注文メール送信email:order-confirmation:{order_id}
出荷作成shipment:{order_id}:{warehouse_id}
日次レポート公開report:{report_id}:{data_interval}

keyはretryをまたいで安定させます。attemptごとに新しいkeyを作ると冪等性は無効になります。

Dedupe Store

sql
CREATE TABLE idempotency_records (
  key TEXT PRIMARY KEY,
  status TEXT NOT NULL,
  response JSONB,
  created_at TIMESTAMPTZ NOT NULL,
  expires_at TIMESTAMPTZ
);

dedupe storeの確認と書き込みは、副作用境界でatomicに行うか、下流サービスに委譲します。

補償

補償はrollbackではありません。過去のコミット済みアクションを業務的に打ち消す新しいアクションです。

補償も失敗します。補償にもretry、冪等性、alert、監査証跡が必要です。

Retry Matrix

エラー種別RetryCompensation備考
response前timeoutYesUnknownidempotency recordを照会
5xxYes通常Nobounded retryとcircuit breaker
4xx validationNoNo理由付きfail
partial multi-step failureMaybeYes成功済みstepを補償
human rejectionNo業務依存rejected終端状態

Outbox

DB commitとmessage publishを同時に扱う場合はOutbox Patternを使います。

Retry Budget

無限リトライはインシデントを隠し、コストを爆発させます。

  • workflow instance別
  • activity type別
  • tenant別
  • downstream dependency別
  • time window別

budgetが尽きたら、可視的にfailするかrepair queueへ送ります。

関連パターン

MITライセンスの下で公開。Babushkaiコミュニティが構築。