【入門編】 2相コミット (2PC) – Cloud Spanner

こんにちは!Cloud Spannerの世界へようこそ。
チーフアーキテクトの私です。

「グローバル規模でデータをガッチリ守りながら、止まらずに超高速で動くデータベース」と聞くと、なんだか難しそうに感じるよね。でも大丈夫。今日は、Cloud Spannerの心臓部であり、複数の場所に散らばったデータを一瞬でまとめ上げる魔法の仕組み「2相コミット(2PC)」について、専門用語をできるだけ使わずに、まるで日常のワンシーンのように優しく紐解いていくよ。

ここをクリアすれば、Cloud Spannerの分散データベースとしての本質がスッと見えてきて、基本はバッチリマスターできますよ。肩の力を抜いて、一緒に見ていこう!

—

1. なぜ「分散」すると難しくなるの?(日常のたとえ)

想像してみてほしい。
あなたは今、仲良しの友人3人(A君、B君、C君)と一緒に、週末の「超豪華・合同バーベキュー大会」を企画しているとするよ。

  • A君は「お肉」を担当。
  • B君は「ドリンク」を担当。
  • C君は「場所取り・炭」を担当。

ここで、「全員の準備が完璧に整ったら、今週末の開催を『正式に決定(コミット)』する!」というルールにしたとしよう。

もし、A君が「肉の調達が無理だった!」と言ったらどうなる?
B君とC君だけが準備万端でも、バーベキューは成り立たないよね。「全員がOKを出したときだけ開催する。1人でもNGなら、今回はすべて白紙に戻す(全員の準備をナシにする)」というのが、データベースの世界でいう「アトミック(原子性:すべて成功するか、すべて失敗するか)」という性質なんだ。

Cloud Spannerでも全く同じ。
データは世界中、あるいは巨大なサーバー群の中で、「スプリット」と呼ばれる小さな箱にパズルのピースのように分割されて保存されている。1つの操作(トランザクション)が、この箱をまたぐとき、私たちは「全員が確実に準備できたか?」を確認し合わなければならないんだ。

—

2. Cloud Spannerの裏側:2相コミット(2PC)の正体

この「全員が確実に準備できたか?」を確認して、一斉にゴーサインを出す仕組みこそが、「2相コミット(2PC:Two-Phase Commit)」だよ。

名前はちょっといかめしいけれど、名前の通り「2つのステップ(段階)」で行うだけのシンプルな約束事なんだ。

第1段階:準備フェーズ(「みんな、準備はいいか?」)

1. コーディネーター(リーダー役)の誕生:
複数のスプリットにまたがる変更が発生したとき、その中の1つのスプリットが「今回は私がまとめ役(コーディネーター)をやるね!」と名乗り出る。
2. 「準備(Prepare)」のお願い:
まとめ役は、関係する他のスプリットたち(参加者)にこう言う。

  • 「こういう変更をしたいんだけど、君たちの担当分のデータ、今からロックして書き込む準備をしておいて! 準備できたら『OK(投票)』を教えてね」

3. 返事を待つ:
参加者たちは、自分の担当データに鍵をかけ、「いつでも書き込めるよ!」となれば「OK!」と返事をする。もし途中でシステムが壊れそうだったりしたら「NG…」と答える。

第2段階:コミットフェーズ(「よし、全員OKだから一斉に本番だ!」)

1. 最終判断:
まとめ役は、全員から「OK!」の返事が返ってきたかを確認する。

  • もし全員OKなら:「よし、本番の変更を書き込んで!」と一斉に指示(Commit)を出す。
  • もし1人でもNG(または返事がない)なら:「今回は中止!みんな元の状態に戻して(Abort)」と指示を出す。

2. 完了:
みんなが指示通りに動き終えたら、無事にトランザクションが完了する。

—

3. コードで見る「安全な世界」

Cloud Spannerを使うとき、実は僕たちはこの複雑な2相コミットのやり取りを、意識する必要すらないように設計されている。Spannerの賢いシステムが、裏側で勝手にこの調整を完璧に行ってくれるからだ。

例えば、複数のアカウント間で残高を移動させるトランザクションを考えてみよう。

// JavaでCloud Spannerのトランザクションを実行するイメージ
databaseClient.readWriteTransaction().run(transaction -> {
// スプリットAにある「口座X」からお金を引く
long balanceX = queryBalance(transaction, “口座X”);
transaction.buffer(Mutation.newUpdateBuilder(“Accounts”)
.set(“AccountID”).to(“口座X”)
.set(“Balance”).to(balanceX – 1000)
.build());

// スプリットBにある「口座Y」にお金を足す
long balanceY = queryBalance(transaction, “口座Y”);
transaction.buffer(Mutation.newUpdateBuilder(“Accounts”)
.set(“AccountID”).to(“口座Y”)
.set(“Balance”).to(balanceY + 1000)
.build());

return null; // ここでトランザクション終了。裏側で2PCが自動発動!
});

> チーフアーキテクトからのワンポイント解説
> 上記のコードでは、「口座X」と「口座Y」が、物理的にまったく違うサーバー(スプリット)に保存されているかもしれない。
> 普通のデータベースなら頭を抱えるような状況だけど、Cloud Spannerはコードの最後の `run` の裏側で、自動的に2相コミットを走らせ、「両方の口座が同時に更新される」か、あるいは「どちらも一切変更されない」のどちらかを完璧に保証してくれるんだ。

—

4. 忘れないで!分散システムの現実と向き合うコツ

2相コミットは魔法のように便利だけど、チーフアーキテクトとして実務のアドバイスを一つ。

「複数のスプリットをまたぐ(=たくさんの仲間を巻き込む)トランザクション」は、どうしても1つのスプリットだけで完結する操作に比べて、ネットワークを行き来する回数が増える分、ちょっぴり時間がかかる。

だから、Cloud Spannerで爆速のパフォーマンスを極めたいときは、

  • 「よく一緒に更新するデータは、同じスプリット(あるいは同じテーブルの行グループ)にまとめて配置する」(これをインターリーブ設計と言ったりするよ)

という工夫を少しだけ意識してあげると、Spannerのポテンシャルを120%引き出すことができるんだ。

—

まとめ

  • Cloud Spannerの2相コミット(2PC)とは、複数の場所に散らばったデータを、全員の合意のもとで安全に同時更新するための仕組み。
  • 「準備して(Prepare)」から「一斉に実行する(Commit)」の2ステップで行われる。
  • 複雑な仕組みだけど、Cloud Spannerが裏側で全部よしなにやってくれるから、僕たちは安心してビジネスロジックに集中できる!

どうだい? 2相コミットの仕組みが、少し身近に感じられたんじゃないかな。
この基本さえ押さえておけば、Cloud Spannerがどれほど堅牢で信頼できるデータベースか、その本質がしっかりと見えてくるはずだ。

さあ、次のステップへ進もう。君なら絶対にマスターできるよ!

コメント

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