分散トランザクションでは、一つの業務が複数のデータベースにまたがると、一部だけ確定して注文と決済が異なる状態になることがあります。2相コミット(2PC)は、参加者にまずコミット準備を求め、その後、調整者が全体の決定を伝えるプロトコルです。原子性を支えますが、障害時に復旧状態の管理が必要で、作業が待機することもあります。

参加者の準備状態と全体決定を示す概念図

原子的コミットが必要な状況

分散トランザクションは、独立した資源への変更を一つの業務として整合させるときに使われます。注文に必要な更新を別々の保存先が担当し、一方だけが確定するとデータの不整合を直しにくくなる場合があります。2PCはすべての参加者が実行可能と答えた場合に全体をコミットし、準備できない参加者がいれば中止を指示します。これは決定を調整するプロトコルであり、あらゆる業務操作を元に戻せる保証ではありません。

準備段階で参加者が約束すること

調整者は各参加者に変更をコミットできるか尋ねます。参加者は準備状態と、再起動後にも処理を継続できる永続的な回復情報を記録してから、賛成または拒否を返します。Martin Fowlerのパターン説明では、必要なロックなどの資源を準備時に確保します。賛成票は最終コミットではなく、全体決定がコミットなら実行するという約束です。

全体のコミットと中止の決定

すべての参加者が賛成すれば、調整者はコミットを伝え、各参加者が変更を確定します。拒否が一つでもあれば中止を伝えます。PostgreSQLではPREPARE TRANSACTIONにより準備済み状態が元のセッションから切り離され、後でCOMMIT PREPAREDまたはROLLBACK PREPAREDで解決されます。これは外部トランザクション管理者を支えるPostgreSQL固有の実装で、SQL全般に共通する命令群ではありません。

調整者障害とブロッキングの意味

参加者が準備状態を永続化した後に調整者が使えなくなると、全体の決定がコミットか中止か分からず、参加者は整合性を保つため準備状態やロックを保持して待つことがあります。これがブロッキングで、必ず永久的なデッドロックになるという意味ではありません。調整者の耐久ログ、復旧処理、再起動後の決定再送で回復できます。ただし決定者とネットワークの両方が不確かな場合、参加者だけで別の決定を安全に作ることはできません。

専門家の視点:保持資源と復旧を設計する

PostgreSQLは準備状態を長期間残すとロックを保持し、VACUUMによる領域回収を妨げ、極端な場合はトランザクションIDのwraparoundを防ぐためサーバー停止につながり得ると注意しています。運用では未解決トランザクションを追跡し、外部管理者が速やかに終了できる設計が必要です。複数サービスに補償処理を行うSagaが代替になることもありますが、補償は原子的なロールバックと同じではなく、業務上の意味を別途設計します。

実装レビューで確認すること

参加者の準備記録が障害後に復旧できるか、調整者の決定を再送しても結果が安全か、未解決状態に警告と復旧担当者があるかを確認します。PostgreSQLでは、外部管理者が準備状態を追跡し速やかに解決できる場合に機能を有効にするよう、公式文書が注意しています。一貫性と可用性のトレードオフは業務の要件や各資源の機能に左右されます。

復旧時は参加者ごとの永続ログと調整者の決定ログを照合し、準備・コミット・中止の状態を確かめます。再起動後に運用者が推測で決めてはいけません。PostgreSQLのpg_prepared_xactsビューを外部管理者の記録と照合すれば、欠落や長期間残る処理を見つける手掛かりになります。決定を再送した場合に各資源で二重適用を防げるかも確認します。

出典