Sagaパターン(分散トランザクションの代替)
マイクロサービス間の一貫性を、ロックを長時間握らず保てます。補償トランザクションで巻き戻す発想と、2PCを避ける理由が原理から分かります。
- 1.Saga は分散トランザクションを、各サービスのローカルトランザクションの連鎖に分解し、途中で失敗したら補償トランザクションで逆順に打ち消して整合性を回復する。
- 2.コレオグラフィ型はイベント駆動で各サービスが自律的に次を呼ぶ疎結合な方式、オーケストレーション型は中央の管理者が手順と補償を一元管理する方式で、複雑な業務ほど後者が有利。
- 3.2PC のように全参加者のロックを保持し続けないため長時間処理やサービス跨ぎに向くが、原子性ではなく結果整合性しか保証できず、補償の設計はアプリケーション側の責務になる。
なぜマイクロサービスは2PCを嫌うのか
2相コミット(2PC)は、複数DBを原子的にそろえる素直な解ですが、prepare で YES を返した参加者がコーディネータの決定を待つ間ロックを保持したまま固まるブロッキング問題を抱えます。単一データセンター内の少数DBならまだ許容できても、マイクロサービスでは事情が悪化します。
- 参加者が別チームが運用する別サービスであり、コーディネータへの信頼や可用性要件が揃わない。
- 処理が注文確定→在庫引当→配送手配のように数秒〜数日かかることがあり、その間ロックを握り続けるのは現実的でない。
- サービスごとにスケール・デプロイが独立しており、1つの障害が他全部をブロックする設計は可用性目標と相容れない。
Saga パターンはこの前提を覆します。「全部を1つの原子的トランザクションにする」のを諦め、代わりに各サービスのローカルトランザクションの連鎖として処理を組み、途中で失敗したらそれまでに成功した分を逆順に打ち消すという設計です。
2PC は「実行前に全員の合意を取り、合意できたら初めてコミットする」という事前調整型です。Saga は逆に「まず各ステップをそれぞれコミットしてしまい、後で問題が起きたら意味的に打ち消す」という事後補償型です。ロックを長時間持たない代わりに、原子性そのものを諦めます。
ローカルトランザクションの連鎖と補償
Saga は T1, T2, ..., Tn という一連のローカルトランザクションで構成されます。各 Ti は単一サービス内で完結し、コミットした瞬間にその結果は他から可視になります(2PC の in-doubt 状態は存在しません)。
ある Tk が失敗したら、それより前に成功していた Tk-1, ..., T1 を、補償トランザクション Ck-1, ..., C1 で逆順に実行して打ち消します。
正常系: T1 -> T2 -> T3 -> T4 (すべて成功、Saga完了)
異常系: T1 -> T2 -> T3 -> T4(失敗)
補償: C3 -> C2 -> C1 (逆順に打ち消す)
補償トランザクションは「元に戻す」というより「業務的に打ち消す」処理です。たとえば在庫引当の補償は行を物理削除するのではなく、在庫を+1戻す という別の正のトランザクションとして実行されます。
DBのUNDOログは物理的な変更前イメージへ戻しますが、Saga の補償は既にコミットされ他者から見えている状態に対して行う新たな正のトランザクションです。決済サービスの補償は「決済を無かったことにする」のではなく「返金トランザクションを新規実行する」形になります。すでに他サービスが T1 の結果を読んでいるかもしれない前提を、補償の設計は常に意識する必要があります。
補償トランザクションには実務上、べき等性(リトライで二重に走っても結果が変わらない)、可換性の制御(実行時点で後続の別トランザクションが同じデータを触っている可能性への配慮)、リトライ可能性(補償自体の失敗に備え成功するまで諦めず再試行する forward recovery)が求められます。
コレオグラフィ型: 自律分散でつなぐ
コレオグラフィ(choreography)型は中央の管理者を置きません。各サービスがイベントを発行し、他サービスがそれを購読して自分の番のローカルトランザクションを実行、また次のイベントを発行する、というリレー形式です。
注文サービス --(注文作成イベント)--> 在庫サービス
在庫サービス --(引当完了イベント)--> 配送サービス
配送サービス --(手配失敗イベント)--> 在庫サービス(補償: 引当解除)
在庫サービス --(補償完了イベント)--> 注文サービス(補償: 注文キャンセル)
各サービスは「自分の前後に何がいるか」だけ知っていればよく、サービス間の結合度が低いのが利点です。一方で欠点もはっきりしています。
参加サービスが増えるほど、「誰が今どのステップまで進んだか」という全体のフロー図がコード上のどこにも存在しない状態になります。新しいステップを追加するたびに複数サービスのイベント購読ロジックを改修する必要があり、循環的なイベント依存やイベントの取りこぼしをテストで検出しにくくなります。
オーケストレーション型: 中央集権でつなぐ
オーケストレーション(orchestration)型は、Saga 専用のオーケストレータ(司令塔サービスやワークフローエンジン)が手順を一元管理します。オーケストレータが各サービスへコマンドを送り、応答を見て次のステップか補償かを判断します。
Orchestrator -> 在庫サービス: 引当コマンド
Orchestrator <- 在庫サービス: 成功
Orchestrator -> 配送サービス: 手配コマンド
Orchestrator <- 配送サービス: 失敗
Orchestrator -> 在庫サービス: 補償コマンド(引当解除)
Orchestrator -> 注文サービス: 補償コマンド(注文キャンセル)
手順・補償の対応関係が1か所(オーケストレータの状態機械)にまとまるため、フローの可視性とテスト容易性が高まります。代償はオーケストレータ自体が新たな結合点になることと、そのサービスの可用性・実装負荷が増すことです。
| 観点 | コレオグラフィ型 | オーケストレーション型 |
|---|---|---|
| 制御の所在 | 各サービスに分散(イベント駆動) | 中央のオーケストレータに集中 |
| 結合度 | 低い(サービス同士は直接知らない) | オーケストレータと各サービスが結合 |
| フローの可視性 | 低い(コードを辿らないと全体像が見えない) | 高い(状態機械やワークフロー定義に集約) |
| 向くケース | ステップ数が少なく単純な連鎖 | ステップ数が多い・分岐や補償ロジックが複雑 |
| 新規ステップ追加 | 複数サービスの改修が要る | オーケストレータの定義変更で完結しやすい |
結果整合性の受容とISOLATIONの喪失
Saga で最も見落とされがちなのが、ACID の I(分離性)を実質的に失う点です。Sagaが受け入れるのは一貫性モデルの階層でいう最も弱い部類の結果整合性であり、途中経過を隠す強い保証は最初から放棄されています。T1 がコミットした瞬間から補償 C1 が完了するまでの間、他のトランザクションはまだ確定していないSagaの中間状態を読み取れてしまいます。これは典型的な分離性違反であり、Sagaは原子性を結果整合性で置き換える代わりに、この中間可視性という代償を払います。
注文Sagaが 在庫引当済み・決済未完了 の状態にある間、別の顧客がその在庫の残数を参照すると、最終的にキャンセルされるかもしれない引当分を含んだ数字を見てしまいます。これを防ぐには、アプリケーション側で意味論的ロック(semantic lock、例えば「引当中」フラグを立てて他の予約を一時的に弾く)や、確定するまで公開しない設計が必要です。DBのMVCCのようにエンジンが自動で面倒を見てくれるわけではありません。
この中間可視性を抑える代表的な対策が、意味論的ロック(「Saga処理中」フラグで競合更新を業務ロジックでブロック)、可換な更新への設計(加減算のように順序に依存しない更新にし途中状態が見えても数値が破綻しないようにする)、べき等なイベント配送(少なくとも1回配送やメッセージ重複を前提に各ステップと補償を実装する)です。
2PCとの対比で押さえる選択基準
| 観点 | 2PC | Saga |
|---|---|---|
| 原子性 | 保証する(全部成功か全部取消) | 保証しない(結果整合性に緩和) |
| ロック保持時間 | コミット確定まで全参加者が保持 | 各ローカルTxの完了時点で解放 |
| 障害時の挙動 | コーディネータ障害でブロッキング | 補償トランザクションで前進的に収拾 |
| 分離性 | 他から中間状態は不可視 | コミット済みの中間状態が他から可視になりうる |
| 失敗時の巻き戻し | コミット前にabortで自然に消える | アプリが補償ロジックを明示的に実装する必要 |
| 向く処理時間 | 短時間(ミリ秒〜秒) | 長時間(秒〜日)でも成立 |
2PCは「原子性を犠牲にしない代わりにブロッキングと可用性を犠牲にする」設計であり、Sagaは「ブロッキングを避ける代わりに原子性と分離性をアプリケーションの責務に押し戻す」設計です。マイクロサービスで2PCが避けられがちなのは、サービスの独立デプロイ・スケール性という前提そのものが、参加者を長時間ブロックする方式と根本的に相性が悪いからです。
「Sagaが2PCの代替になるのは、ロックを長時間保持せず各ローカルTxを即座にコミットし、失敗時は補償トランザクションで打ち消すから。代償は原子性でなく結果整合性しか保証できず、コミット済みの中間状態が他者から見えてしまう点」――この因果とコレオグラフィ/オーケストレーションの使い分けをセットで言えれば要点を押さえています。
Saga は決して「2PCの簡易版」ではなく、保証するものの種類を意図的に変える設計判断です。ステップ数が少なく単純ならコレオグラフィ、業務フローが複雑で可視性やテスト容易性を重視するならオーケストレーション。どちらを選んでも、補償トランザクションのべき等性設計と中間状態の可視性対策は避けて通れません。
データベース Article
Sagaパターン(分散トランザクションの代替)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Saga
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
コレオグラフィ型はイベント駆動で各サービスが自律的に次を呼ぶ疎結合な方式、オーケストレーション型は中央の管理者が手順と補償を一元管理する方式で、複雑な業務ほど後者が有利。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「Saga / 分散トランザクション」に近いか確認する。
- 強みである「Saga は分散トランザクションを、各サービスのローカルトランザクションの連鎖に分解し、途中で失敗したら補償トランザクションで逆順に打ち消して整合性を回復する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。