Spannerの読み取り専用レプリカとTrueTime:なぜ「強い整合性を持つ分散読み取り」が神速なのか
こんにちは。プロダクトのアーキテクチャ設計やコードレビューを担当していると、データベースの分散トランザクションやレプリケーションの挙動について、しばしば深い議論になります。
特に「Cloud Spanner」を採用するプロジェクトにおいて最も誤解されやすいのが、「読み取り専用レプリカ(Read-Only Replica)」の挙動と、そこに絡むTrueTimeのメカニズムです。
「読み取り専用レプリカだから、どうせ結果整合性(Eventual Consistency)なんでしょ?古いデータが返ってくるリスクはある?」
「リーダーノードからの非同期レプリケーションでラグがあるなら、リアルタイムな参照系クエリでバグるのでは?」
もしあなたのチームでこんな懸念が出ているなら、Cloud Spannerのコアアーキテクチャの本質がまだ伝わっていません。
結論から言いましょう。Cloud Spannerの読み取り専用レプリカは、「遅延を許容しながらも、数学的に絶対的な過去の時点における強い整合性(External Consistency / Linearizability)」を保証します。
今回は、チーフアーキテクトの視点から、Spannerのレプリケーションがリーダーノードとどう同期し、TrueTimeがどのように「整合性の魔法」をかけているのか、実務で使える設計パターンとともに徹底解説します。
—
1. 内部構造の真実:リーダーノードとの同期メカニズム
まず、一般的なRDBやNoSQLの「レプリカ」の概念を一度頭から捨ててください。Spannerのデータは「スプリット(Split)」という単位に分割され、それぞれがPaxosグループ(リーダーと複数のフォロワー、そして読み取り専用レプリカ)によって管理されています。
パフォーマンスと整合性のトレードオフをどう突破しているか
通常の書き込み(Write)や読み取り-書き込みトランザクション(Read-Write Transaction)は、Paxosグループの過半数(Quorum)の合意を必要とします。しかし、読み取り専用レプリカは、このPaxosの書き込みクォーラムの合意形成パスから意図的に外されています。
では、データはどのように同期されるのでしょうか?
1. Paxosログの非同期ストリーミング
リーダーノードでコミットされたPaxosログ(変更差分)は、バックグラウンドで読み取り専用レプリカへと継続的にストリーミングされ、適用されます。
2. レプリケーションラグ(Lag)の存在
当然、ネットワーク転送と適用処理の分だけ、リーダーノードの最新状態に対してラグ(通常は数ミリ秒〜数十ミリ秒、リージョン間であればネットワーク遅延に依存)が存在します。
「じゃあ、やっぱり古いデータが読まれるリスクがあるじゃないか」と思いましたね?
ここで登場するのが、Googleが誇るインフラストラクチャの心臓部、TrueTimeです。
—
2. TrueTime API:不確実性を手懐ける「絶対時間」
分散システムにおける最大の敵は「マシンの時計のズレ(Clock Skew)」です。物理時計は絶対にズレます。この避けられないズレを、GPSと原子時計を用いてハードウェア・ソフトウェアレベルで保証し、「現在時刻の不確実性の幅($\epsilon$:イプシロン、通常は1〜7ミリ秒)」として提供するのがTrueTime APIです。
TrueTime.now() -> [ earliest, latest ] (幅 2ε = 最大数ミリ秒)
スパナーの「タイムスタンプバウンド読み取り(Stale Read)」のカラクリ
読み取り専用レプリカが真価を発揮するのは、このTrueTimeを利用した「タイムスタンプ指定の読み取り(Stale Read)」を行う瞬間です。
Spannerは、読み取り専用トランザクションを実行する際、以下のように動作します。
1. 正確な過去のタイムスタンプの決定
クライアントが「正確に10秒前の時点でのデータを読みたい」、あるいは「現時点で、TrueTimeの不確実性の幅が完全に経過した安全な過去の時点(例: 10秒以上前のタイムスタンプ)」を指定します。
2. レプリカでのローカル読み取り
読み取り専用レプリカは、指定されたタイムスタンプまでのPaxosログが確実に自分のストレージに適用されていることを確認します。
3. ロックフリーかつグローバルに一貫した読み取りの完了
リーダーノードへの問い合わせや、他の書き込みトランザクションとのロック競合(Two-Phase Locking)を一切行わず、ローカルのディスクから一瞬でデータを読み出します。
つまり、読み取り専用レプリカは「リーダーノードに通信することなく、リーダーと完全に一致していた過去の正確なスナップショットを、ノーロックで安全に読み出す」ことができるのです。これが、Spannerが「読み取り専用レプリカでも強い整合性を維持できる」理由です。
—
3. 実務で使う:堅牢な設計パターンとコード実装
では、この特性を実務のシステム設計にどう落とし込むべきか。
一般のOLTPクエリでは強力なRead-Writeトランザクション(強い整合性・リアルタイム)を使いますが、参照負荷の高いダッシュボード、集計、検索のサジェスト、あるいはAPIのバックエンド参照では、Stale Readを積極的に活用すべきです。
以下に、Google Cloud Spannerのクライアントライブラリ(Python / Goを想定した概念コード)を用いた、実務的な設計パターンを示します。
パターンA: 最大許容ラグ(Exact Stale Read with Max Staleness)を指定する
「データの古さは最大でも5秒以内に収めたい。しかし、リーダーへの負荷は絶対に分散させたい」という要件の場合のパターンです。
from google.cloud import spanner
client = spanner.Client()
instance = client.instance(“my-production-instance”)
database = instance.database(“my-database”)
def get_user_profile_low_latency(user_id: str):
# 5秒以内のデータの古さを許容しつつ、読み取り専用レプリカから
# ネットワーク・ロックのオーバーヘッドなしで高速に取得する
with database.snapshot(exact_staleness=datetime.timedelta(seconds=5)) as snapshot:
row = snapshot.read(
table=”Users”,
keys=[[user_id]],
columns=[“UserId”, “UserName”, “Email”, “UpdatedAt”]
)
# データを処理…
return parse_row(row)
【チーフアーキテクトのコードレビュー視点】
> 「なぜここで `exact_staleness` を使っているのか?」をコードのコメントやPRに明記させてください。
> この設定により、Spannerは手元のレプリカがそのタイムスタンプまで追いついているかをミリ秒単位で確認し、追いついていれば即座にローカルから返します。リーダーノードのCPU負荷やネットワークホップを完全にバイパスできるため、QPS(Query Per Second)が桁違いに向上します。
—
4. パフォーマンス上の注意点とアンチパターン
どれほど優れたアーキテクチャであっても、設計を誤ればシステムは破綻します。現場でよく見かける「やってはいけないアンチパターン」を警鐘として鳴らしておきます。
アンチパターン1: すべての読み取りで「最新(Exact)」を求め、かつ読み取り専用レプリカを過信する
もしあなたが「最新の書き込み結果を、1ミリの遅延もなく読み取り専用レプリカから取得したい」と考えているなら、それはアーキテクチャの誤認です。
レプリケーションラグが存在する以上、レプリカに対して「現在の最新状態(`TIMESTAMP_BOUND`なし、あるいは `strong=True`)」を要求した場合、レプリカはリーダーへ同期待ちを発生させるか、あるいは安全のためにラグの分だけ待機させられます。結果としてレイテンシが跳ね上がります。
- 教訓:
- 「今書いたデータを即座に読みたい(Read-your-writes consistency)」なら、Read-Writeトランザクションまたはリーダーノードを参照する Strong Read を使う。
- 「数秒のラグが許容される参照系(Analytics, Dashboards, Feed)」なら、積極的に Stale Read(タイムスタンプ指定) を使ってレプリカを叩く。
アンチパターン2: データの不整合を恐れて、過度に大きな `max_staleness` を設定する
「ラグが怖いから1時間前のデータを読ませよう」という保守的な設計は、ユーザー体験を損ないます。Spannerのレプリケーションラグは通常、同一リージョン内であれば数ミリ秒〜数十ミリ秒です。
ビジネス要件に合わせて `staleness` は最小限(例: 1秒〜5秒)に攻めた設計にしてください。Spannerの真価である「極限の低レイテンシ」を最大限に引き出すことができます。
—
まとめ:分散システムの「常識」をアップデートせよ
Cloud Spannerの読み取り専用レプリカとTrueTimeの組み合わせは、分散データベースにおける「可用性」「スケーラビリティ」「整合性」のトレードオフを極限までハックした芸術品です。
- リーダーノードの負荷を分散しながら、
- TrueTimeによる数学的な整合性を担保し、
- 過去の特定時点のスナップショットをノーロックで爆速で引く。
このメカニズムを正確に理解していれば、「とりあえずキャッシュ層(Redis/Memcached)を前に挟んで整合性バグに悩まされる」というレガシーな設計から脱却し、Spanner単体で堅牢かつ圧倒的にスケーラブルなシステムを構築できます。
さあ、あなたの設計しているシステムのクエリを見直してみませんか?
「本当にその読み取りにリーダーのロックが必要か?」――その問いの答えが、システムのパフォーマンスを劇的に変えるはずです。
コメント