【実務・中級編】 外部整合性 – Cloud Spanner

外部整合性(External Consistency):Spannerが「物理時間」をハックして実現した究極の約束

多くのエンジニアは「トランザクションの分離レベル」という言葉に踊らされ、最終的に「直列化可能(Serializable)」という呪文に逃げ込みます。しかし、分散システムにおける「直列化可能」は、単なる論理的な整合性に過ぎません。

世界中のどこからアクセスしても、トランザクションの順序が「現実世界の発生順序」と一致する。この狂気じみた保証こそが、Cloud Spannerを他のRDBやNoSQLと決定的に分かつ「外部整合性(External Consistency)」の正体です。

今回は、この魔法がどのように物理的に実現され、我々エンジニアがどう設計すべきかを、現場の視点から解剖します。

—

1. TrueTime:物理の限界をソフトウェアで超える

外部整合性を語る上で、TrueTime APIを避けて通ることはできません。

分散システムにおいて、各ノードの時刻を完全に同期させることは理論上不可能(クロックスキュー問題)です。しかし、Spannerはこれを「絶対的な正解」を求めるのではなく、「不確実性($\epsilon$)を伴う範囲(Interval)」として定義することで解決しました。

  • [start, end]: 物理的な時刻の幅。
  • TrueTime: 常にこの期間内に「絶対的な時刻」が存在することを保証する。

Spannerは、コミットの瞬間にこの不確実性の幅が解消されるまで「待機(Commit Wait)」します。この数ミリ秒の待機こそが、地球の裏側で発生したイベントAが、イベントBより前に発生したことを全ノードが完全に合意するための「代償」です。

2. なぜこれが「実務」で最強の武器になるのか

外部整合性があるおかげで、我々のアプリケーション設計は劇的にシンプルになります。

ケーススタディ:金融取引における「口座振替」

従来の分散DBであれば、メッセージキューや分散ロックを駆使して「順序」を担保しなければなりませんでした。Spannerでは、単一のトランザクションで完結させれば、後続の読み取りは常に最新の確定状態を保証されます。

— Spannerでの堅牢な更新パターン
— 外部整合性により、このコミットが成功した瞬間に、
— 全てのノードからこの残高変更が「最新」として観測される。
BEGIN TRANSACTION;
— 読み取りと書き込みが外部整合性によって一貫性を維持
UPDATE Accounts SET Balance = Balance – 100 WHERE Id = ‘A’;
UPDATE Accounts SET Balance = Balance + 100 WHERE Id = ‘B’;
COMMIT;

これだけで、「ある拠点では残高が反映されているのに、別拠点では古いまま」という地獄のようなRace Conditionから解放されます。

3. パフォーマンス上の注意点:設計者が払う「対価」

外部整合性は魔法ではありません。コストは必ず支払う必要があります。

1. Commit Waitのオーバーヘッド:
Spannerはコミット時に数ミリ秒(通常10ms未満)の待機を強制します。超高頻度な更新(ホットスポット)が発生する場合、この待機時間がレイテンシのボトルネックになります。
2. 書き込みの集中を避ける:
「外部整合性」を担保するためにトランザクションは直列化されます。キーの設計でインクリメントなID(UUIDではなく、連番)を使うと、特定の範囲に書き込みが集中し、Spannerの分散メリットが殺されます。

  • 対策: 常にキーはランダム化する(例: `UUID` または `HASH(user_id)` を先頭に付与)。

4. 堅牢な設計パターン:ReadOnly Transactionの活用

外部整合性の恩恵を最大限に受けるなら、`ReadOnly Transaction`を積極的に使うべきです。

特定のタイムスタンプを指定して読み取る「Snapshot Read」は、Spannerの真骨頂です。読み取り専用であれば、ロックを取得することなく、過去の任意の時点における外部整合性が保たれたデータを読み込めます。

// Java (Google Cloud Spanner Client) での例
Timestamp bound = Timestamp.now();
// このタイムスタンプにおける整合性のあるスナップショットを取得
ReadOnlyTransaction tx = dbClient.readOnlyTransaction(TimestampBound.ofExactStaleness(Duration.ofSeconds(15)));

// これにより、読み取り負荷を分散しつつ、整合性の崩れたデータを見ることは100%ない

結論:Spannerを「ただのRDB」として使うな

Spannerを導入する最大の理由は「RDBだから」ではありません。「分散環境で、整合性を犠牲にせずにスケールする」という、エンジニアの聖杯を手にできるからです。

  • 設計レビューの鉄則:

「整合性」をアプリケーション層で担保しようとしていませんか? そのロジックはSpannerの外部整合性に任せ、コードを削除しましょう。

  • パフォーマンスの鉄則:

ホットスポットを作らず、読み取りはスナップショットを活用する。これができれば、数千万リクエストを捌くシステムも、単一のコードベースから驚くほどシンプルに構築できます。

技術は常にトレードオフですが、Spannerの外部整合性は、そのトレードオフの先にある「エンジニアの自由」です。心ゆくまで活用してください。

コメント

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