失敗からの復旧は外部副作用をコミット後・冪等にし、資源は能動解放し再試行寄りで設計する
設計判断
非同期処理
冪等性
信頼性
原則
処理が途中で失敗しうる経路では、正しさ(重複を作らない)だけでなく「壊れた後にどう整合へ戻すか」を一級の設計対象にする。最適化するのは「部分失敗しても再実行・後追いで収束できること」、避けるのは「DB トランザクションの内側で非トランザクショナルな副作用を起こして失敗時に外部状態だけ残す設計」と「失敗を放置して資源(ロック・処理中状態)を握り続ける設計」。
判断基準
- 非トランザクショナルな副作用(検索インデックス更新、外部 API 連携、別サービスへの書き込み、通知)はトランザクションの内側で起こさない。内側は DB 書込のみに限定し、外部反映はコミット後の別フェーズへ出す(commit → 外部反映の順)。途中失敗で DB だけ巻き戻ると、反映済みの外部副作用が残って恒久不整合になる。
- 外部反映は冪等にし、失敗は記録して後追い再実行できる経路(outbox / 失敗レコード化)を用意する。「失敗=即終了・リトライなし」を選ぶなら、外部副作用の不整合可能性を設計として明示する。
- 排他ロックや処理中状態を取る処理は、再試行が枯渇したときに資源を能動解放する。ブローカーの配信回数などで「次に失敗したら見捨てられる」最終試行を検知し、放置せず失敗確定+ロック解放する。「次回起動時に期限切れなら解放」だけだと TTL ぶんブロックが続く。
- 一時障害か恒久障害かの分類は非対称に倒す。データ更新系では一時障害を恒久と誤判定して黙ってスキップする方が実害が大きいので、疑わしきは再試行寄りにし、明確に恒久と分かる例外だけ恒久扱いにする。
- 冪等性キーの無い登録・副作用つき処理は、付随処理の失敗を全体失敗にして利用者に再送させると重複を生む。付随処理の失敗は警告に留め、本処理は成功扱いで再送経路を絶つ。
検証
外部副作用を出した後で意図的に失敗させ、(1) DB がロールバックされても外部状態の不整合が残ること、(2) 再試行経路があれば再実行で収束し、無ければ不整合が残ること、(3) 最終試行の失敗で資源が即解放され後続が待たされないことを確認する。
根拠(synthesize 元)
- 546 非同期バルクワーカーで全件を単一トランザクションに包むと内側の非トランザクショナル副作用が不整合になる
- 559 排他ロックを持つキューワーカーはリトライ枯渇時にロックを能動解放する(配信回数を見る)
- 258 登録成功後の付随処理(ファイルアップロード等)失敗を全体失敗にせず重複登録を防ぐ