ひとことで言うと#
分散システムで複数サービスにまたがる処理を、ローカルトランザクションの連鎖+失敗時の補償アクションで実現するパターン。2フェーズコミットのような重い仕組みを使わず、結果整合性を保つ。
押さえておきたい用語#
- 補償アクション
- あるステップが失敗したとき、それ以前に成功したステップを元に戻す逆の処理。Undoではなく「取り消すための新しいトランザクション」を実行する。
- オーケストレーション
- 中央のコーディネーター(オーケストレーター)が各サービスのステップを順に呼び出す方式。フロー全体の制御が明確でデバッグしやすい。
- コレオグラフィ
- 各サービスがイベントを発行し、次のサービスがそれを購読して自律的に処理する方式。疎結合だが全体の流れが追いにくい。
- 結果整合性
- すべてのステップが完了した時点で最終的に一貫性が保証される緩やかな整合性モデル。即時整合性は犠牲にする代わりにスケーラビリティを得る。
- セマンティックロック
- サガの実行中に対象データが「処理中」であることを示すフラグやステータス。他の処理が同じデータを変更するのを防ぐ。
Sagaパターンの全体像#
こんな悩みに効く#
- マイクロサービスに分割したら、複数サービスにまたがるトランザクションが管理できなくなった
- 分散トランザクション(2PC)は重すぎて採用したくない
- 途中で失敗した場合の「巻き戻し」をどう実装すればいいかわからない
基本の使い方#
複数サービスにまたがる一連の処理を洗い出す。
- 例:「注文 → 在庫確保 → 決済 → 配送手配」
- 各ステップがどのサービスで実行されるかを明確にする
- ステップ間の依存関係と順序を整理する
ポイント: 1つのサガ=1つのビジネストランザクション。
各ローカルトランザクションに対して、失敗時に元に戻すための補償処理を設計する。
- 在庫確保 → 補償: 在庫を戻す
- 決済実行 → 補償: 返金処理
- 補償アクションは冪等(何度実行しても同じ結果)にする
ポイント: 補償アクションは「Undo」ではなく「逆の処理を新たに実行する」という考え方。
サガの実行方式を決める。
- オーケストレーション: 中央のコーディネーターが各ステップを順に呼び出す。制御が明確でデバッグしやすい
- コレオグラフィ: 各サービスがイベントを発行し、次のサービスがそれを受け取って処理する。疎結合だが流れが追いにくい
ポイント: 複雑なサガにはオーケストレーション、シンプルなものにはコレオグラフィが向く。
異常系のハンドリングを確実に実装する。
- タイムアウト、リトライ、デッドレターキューを設計する
- 補償アクションの実行順序(逆順)を保証する
- サガの状態を永続化し、障害からの復旧を可能にする
ポイント: 正常系より異常系の設計が本番。ここに時間をかける。
具体例#
ビジネスプロセス: 注文作成 → 在庫確保 → 決済 → 配送手配(4サービス)
オーケストレーション方式で実装:
OrderSagaOrchestratorが各サービスを順次呼び出す- 各ステップの状態をPostgreSQLに永続化
障害シナリオ: 決済サービスがタイムアウト
- オーケストレーターが3回リトライ(指数バックオフ)
- 3回失敗 → 補償フロー開始
- C2: 在庫サービスに「在庫戻し」リクエスト → 成功
- C1: 注文を「キャンセル」ステータスに更新 → 成功
- ユーザーに「決済に失敗しました」と通知
結果: 月間15万件の注文を処理し、障害発生率0.3%の状況でもデータ不整合ゼロを達成。補償アクションの平均完了時間は2.1秒。
ビジネスプロセス: フライト予約 → ホテル予約 → レンタカー予約
コレオグラフィ方式で実装:
- 各サービスがKafkaにイベントを発行し、次のサービスが購読
FlightBooked→HotelBooked→CarBookedの連鎖
障害シナリオ: レンタカー予約が在庫切れで失敗
- レンタカーサービスが
CarBookingFailedイベントを発行 - ホテルサービスが購読し、ホテル予約をキャンセル →
HotelCancelled発行 - フライトサービスが購読し、フライト予約をキャンセル
結果: 1日3,200件の予約処理で、全キャンセルの整合性を保証。ただし、サービスが5つに増えた時点でイベントの流れが追いにくくなり、オーケストレーション方式に移行した。
ビジネスプロセス: 残高チェック → 送金元引落 → 送金先入金 → 通知
課題: オーケストレーターがステップ2完了後にクラッシュ。再起動後に「どこまで進んだか」がわからず、二重引落のリスク。
対策:
- サガの各ステップをDBに記録(saga_id, step, status, created_at)
- オーケストレーター再起動時に未完了のサガをDBから復元
- 各ステップに冪等性キーを付与し、リトライ時の二重実行を防止
結果: 月間8万件の送金を処理。オーケストレーターの計画的再起動を含め、二重引落・二重入金は累計ゼロ。サガの状態テーブルが障害調査のログとしても機能し、原因特定時間が平均45分→8分に短縮。
やりがちな失敗パターン#
- 補償アクションを冪等にしない — ネットワーク障害でリトライが発生した際、二重キャンセルや二重返金が起きる。すべての補償アクションは冪等性キーで二重実行を防ぐこと
- サガの状態を永続化しない — オーケストレーターがクラッシュした場合、どこまで進んだかわからなくなる。サガの進行状態は必ずDBに保存する
- サガを細かくしすぎる — 2〜3ステップで済む処理を10ステップのサガにすると、補償アクションの組み合わせ爆発で管理不能になる。本当に分散が必要な単位で設計する
- 正常系だけテストして安心する — 分散システムの本番は異常系。ネットワーク分断、タイムアウト、部分障害のシナリオをカオスエンジニアリングで網羅的にテストすること
まとめ#
Sagaパターンは分散システムにおけるトランザクション管理の定番手法。「各サービスのローカルトランザクション+補償アクション」という仕組みで、結果整合性を実現する。異常系の設計が肝なので、正常系が動いたら終わりではなく、失敗シナリオを徹底的にテストしよう。