【実務・中級編】 分散トランザクションプロトコル – Cloud Spanner

Cloud Spannerの分散トランザクション解体新書:2PCとPaxosが織りなす「限界なき整合性」の舞台裏

テックリードの私だ。今回のコードレビューで、また「Spannerなら何をやってもトランザクションが安全にスケールする」というフワッとした理解で書かれたクエリとスキーマデザインを見かけた。

気持ちは分かる。TrueTime APIの魔法によって、あたかも単一の巨大なリレーショナルデータベースを使っているかのようにグローバルなACIDトランザクションが書けてしまうからだ。だが、アーキテクトとしてシステムを極限までスケールさせたいなら、「その裏側で何が起きているのか」を解像度高く理解していなければならない。

今回は、Cloud Spannerのコアエンジンである「分散トランザクションプロトコル(2PC × Paxos)」の深淵に迫る。コーディネーターと参加者のハンドシェイク、そしてレイテンシの罠をどう回避するか。実務の現場で生きる設計パターンを叩き込む。

—

1. 基礎的迷宮の突破:なぜSpannerの2PCは「普通の2PC」と違うのか?

一般的に、分散トランザクションにおける2相コミット(2PC)は「悪者」扱いされる。なぜか?
1. 単一障害点(SPOF): コーディネーターが死んだらロックが解放されず系全体がデッドロックする。
2. スループットの崩壊: ネットワーク遅延とロック保持時間の長さにより、スケールアウトさせようとすると逆に性能が落ちる。

しかし、Spannerの設計はここが狂気的なまでに洗練されている。Spannerの2PCは、単なるノード間通信ではなく、「Paxosグループ間の合意形成」として実装されているのだ。

階層構造の理解

  • Paxos Group: 各シャード(データ範囲)は、複数のレプリカ(Paxosグループ)によって冗長化されている。
  • Leader: 各Paxosグループにはリーダーが存在し、書き込みやトランザクションの調整役を担う。

つまり、Spannerにおける2PCの「参加者(Participant)」は個別のマシンではなく、「Paxosグループのリーダー」である。さらに言えば、2PCの決定事項自体がPaxosによってレプリケートされる。仮にコーディネーターのリーダーが死んでも、別のレプリカがその状態を引き継ぎ、トランザクションを安全に完遂(あるいはロールバック)させる。ここが、自前で2PCを実装したレガシーシステムとSpannerの決定的な違いだ。

—

2. 実行フローの全貌:コーディネーターと参加者のダンス

読み手が最も知りたいのは、「1つのトランザクションがコミットされるまでに、内部で何が起きているのか」という時系列の動きだろう。

以下のシナリオを考えてみてほしい。東京とオレゴンのデータにまたがる書き込みを含む、クロスシャード・トランザクションの発行だ。

— ユーザーAの残高を引き落とし、ユーザーB(別シャードに存在)に入金する
BEGIN TRANSACTION;
UPDATE Accounts SET Balance = Balance – 100 WHERE UserID = ‘A’;
UPDATE Accounts SET Balance = Balance + 100 WHERE UserID = ‘B’;
COMMIT;

この瞬間、Spannerの内部では以下のドラマが展開されている。

[クライアント]
│
▼ (1. 読み取りとロック獲得 / Write意図のバッファリング)
[コーディネーター・リーダー (例: ユーザーAのグループ)]
│
├─ (2. Prepare Phase) ────► [参加者リーダー1 (ユーザーBのグループ)] (Vote: Yes/No)
├─ (Prepare Phase) ────► [自身でPrepare確定]
│
▼ (3. TrueTimeによるコミットタイムスタンプ $s$ の確定)
[コーディネーター]
│
├─ (4. Commit Phase) ────► [参加者リーダー1] (“Commit at time s”)
│
▼ (5. クライアントへ成功を返却)

ステップごとの深掘り

1. フェーズ0:ローカルのバッファリングとロック獲得
クライアントは読み取りを行い、書き込みの意図をクライアント側のメモリ(または直近のリーダー)にバッファリングする。トランザクション開始時、対象となるPaxosグループのリーダーに対して排他ロック(Write Lock)が取得される。
2. フェーズ1:Prepare(準備段階)

  • コーディネーターに選ばれたPaxosグループのリーダーが、他の参加者グループのリーダー群に対して `Prepare` メッセージを投げる。
  • 各参加者は、トランザクションをコミットできる状態か確認し、ログに記録(Paxosで合意)した上で、コーディネーターに `Prepared`(投票結果)を返す。

3. フェーズ2:TrueTimeとコミットの決定

  • すべての参加者から `Prepared` が返ると、コーディネーターは TrueTime API を使ってコミット・タイムスタンプ $s$ を決定する。
  • この時、分散環境の不確実性($\epsilon$: 誤差の幅)を考慮し、「絶対に現実時間が $s$ を過ぎている」と保証できるまでコーディネーターはコミットをわずかにウェイトする(Commit Wait)。これがSpannerのレイテンシの正体だ。

4. フェーズ3:Commit(確定段階)

  • コーディネーターは、タイムスタンプ $s$ と共に `Commit` メッセージを全参加者に送る。
  • 各参加者は $s$ のタイミングでデータを適用し、ロックを解放する。

—

3. 実務で直面するパフォーマンスの罠と設計パターン

アーキテクトとして、このメカニズムから何を導き出すべきか。コードレビューで指摘すべきポイントを挙げる。

罠1:暗黙のクロスシャード・トランザクション(ホットスポットとレイテンシ増大)

テーブル設計が適当だと、1つのトランザクションが意図せず数十のPaxosグループを巻き込む(クロスシャード・トランザクションの爆発)。これにより、ネットワークホップ数が増え、2PCの合意形成にかかる時間が線形に増加する。

【アンチパターン】連番IDやランダムUUIDを主キーにしたテーブル

— 最悪の例:ランダムUUIDを使うと、行がグローバルに分散し、
— ちょっとしたバッチ処理でも無数のPaxosグループを2PCで巻き込むことになる
CREATE TABLE Orders (
OrderID STRING(36) NOT NULL,
CustomerID STRING(36) NOT NULL,
TotalAmount INT64,
) PRIMARY KEY(OrderID);

【推奨パターン】Interleave(インターリーブ)によるコロケーション設計
親テーブルと子テーブルを物理的に同じスプリット(同一Paxosグループ)に強制配置する。これにより、親子間の結合やトランザクションは単一ノード(単一Paxosグループ)内で完結し、2PCのオーバーヘッドを完全にバイパスできる。

— 優れた例:CustomerIDを親に持ち、オーダーを物理的に同じ場所に配置
CREATE TABLE Customers (
CustomerID STRING(36) NOT NULL,
Name STRING(64),
) PRIMARY KEY(CustomerID);

CREATE TABLE Orders (
CustomerID STRING(36) NOT NULL,
OrderID STRING(36) NOT NULL,
TotalAmount INT64,
) PRIMARY KEY(CustomerID, OrderID),
INTERLEAVE IN PARENT Customers ON DELETE CASCADE;

レビュー時の言葉: 「おい、このテーブル設計だと毎回の決済で2PCが走ってレイテンシが200ms跳ねるぞ。`INTERLEAVE` を使ってコロケーションしろ」と言えるかどうかがシニアの分かれ目だ。

罠2:Commit Wait(TrueTimeの不確実性)との付き合い方

Spannerはトランザクションの順序を外部一貫性(External Consistency)で保証するため、コミット時に数ミリ秒(通常2〜10ms程度)の待機時間(Commit Wait)が発生する。
これを理解していないと、「単純な `UPDATE` なのに、なぜこんなにレイテンシがあるのか」と文句を言うジュニアが現れる。

設計上の対策:

  • 高頻度な単一行のカウンター更新などはSpannerに向かない: 1つのアトミックなカウンターを毎秒何千回も更新しようとすると、単一のPaxosグループで競合が起き、かつCommit Waitがボトルネックになる。
  • 解決策: カウンターが必要なら、アプリケーション層でシャード化(例: `Counter_0` から `Counter_9` に分散)するか、集計用の別アプローチ(Pub/Sub + Dataflow等でのストリーム集計)を検討する。OLTPとOLAPの役割分担を間違えてはならない。

—

4. チーフアーキテクトからの最終提言

Cloud Spannerの2PCとPaxosの統合は、分布式データベースにおける最高傑作の一つだ。しかし、それは「物理法則の制約を消し去った」わけではない。光速の限界、ネットワークの遅延、そしてTrueTimeの誤差という物理的現実の上で、極限の整合性を美しくプログラミングしているに過ぎない。

君たちが設計レビューを行うときは、常にこう自問してほしい:
「このトランザクションは、いくつものPaxosグループを横断する無駄なダンス(2PC)を踊っていないか?」
「データの物理配置は、トランザクションのトポロジーと一致しているか?」

この問いにロジカルに答えられるようになった時、君はもはや単なるプログラマではなく、真のディストリビューテッド・システム・アーキテクトだ。さあ、コードを書き直そう。

コメント

タイトルとURLをコピーしました