1. 2相コミット — 「準備」と「実行」を分ける
参加者が複数いるとき、いきなり「コミットせよ」と命じるのは危険です。片方だけ成功して片方が失敗したら、送金額が消えたり増えたりします。2PC はコーディネータ (調停役)を立て、まずフェーズ1(prepare) で全員に「コミットできる状態を作って約束せよ」と聞き、全員が YES と答えたときだけフェーズ2(commit) で実行させます。1人でも NO なら全員 abort。
ステップ実行で、メッセージと各DBの状態(ロック・undo/redoログ・残高)の変化を追ってみてください。
2PC の正常系 — 銀行A→Bへ10万円送金をステップ実行
▶ 次のステップ
最初から
⏸ 一時停止
オレンジ=prepare、水色=YES投票、緑=commit、白=ACK。参加者は YES と答えた瞬間から行をロックし続け 、コーディネータの決定が届くまで解放できません。コーディネータが「決定=COMMIT」を自分のログに書いた瞬間(ステップ4)が不可逆点 です。
POINT — 2PC の本質は「引き返す権利の放棄」
参加者が YES と投票することは「以後、自分の判断で abort する権利を捨てる」という約束。だから YES の前に undo/redo ログをディスクに書き、自分が故障しても再起動後にどちらへも倒せる状態 を作っておく。全体の運命を握るのはコーディネータのログに書かれた決定ただ1つ。
2PC のコスト: メッセージ 3N 本 + 強制ディスク書込 2N + 1 回
N = 参加者数(ACK まで数えると 4N 本)。しかも prepare 〜 commit の間、関係する行はロックされ続ける
2. 2PC の弱点 — ブロッキング
問題はコーディネータが最悪のタイミングで落ちたとき です。参加者が YES と投票した後にコーディネータが沈黙すると、参加者は commit も abort も自分では選べません(他の誰かが既に commit を受け取っているかもしれないから)。ロックを握ったまま、コーディネータの復旧をひたすら待つ しかない — これをブロッキング と呼びます。故障のタイミングをボタンで切り替えて、危険な瞬間がどこかを確かめてください。
故障タイミング実験 — どこで落ちると詰むか
① prepare 前に故障
② 全員 YES の直後に故障
③ commit を片方に送って故障
⏸ 一時停止
①なら参加者はタイムアウトで安全に自主 abort できます。危険なのは②:ロック保持時間がどこまでも伸び、同じ行を触る他のトランザクションが後ろに積み上がっていきます 。③では A だけ確定済みという非対称な状態になり、B は A に問い合わせれば助かる(協調的終了)ものの、A も落ちていれば結局待つだけです。
注意 — 「タイムアウトで諦めれば?」は通用しない
YES 投票後の参加者が勝手に abort すると、他の参加者は commit を受け取っているかもしれず、原子性そのものが壊れる 。3相コミット(3PC)はこの穴を塞ごうとした改良だが、ネットワーク遅延に上限がない現実の非同期ネットワーク+分断の下では安全にできない。ブロッキングは 2PC の実装バグではなく、原理的な代償 。
3. Saga — ロックせず、失敗したら「補償」で打ち消す
マイクロサービスの世界では、注文フローが「在庫サービス → 決済サービス → 配送サービス」と複数のDBをまたぎます。ここで 2PC を使うとサービス同士がロックで縛り合うため、代わりに Saga がよく使われます。全体をローカルトランザクションの連鎖 に分割し、各ステップは終わった端から即コミット 。途中で失敗したら、成功済みのステップを補償トランザクション (返金・予約取消など)で逆順に打ち消して いきます。
Saga の実行 — 失敗地点を変えて補償の走り方を見る
緑=ローカル commit 済み、赤=失敗、オレンジ=補償(逆順)。ロックを持ち越さないので他の処理を待たせません。ただし T1 が commit してから補償が終わるまでの間、「在庫は減ったのに注文は存在しない」という中間状態が外から見えます — Saga は原子性を「結果的に」に緩めた設計です。
T1 → T2 → … → Tk が失敗 ⇒ Ck−1 → … → C2 → C1
Ci は Ti の補償トランザクション(意味的な打ち消し)。逆順に実行して「なかったこと」に近づける
注意 — 補償は「巻き戻し」ではない
「Saga を使えば 2PC と同じ原子性が手に入る」は誤解。補償は undo ではなく新しい打ち消し操作 で、返金しても請求メールは送信済みかもしれない。分離性もないので中間状態は他者から見える。だから補償できない操作(通知送信・出荷など)はできるだけ連鎖の最後に置く のが設計の定石。
4. 使い分け — 強い原子性か、ロックしない自由か
2PC と Saga は競合ではなく、守るものが違う道具 です。境界をまたぐ時間が短く、参加者が同じ管理下にあり、強い整合性が絶対条件なら 2PC。サービスが独立していて処理が長く、可用性を優先するなら Saga。バーを切り替えて得意分野を見比べてください。
2PC vs Saga — 性質の比較と使い分け指針
視点を切り替える(両方 → 2PC → Saga)
青=2PC、緑=Saga。2PC は「自動で全か無か」を保証する代わりにロックとブロッキングを抱え、Saga はロックなしで走る代わりに補償ロジックの設計・実装を人間が背負います。どちらを選んでも「タダの原子性」は存在しません。
一歩先へ — 現代のシステムはどう解いているか
最大の急所が「コーディネータが単一障害点」なら、そこを合意アルゴリズムで冗長化 すればよい — Google Spanner は Paxos グループの上で 2PC を走らせ、コーディネータ故障をグループ内フェイルオーバーで吸収する。アプリ層では、Saga の実務形としてTCC(Try-Confirm-Cancel) や、DB書込とメッセージ送信を原子的に見せるOutbox パターン が定番。「どこまでを1つのDBに収め、どこから補償で運ぶか」が現代の設計論点。
5. まとめ
2PC :prepare で全員の約束を取り、コーディネータのログが唯一の決定。強い原子性の代償がロック長期化とブロッキング 。
ブロッキング :YES 投票後にコーディネータが沈黙すると、参加者はロックを握って待つしかない。原理的な代償であり実装では消せない。
Saga :ローカルTxの連鎖+失敗時は補償を逆順実行。ロックを持ち越さないが、原子性・分離性は弱まり中間状態が見える。
使い分け :短時間・同一管理下・強整合なら 2PC 系、長時間・サービス独立・可用性優先なら Saga 系。補償不能な操作は連鎖の最後へ。