Cloud Spannerの「外部整合性」を支配する:TrueTimeの物理的限界と戦うアーキテクチャ設計
こんにちは。テックリードの私だ。
今日のコードレビューで、あるジュニアエンジニアがこんな質問をしてきた。
> 「Spannerってグローバルで強整合性(External Consistency)が担保されるんですよね?じゃあ、世界中のどこから同時に書き込んでも、実世界の正しい順序で綺麗にシリアライズされるから、アプリケーション側で排他制御やシーケンサーを一切考えなくていいんですよね?」
……甘い。非常に美しい誤解だ。
確かにCloud Spannerは、世界最高峰の分散データベースであり、ACID特性をグローバルスケールで完璧に満たす。だが、「マジックのように全自動で綺麗に隠蔽されているから、何も考えなくていい」というのは、インフラをただのブラックボックスとして扱う者の思考停止にすぎない。
プロフェッショナルなエンジニアであれば、「外部整合性(External Consistency)」が物理的制約の上でどうやって実現されているか、その裏側にあるTrueTimeのメカニズムと、それに起因するレイテンシの罠を正確に理解していなければならない。
今日は、Spannerのコア中のコアである外部整合性とTrueTimeの正体を丸裸にし、実務の現場でどう設計に落とし込むべきかをロジカルに伝授しよう。
—
1. 外部整合性(External Consistency)の本質
分散システムにおける最強の整合性モデルは、分散トランザクションの理論において「直列可能性(Serializable)」と呼ばれる。しかし、Spannerが目指したのはその先にある。
外部整合性(Linearizability / External Consistency):
> トランザクション $T_2$ が実世界でトランザクション $T_1$ が完了した後に開始された場合、$T_2$ のコミットタイムスタンプは必ず $T_1$ のコミットタイムスタンプよりも大きくなる。
つまり、「物理的な時計の進み方」と「トランザクションのコミット順序」が完全に一致する。ユーザーAが東京で何かを確定させ、そのニュースを電話で聞いたニューヨークのユーザーBが即座にトランザクションを実行したとき、Spannerの世界では、東京のトランザクションが絶対に先祖として記録される。
これをロックフリー、かつグローバルスケールでやってのけるのが、Google特許の心臓部 TrueTime だ。
—
2. TrueTimeの正体:絶対時間ではなく「不確実性区間」
「Googleは世界中のサーバーの時計を完全に同期させている」――これは半分正解で半分間違いだ。
物理的な世界において、完全にズレのない時計など存在しない。原子時計ですら温度や経年変化でドリフトし、GPS信号は遮断されるリスクがある。
だからこそ、GoogleのTrueTime APIは、時刻を単一の数値(`t`)ではなく、誤差($\epsilon$:イプシロン)を持つ「幅」として返す。
TrueTime.now() = [earliest, latest]
|—— 2ε ——|
<--t_now-->
ここで重要なのは、$\epsilon$(不確実性)は通常1ms〜7ms程度(GPSと原子時計のハードウェア冗長化による)だが、ネットワーク状況やハードウェアの負荷によって変動するということだ。
コミット待ち(Commit Wait)という代償
Spannerが外部整合性を保証するために使うアルゴリズムのキモが、この Commit Wait だ。
1. トランザクションがコミットされる際、リーダーレプリカはタイムスタンプ $s$ を割り当てる。この $s$ は、その時点の `TrueTime.latest()` よりも大きくなければならない。
2. しかし、Spannerは即座にコミットを完了させない。
3. 「実世界の時間において、真の現在時刻が確実に $s$ を過ぎた」と言い切れるまで、トランザクションの応答を意図的に待たせる。
待ち時間の長さは、まさにその瞬間のTrueTimeの不確実性区間、すなわち $2\epsilon$ に等しい。
— 概念的なタイムライン
T1 Commit (s) 割り当て ──> [Commit Wait (2ε 待機)] ──> クライアントへ成功返却
↑
この間に物理時間が確実に s を超えるのを待つ
つまり、Spannerの書き込みレイテンシの底には、物理的な原子時計とGPSの精度($2\epsilon$ = 数ミリ秒)がハードリミットとして常に横たわっている。これを無視した設計は、高負荷時に必ずレイテンシの雪崩を引き起こす。
—
3. 実務で直面するアンチパターンと堅牢な設計パターン
このアーキテクチャ特性を理解していないと、コードレビューで致命的な見落としを生む。現場でよくある失敗と、それを回避するための設計パターンを見ていこう。
アンチパターン:高頻度な単一キーへのホットスポット書き込み
「1つのカウンター行に対して、数千のクライアントからミリ秒単位でひたすら `UPDATE` を飛ばし続ける」
これはRDB時代の発想を引きずった最悪のアンチパターンだ。
Spannerは分散データベースであり、行はスプリット(Split)されて分散配置されるが、同一のキーレンジや単一のホット行に対する競合は、Paxosグループのリーダーへの集中を生む。さらに、TrueTimeのコミット待ちと相まって、スループットは劇的に頭打ちになり、レイテンシが跳ね上がる。
【堅牢な設計パターン】シャード化と非同期集約
カウンターや在庫数のような高頻度更新が必要な場合、キーをシャード(分散)させ、読み取り時に集約するパターンをとる。
— 【悪い例】単一の行を更新
UPDATE Inventory SET stock = stock – 1 WHERE item_id = ‘A’;
— 【良い例】シャードキーを持たせて競合を分散
— item_id + 乱数サフィックス(00〜09)で10個のPaxosグループに書き込みを分散する
UPDATE ShardedInventory
SET stock = stock – 1
WHERE item_id = ‘A’ AND shard_id = 3;
アプリケーション側で集約するか、読み取り時に `SUM(stock)` を引く設計に落とし込む。どうしても厳密な単一リソースの排他制御が必要な場合は、楽観的同時実行制御(OCC)のコンフリクトコストを意識し、リトライロジックを堅牢に実装する必要がある。
—
4. 読み取り専用トランザクション(Read-Only Transactions)の魔術
Spannerの真骨頂は、読み取り専用トランザクションが、書き込みのロックを一切取得しない点にある。
外部整合性が担保されているため、アプリケーションは過去の任意の正確な時点(Timestamp Bound)を指定して、一貫性の崩れないデータをノーロックで読み出すことができる。
コード例:過去の正確な時点を読む(Stale Read)
from google.cloud import spanner
from google.cloud.spanner_v1 import TimestampBound
import datetime
client = spanner.Client()
database = client.instance(“my-instance”).database(“my-db”)
10秒前の正確な状態を読み出す(ロックなし、他トランザクションと競合しない)
ten_seconds_ago = datetime.datetime.utcnow() – datetime.timedelta(seconds=10)
timestamp_bound = TimestampBound(exact_staleness=datetime.timedelta(seconds=10))
with database.snapshot(timestamp_bound=timestamp_bound) as snapshot:
results = snapshot.execute_sql(
“SELECT user_id, balance FROM Accounts WHERE status = ‘ACTIVE'”
)
for row in results:
print(f”User: {row[0]}, Balance: {row[1]}”)
この設計がもたらす実務的メリット
- 完全なアイソレーション: 重いバッチ処理やアナリティクス用クエリを走らせても、OLTP側の書き込み性能(TPS)にミリ秒単位の悪影響も与えない。
- レプリカの活用: リーダーだけでなく、地理的に近いリードオンリーレプリカ(ROレプリカ)に対してStale Readを投げることで、レイテンシを極限まで削ぎ落とすことができる。
—
5. チーフアーキテクトからの総括
Cloud Spannerの外部整合性とTrueTimeは、分散システムの「CAP定理」のジレンマに対するGoogleからの美しき回答だ。可用性を犠牲にせず、線形整合性をグローバル規模で達成している。
しかし、それは「物理法則を魔法で消し去った」わけではない。
- すべての書き込みの背後には、必ず $2\epsilon$ のCommit Waitという物理的な時間のゆらぎが存在する。
- グローバルに分散したデータモデルにおいて、安易なホットスポット作成はシステムの息の根を止める。
君たちがコードレビューやアーキテクチャ設計を行うときは、単に「Spannerだから落ちない、整合性が取れる」で思考を止めてはならない。
「このクエリはどのPaxosグループにヒットし、どの程度のTrueTimeの不確実性と競合コストを孕んでいるか」をイメージできること。そこまで到達して初めて、真のSpanner使いと呼べる。
さあ、設計書を開け。甘い前提はすべて捨て去り、物理法則と美しく調和するコードを書こう。
コメント