「強整合性」という幻想を捨てろ:Cloud Spannerにおけるステイル読み取りの極意
Cloud Spannerを触るエンジニアの多くが、最初に出会う「罠」がある。それは、「すべての読み取りに強整合性(Strong Read)を求める」という呪縛だ。
Spannerは確かに分散トランザクションにおいて魔法のような整合性を提供する。しかし、その「魔法」にはコストがある。全ノード間でのPaxos合意形成、リーダーノードへのトラフィック集中、そして何より、物理的な光速の壁に縛られたレイテンシだ。
もし君が、読み取りのためにわざわざマルチリージョンを跨いでリーダーノードに問い合わせ、数百ミリ秒の遅延を許容しているなら、それはSpannerを「ただの遅いRDBMS」として使っているに等しい。
今日は、Spannerのパフォーマンスを極限まで引き出すための「ステイル読み取り(Stale Reads)」の設計思想を叩き込む。
—
1. ステイル読み取りとは「過去の断片」を愛でること
ステイル読み取りとは、直近の書き込みを無視し、特定のタイムスタンプ時点でのデータを読み取る手法だ。
なぜこれが重要か? 「読み取り専用のクエリ」であれば、リーダーノードに行く必要がないからだ。 近くのレプリカノードで、ローカルに保持されたスナップショットを叩けば済む。これが実現できれば、ネットワークの往復時間を劇的に短縮できる。
設定の選択肢:どう「過去」を指定するか
クライアントライブラリで設定する場合、主に以下の3つの戦略がある。
- `exact_staleness`: 「今からN秒前」のデータを読む。
- `min_read_timestamp`: 「特定の時刻以降の最新」を読む。
- `read_timestamp`: 「特定の正確な時刻」を指定して読む。
実務で最も多用するのは `exact_staleness` だ。
PythonでのStale Read実装例
from google.cloud import spanner
def read_with_staleness(instance_id, database_id):
client = spanner.Client()
instance = client.instance(instance_id)
database = instance.database(database_id)
# 15秒前のデータを読み取る(リーダーへの問い合わせを回避)
# これにより、読み取り専用のトラフィックは近隣レプリカで完結する
with database.snapshot(exact_staleness=timedelta(seconds=15)) as snapshot:
results = snapshot.execute_sql(“SELECT FROM Orders WHERE Status = ‘PENDING'”)
for row in results:
print(row)
2. 許容される遅延(Staleness)の設計パターン
エンジニアが悩むのは「何秒の遅延を許容すべきか」という点だ。答えはシンプルだ。「ビジネス要件が許す最小値」だ。
パターンA:ダッシュボードや分析クエリ(遅延:10〜30秒)
管理画面の統計情報や、集計ジョブであれば、1秒や2秒の新鮮さは不要だ。15秒〜30秒のステイルを設定すれば、たとえ負荷がスパイクしても、書き込み系トランザクションに一切の悪影響を与えず、読み取り負荷をレプリカに完全にオフロードできる。
パターンB:ユーザープロフィールの表示(遅延:5秒未満)
「更新ボタンを押してから画面に反映されるまで、0.5秒のラグがあってもユーザーは文句を言わない」というケースは多い。この場合、1秒程度の `exact_staleness` を入れるだけで、読み取りレイテンシは劇的に改善し、システム全体の堅牢性が向上する。
—
3. パフォーマンスと整合性のトレードオフ:設計上の注意点
ステイル読み取りを導入する際に、必ずレビューで指摘すべきポイントがある。
① 「読み取りの断片化」を許容できるか?
ステイル読み取りは、書き込みの順序を厳密に守らない場合がある。例えば、Aという書き込みの後にBという書き込みがあったとき、ステイル読み取りではBが見えるがAが見えない、といった状況は(理論上)あり得る。
解決策: 「厳密な順序」が必要な操作には決してステイル読み取りを使ってはいけない。ユーザーの残高表示や決済処理には、必ず強整合性(デフォルト)を使え。
② レプリカの遅延(Replica Lag)を確認せよ
ステイル読み取りを設定しても、その時点のデータがレプリカに届いていなければ意味がない。Cloud Monitoringで `Spanner.googleapis.com/instance/replica_lag_seconds` を監視せよ。レプリカ遅延が設定した `exact_staleness` を上回ると、ステイル読み取りの恩恵が薄れるだけでなく、意図しない読み取り結果になるリスクがある。
③ クエリプランの確認
`EXPLAIN ANALYZE` を常に叩け。ステイル読み取りが正しく機能していれば、クエリプランのノードに「リーダー」へのアクセスが含まれていないはずだ。
—
終わりに:アーキテクトからの助言
多くのエンジニアが「整合性」という言葉に過剰に反応し、すべてを強整合性で設計して自爆する。しかし、現実世界を見渡せば、銀行口座の残高以外で「1秒前のデータが致命的になる」ケースなど稀だ。
ステイル読み取りを使いこなすということは、「データが常に最新である必要はない」という事実をビジネス側と合意し、システムをスケーラブルにする勇気を持つことだ。
君たちのシステムが、トラフィックの増大に悲鳴を上げているなら、まずはコードの読み取り箇所を見直せ。「本当に今、最新のデータを読む必要があるのか?」と自問自答せよ。
その問いの先に、Spannerを真に使いこなす道がある。設計レビューの場で、君の論理的な選択がシステムを救うことを期待している。
コメント