3PC(Three-Phase Commit)を徹底解説:分散トランザクションの安全性・限界・実装ポイント

3PCとは何か — 背景と目的

3PC(Three-Phase Commit、三相コミット)は、分散トランザクションにおける合意(commit/abort)を安全に行うためのプロトコルの一つです。従来の2PC(Two-Phase Commit)がブロッキング(コーディネータやネットワークの障害で参加者が永久に待たされる)問題を抱えるため、それを緩和する目的で設計されました。3PCは追加の“pre-commit(準備完了)”相を導入することで、障害発生時に参加者が安全に自己救済できるようにし、非ブロッキング性(特定の同期条件下で)を高めます。

3PCの主要フェーズ(プロトコルの流れ)

  • フェーズ0:開始(Start) — コーディネータがトランザクションを開始し、参加者に処理実行を依頼します。
  • フェーズ1:CanCommit(投票フェーズ) — コーディネータが各参加者に「コミット可能か?」を問い合わせ、参加者はローカルで整合性チェックを行い「Yes/No」で応答します。参加者はこの段階でローカルの準備(ログへの書き込みやロック保持)を行います。
  • フェーズ2:PreCommit(準備完了フェーズ) — コーディネータが全員から「Yes」を受け取った後、参加者に「pre-commit(事前コミット)」を通知します。参加者はこの通知を受けて、最終コミット前の安定状態(準備済み状態)をログに書き込み、ACKを返します。この状態が3PCの核であり、決定不可能状態を回避するために重要です。
  • フェーズ3:DoCommit(最終コミット/中止フェーズ) — コーディネータは全員のACKを受けたら最終的な「commit」を通知し、参加者はそれを実行します。もし何らかの理由でabortが決定した場合は、その旨を通知してロールバックします。

なぜPreCommitが重要か(安全性の向上)

2PCでは「準備(prepare)」と「コミット(commit)」の2段階しかないため、コーディネータが障害でダウンすると参加者は「準備済み」状態から抜け出せず、外部からの指示を無限に待つ(ブロッキング)可能性があります。3PCのpre-commitは、参加者がpre-commit状態に入った時点で「他の参加者も最終決定に到達する余地がある」ことを保証する状態を作り出します。これにより、適切なタイムアウトや同期仮定のもとでは、参加者が単独で安全に最終決定(commitまたはabort)へ進める道が開きます。

前提条件と制約(なぜ万能ではないか)

  • 部分同期モデルを仮定 — 3PCの非ブロッキング性はネットワーク遅延が無限にならない(つまり、ある程度の同期性が担保される)ことを前提としています。完全非同期環境ではFLPの不可能性定理により、致命的な障害下での安全かつ確実な合意は保証できません。
  • 障害モデルはクラッシュ止まり(fail-stop) — 3PCは参加者やコーディネータがクラッシュして再起動するモデルを扱いますが、ビザンチン(悪意ある振る舞い)に対しては対処できません。
  • タイムアウトの調整が必須 — pre-commitから自己判断に移るためのタイムアウト設定は運用環境に強く依存し、不適切だと誤った自己判断(誤コミットや不必要なアボート)を誘発します。

障害シナリオの例と回復方法

主な障害シナリオを挙げ、それぞれの振る舞いを概説します。

  • コーディネータが投票応答前にクラッシュ — 参加者はタイムアウト後にabortするか、別のコーディネータへ再試行を要求します。3PC単体ではコーディネータ選出機構を含まないため、別途リーダ-electionの仕組みが必要です。
  • コーディネータがpre-commit後にクラッシュ — 参加者はpre-commit状態にあるため、タイムアウト後に最終決定を自律的に行える(commitを安全に行える)条件が満たされていれば進行できます。これが2PCとの差です。
  • ネットワーク分断(パーティショニング) — 分断状況下では参加者間で相互に状態が見えなくなり、pre-commitの安全性前提が崩れるため、誤った決定を下すリスクがあります。部分同期モデルの下でも、パーティション検出と復旧が重要です。

2PCとの比較(メリットとデメリット)

  • メリット
    • 2PCよりもブロッキングを軽減できる(条件付きで非ブロッキング)
    • 参加者がより多くの状態を共有し、障害時に自己判断しやすい
  • デメリット
    • メッセージ数とログ記録が増え、オーバーヘッドが大きい
    • 同期やタイムアウトなどの運用パラメータに敏感で、誤設定で誤動作しやすい
    • ビザンチン障害や長期的なネットワーク分断を扱えない

実運用での現実的な選択肢 — いつ3PCを使うか

多くの現代の分散システムは、2PCや3PCだけでなく、合意問題を解くためにPaxosやRaftのようなコンセンサスアルゴリズムを採用します。これらは状態機械レプリケーションを前提にしており、コーディネータの単一障害点を減らす設計がなされています。したがって、3PCは理論的に魅力的ですが、実運用では以下のケースで検討されます。

  • 比較的小規模でクラッシュのみを想定した閉域ネットワーク
  • 2PCのブロッキングが許容できない、かつ完全なコンセンサスメカニズムを導入するコストが高い場合
  • レガシーな分散DBやトランザクションミドルウェアの拡張として

実装上の注意点と最適化

  • ログの永続化 — 各フェーズの遷移をローカルログに永続化すること。障害からの回復時に状態を再構築するために不可欠です。
  • タイムアウト設計 — ネットワーク遅延や負荷波動を考慮したダイナミックなタイムアウトや、エクスポネンシャルバックオフを組み合わせると安定性が増します。
  • コーディネータ選出機構 — 3PC自体はリーダ選出を扱わないため、ZooKeeperやRaft等を用いてフォールトトレラントなコーディネータ管理を行うのが実用的です。
  • モニタリングと可観測性 — 各参加者の状態遷移やタイミングを可視化し、障害パターンに基づくチューニングを行います。

設計上のリスクと代替手段

3PCは万能ではありません。大規模分散やマルチデータセンタ環境、ビザンチン障害が懸念される場面では、PaxosやRaftによるレプリカ合意や、分散トランザクションを避けるための設計(イベントソーシングや補償トランザクション、最終的整合性)を検討するべきです。また、分散トランザクションのコストが性能上のボトルネックになるため、ドメイン駆動設計でトランザクション境界を小さくするなどのアーキテクチャ的判断も重要です。

まとめ

3PCは2PCの欠点を補うために設計されたプロトコルで、pre-commitという中間状態を導入することで、特定の同期条件下では非ブロッキング性と安全性を両立させようとします。しかし、その有効性はネットワーク同期性や障害モデル(クラッシュのみ)といった前提に強く依存し、実運用ではオーバーヘッドや運用の難しさを伴います。現代の大規模分散システムでは、Paxos/Raft等のコンセンサスアルゴリズムや最終整合性設計がより採用される傾向にありますが、限定的な環境では3PCは有用な選択肢になり得ます。

参考文献