こんにちは!クラウドシステムの設計やデータベースの裏側を覗くのが大好きな先輩エンジニアです。
今回は、世界中のシステムエンジニアがその美しさに唸る、Cloud Spannerの心臓部「外部整合性(がいぶせいごうせい)」についてお話しします。
「なんだか難しそうな名前だな……」と思いましたか?
安心してください。専門用語の壁をすっと取り払って、日常の身近な例えを使いながら、この凄まじい仕組みの本質を一緒に紐解いていきましょう。
ここをクリアすれば、Cloud Spannerの基本はバッチリマスターできますよ!それでは、コーヒーでも飲みながらリラックスして読んでいってくださいね。
—
1. 世界中のデータが「一瞬で」一致する不思議
私たちが普段使っているスマホアプリやWebサービス。裏側ではデータベースが動いていますが、もし「東京のサーバー」と「アメリカのサーバー」で同時にデータを書き換えたらどうなるでしょうか?
普通のデータベースだと、光の速さの限界(物理的な距離の壁)があるため、どうしても情報の伝達にタイムラグが生まれます。「あれ、さっきアメリカで更新したはずのデータが、東京の画面にまだ反映されていないぞ?」という現象が起きてしまうわけです。
しかし、Cloud Spannerは違います。地球の裏と表で同時にデータを更新しても、世界中のどこから見ても「絶対に矛盾のない、正しい順番」でデータが見えるようになっています。
この「現実世界で起きた出来事の順番と、データベース上の記録の順番が完全に一致する性質」を、コンピュータ科学の世界では外部整合性(External Consistency)、あるいはリニアizability(線形可能性)と呼びます。
—
2. 日常で例えてみよう:世界時計の「誤差」との戦い
なぜこれがすごいことなのか、身近な例で考えてみましょう。
いま、遠く離れた場所にいるA君とB君がいるとします。
- A君(東京):「12時00分00秒」に銀行口座から1万円を引き出しました。
- B君(ニューヨーク):「12時00分01秒」に同じ口座に残っていた全額を引き出しました。
現実世界では、A君の操作が先で、B君の操作が後です。したがって、データベースの記録も必ず「A君の処理 $\to$ B君の処理」の順番で並ばなければなりません。もしこれが逆になって、B君の処理が先になったら、A君の引き出しが「残高不足」でエラーになってしまいますよね。
ここで問題になるのが、「パソコンやサーバーの内蔵時計は、わずかにズレている」ということです。東京の時計とニューヨークの時計が、もし0.5秒ずれていたらどうでしょう? ネットワークの遅延も合わさって、コンピュータには「あれ? B君の操作の方が先に来たぞ?」と誤認されてしまうリスクがあります。
この「時計のズレ」という致命的な弱点を、物理とテクノロジーの力で華麗に解決したのが、Cloud Spannerの秘密兵器「TrueTime(トゥルータイム)」です。
—
3. TrueTimeの魔法:「今、何秒か」を「幅」で捉える
普通の時計は、「今は12時00分00.00秒です」と点(1つの時刻)で答えます。だからズレたときに困るのです。
しかし、Spannerの心臓部にあるTrueTimeは違います。GPS衛星と原子時計を組み合わせることで、時刻を「幅(タイム・アンソティ/不確実性)」として把握します。
> 「今の正確な時間は、だいたい12時00分00.00秒から、12時00分00.07秒の間です!」
このように、あえて「ズレの幅(多くの場合数ミリ秒以内)」を認めてあげるのです。
Spannerはどうやって順番を守るのか?
Spannerはこの「時間の幅」を利用して、次のようなルールを定めています。
1. A君のトランザクションが完了したときの「時間の幅」を記録する。
2. 次にB君のトランザクションが始まる時、「A君の時間の幅の終わり」よりも、自分の「時間の幅の始まり」が後になるまで、ほんの数ミリ秒だけ待つ(コミット・ウェイト)。
この絶妙な「ちょっと待つ」という仕掛けにより、物理的な時間の逆転を完全に防ぎます。これにより、現実世界の因果律(原因が先で、結果が後)が、グローバル規模のデータベースでも完璧に守られるのです。これが外部整合性の正体です。
—
4. 実際にコードと挙動をイメージしてみる
Cloud Spannerを操作する際、開発者はこの複雑な「時計の同期や順番の制御」を意識する必要はありません。Spannerが裏側で完璧にやってくれます。
例えば、Pythonを使ったCloud Spannerへの書き込みイメージは次のような形です。
from google.cloud import spanner
クライアントの初期化
client = spanner.Client()
instance = client.instance(“my-global-instance”)
database = instance.database(“my-database”)
def update_balance(transaction, user_id, amount):
“””指定されたユーザーの残高を更新するトランザクション
Cloud Spannerは、このトランザクションが「いつ世界に確定したか」を
外部整合性を保った状態で安全に記録します。
“””
# 1. 現在の残高を読み込む
row_iters = transaction.execute_sql(
“SELECT balance FROM Accounts WHERE user_id = @user_id”,
params={“user_id”: user_id},
param_types={“user_id”: spanner.param_types.INT64},
)
current_balance = list(row_iters)[0][0]
# 2. 残高を計算
new_balance = current_balance + amount
# 3. データを書き戻す
transaction.update(
table=”Accounts”,
columns=[“user_id”, “balance”],
values=[(user_id, new_balance)],
)
トランザクションの実行
Spannerは内部でTrueTimeを使い、世界中のどこから呼ばれても
矛盾のない順序でこの処理を安全に確定(コミット)させます。
database.run_in_transaction(update_balance, user_id=101, amount=1000)
開発者はただ普通にトランザクションを書くだけで、グローバル規模で「データの整合性が絶対に壊れない」という最高峰の安心を手に入れることができます。
—
5. まとめ:なぜCloud Spannerの外部整合性は革命的なのか?
これまでのデータベースの歴史では、大きなジレンマがありました。
- 「データをあちこちに分散させて速くする(可用性・スケール)」か、
- 「一つの場所にデータを集めて順番を厳密に守る(強整合性)」か。
この2つはトレードオフ(一長一短)であり、両立は不可能だと長年言われてきました。それを「TrueTime」というハードウェアとソフトウェアの融合によって見事に打ち破ったのが、Cloud Spannerの外部整合性です。
「世界中どこにデータを置いても、あたかも目の前にある1つのノートにペンで順番に書き込んでいるかのように、矛盾なく記録される」。
このアーキテクチャの美しさと力強さを知ると、クラウドデータベースの設計が一段と楽しくなりますよね。ここを理解していれば、Spannerを使ったシステム設計の基本はもうバッチリです!
それでは、次回のコアアーキテクチャ解説もお楽しみに!
コメント