Sagaパターン

英語名 Saga Pattern
読み方 サガ パターン
難易度
所要時間 設計に1〜2週間
提唱者 ヘクター・ガルシア=モリーナ、ケネス・セーラム(1987年)
目次

ひとことで言うと
#

分散システムで複数サービスにまたがる処理を、ローカルトランザクションの連鎖+失敗時の補償アクションで実現するパターン。2フェーズコミットのような重い仕組みを使わず、結果整合性を保つ。

押さえておきたい用語
#

押さえておきたい用語
補償アクション
あるステップが失敗したとき、それ以前に成功したステップを元に戻す逆の処理。Undoではなく「取り消すための新しいトランザクション」を実行する。
オーケストレーション
中央のコーディネーター(オーケストレーター)各サービスのステップを順に呼び出す方式。フロー全体の制御が明確でデバッグしやすい。
コレオグラフィ
各サービスがイベントを発行し、次のサービスがそれを購読して自律的に処理する方式。疎結合だが全体の流れが追いにくい。
結果整合性
すべてのステップが完了した時点で最終的に一貫性が保証される緩やかな整合性モデル。即時整合性は犠牲にする代わりにスケーラビリティを得る。
セマンティックロック
サガの実行中に対象データが「処理中」であることを示すフラグやステータス。他の処理が同じデータを変更するのを防ぐ。

Sagaパターンの全体像
#

補償アクションで整合性を保つ分散トランザクション
T1: 注文作成注文サービスT2: 在庫確保在庫サービスT3: 決済 ✗ 失敗決済サービス補償開始C2: 在庫戻し補償アクションC1: 注文取消補償アクション失敗時は逆順に補償アクションを実行して整合性を回復
Sagaパターンの設計プロセス
1
プロセス分解
ビジネスフローをステップに分割
2
補償定義
各ステップの逆処理を設計
3
方式選択
オーケストレーション or コレオグラフィ
4
異常系実装
リトライ・補償・状態永続化

こんな悩みに効く
#

  • マイクロサービスに分割したら、複数サービスにまたがるトランザクションが管理できなくなった
  • 分散トランザクション(2PC)は重すぎて採用したくない
  • 途中で失敗した場合の「巻き戻し」をどう実装すればいいかわからない

基本の使い方
#

ステップ1: サガの対象となるビジネスプロセスを特定する

複数サービスにまたがる一連の処理を洗い出す。

  • 例:「注文 → 在庫確保 → 決済 → 配送手配」
  • 各ステップがどのサービスで実行されるかを明確にする
  • ステップ間の依存関係と順序を整理する

ポイント: 1つのサガ=1つのビジネストランザクション。

ステップ2: 各ステップの補償アクションを定義する

各ローカルトランザクションに対して、失敗時に元に戻すための補償処理を設計する。

  • 在庫確保 → 補償: 在庫を戻す
  • 決済実行 → 補償: 返金処理
  • 補償アクションは冪等(何度実行しても同じ結果)にする

ポイント: 補償アクションは「Undo」ではなく「逆の処理を新たに実行する」という考え方。

ステップ3: オーケストレーションまたはコレオグラフィを選択する

サガの実行方式を決める。

  • オーケストレーション: 中央のコーディネーターが各ステップを順に呼び出す。制御が明確でデバッグしやすい
  • コレオグラフィ: 各サービスがイベントを発行し、次のサービスがそれを受け取って処理する。疎結合だが流れが追いにくい

ポイント: 複雑なサガにはオーケストレーション、シンプルなものにはコレオグラフィが向く。

ステップ4: 失敗検知とリトライ・補償のフローを実装する

異常系のハンドリングを確実に実装する。

  • タイムアウト、リトライ、デッドレターキューを設計する
  • 補償アクションの実行順序(逆順)を保証する
  • サガの状態を永続化し、障害からの復旧を可能にする

ポイント: 正常系より異常系の設計が本番。ここに時間をかける。

具体例
#

例1:ECサイトの注文フローをSagaで実装する

ビジネスプロセス: 注文作成 → 在庫確保 → 決済 → 配送手配(4サービス)

オーケストレーション方式で実装:

  • OrderSagaOrchestratorが各サービスを順次呼び出す
  • 各ステップの状態をPostgreSQLに永続化

障害シナリオ: 決済サービスがタイムアウト

  1. オーケストレーターが3回リトライ(指数バックオフ)
  2. 3回失敗 → 補償フロー開始
  3. C2: 在庫サービスに「在庫戻し」リクエスト → 成功
  4. C1: 注文を「キャンセル」ステータスに更新 → 成功
  5. ユーザーに「決済に失敗しました」と通知

結果: 月間15万件の注文を処理し、障害発生率0.3%の状況でもデータ不整合ゼロを達成。補償アクションの平均完了時間は2.1秒。

例2:旅行予約サービスで3社連携のSagaを運用する

ビジネスプロセス: フライト予約 → ホテル予約 → レンタカー予約

コレオグラフィ方式で実装:

  • 各サービスがKafkaにイベントを発行し、次のサービスが購読
  • FlightBookedHotelBookedCarBookedの連鎖

障害シナリオ: レンタカー予約が在庫切れで失敗

  1. レンタカーサービスがCarBookingFailedイベントを発行
  2. ホテルサービスが購読し、ホテル予約をキャンセル → HotelCancelled発行
  3. フライトサービスが購読し、フライト予約をキャンセル

結果: 1日3,200件の予約処理で、全キャンセルの整合性を保証。ただし、サービスが5つに増えた時点でイベントの流れが追いにくくなり、オーケストレーション方式に移行した。

例3:金融サービスの送金処理でSagaの状態永続化を徹底する

ビジネスプロセス: 残高チェック → 送金元引落 → 送金先入金 → 通知

課題: オーケストレーターがステップ2完了後にクラッシュ。再起動後に「どこまで進んだか」がわからず、二重引落のリスク。

対策:

  • サガの各ステップをDBに記録(saga_id, step, status, created_at)
  • オーケストレーター再起動時に未完了のサガをDBから復元
  • 各ステップに冪等性キーを付与し、リトライ時の二重実行を防止

結果: 月間8万件の送金を処理。オーケストレーターの計画的再起動を含め、二重引落・二重入金は累計ゼロ。サガの状態テーブルが障害調査のログとしても機能し、原因特定時間が平均45分→8分に短縮。

やりがちな失敗パターン
#

  1. 補償アクションを冪等にしない — ネットワーク障害でリトライが発生した際、二重キャンセルや二重返金が起きる。すべての補償アクションは冪等性キーで二重実行を防ぐこと
  2. サガの状態を永続化しない — オーケストレーターがクラッシュした場合、どこまで進んだかわからなくなる。サガの進行状態は必ずDBに保存する
  3. サガを細かくしすぎる — 2〜3ステップで済む処理を10ステップのサガにすると、補償アクションの組み合わせ爆発で管理不能になる。本当に分散が必要な単位で設計する
  4. 正常系だけテストして安心する — 分散システムの本番は異常系。ネットワーク分断、タイムアウト、部分障害のシナリオをカオスエンジニアリングで網羅的にテストすること

まとめ
#

Sagaパターンは分散システムにおけるトランザクション管理の定番手法。「各サービスのローカルトランザクション+補償アクション」という仕組みで、結果整合性を実現する。異常系の設計が肝なので、正常系が動いたら終わりではなく、失敗シナリオを徹底的にテストしよう。