Spannerの「STORING句」を使いこなせ:カバリングインデックスでI/Oを極限まで削ぎ落とせ
Spannerにおいて、パフォーマンスのボトルネックは「ノードの計算リソース」ではない。「データアクセスの回数(I/O)」だ。
Spannerのクエリ実行計画を眺めていると、インデックスを引いた後に `Table Scan` が発生しているのを見かける。これは、インデックスには検索キーしか含まれておらず、クエリが必要とする実データ(非キー列)を取得するために、わざわざ本体テーブルへ「行ルックアップ」をしに行っている証拠だ。
この「行ルックアップ」こそが、高トラフィック環境でレイテンシを跳ね上げ、CPU使用率を急騰させる元凶である。これを物理的に排除する手法こそが、`STORING` 句によるカバリングインデックス(Covering Index)だ。
—
1. なぜ「STORING」が最強の武器なのか
Spannerのインデックスは、それ自体が独立したキー・バリューストアのような構造をしている。通常、インデックスのキーには「検索条件に使う列」のみを指定するが、`STORING` 句を使うことで、検索には使わないがクエリ結果として頻繁に参照される列を、インデックスのリーフノードに「コピー」として持たせることができる。
これにより、クエリエンジンはインデックスをスキャンするだけで必要なすべてのデータを回収できる。「テーブル本体への参照をゼロにする」。この一点突破が、大規模分散データベースにおけるパフォーマンス最適化の黄金律だ。
2. 実践:STORING句の設計パターン
例えば、ユーザーの決済履歴を管理するテーブルがあるとしよう。
— 決済テーブルの定義
CREATE TABLE Payments (
PaymentId INT64 NOT NULL,
UserId INT64 NOT NULL,
Amount INT64 NOT NULL,
Status STRING(20) NOT NULL,
CreatedAt TIMESTAMP NOT NULL,
) PRIMARY KEY (PaymentId);
ここで「特定のユーザーの最新の決済ステータスを知りたい」というクエリが頻発する場合を考える。
NGなインデックス設計
CREATE INDEX idx_user_status ON Payments (UserId);
これだと、`SELECT Status FROM Payments WHERE UserId = 100` を叩いた瞬間に、インデックスで見つけた `PaymentId` を元に本体テーブルへのルックアップが走り、レイテンシが確実に悪化する。
BESTなインデックス設計(カバリング)
CREATE INDEX idx_user_status_covering ON Payments (UserId)
STORING (Status); — ここが肝。Statusをインデックス内に埋め込む
これで、クエリエンジンはインデックスのみを読み込み、即座に結果を返す。分散処理において「ネットワーク越しのデータフェッチを減らす」ことは、何よりも優先すべき最適化だ。
—
3. 運用・設計上の「極限の注意点」
カバリングインデックスは魔法ではない。エンジニアたるもの、その代償も正しく理解して使い分ける必要がある。
① ストレージコストの増大
`STORING` に指定した列は、実質的にデータの二重保持となる。書き込み(INSERT/UPDATE)が発生するたびに、インデックス側の更新コストも増える。頻繁に更新される列を安易に含めるのは愚策だ。「読み取り専用に近い列」あるいは「更新頻度が低い列」をターゲットにするのが鉄則である。
② データ型の制限
大きな `BYTES` 型や `STRING(MAX)` のような巨大な列を `STORING` に入れるのは避けるべきだ。リーフノードのサイズが肥大化し、メモリに乗りにくくなる。結果としてキャッシュヒット率が下がり、パフォーマンスが本末転倒になる。
③ インデックスの爆発を避ける
「あれもこれも」と `STORING` を詰め込むと、インデックスのサイズが肥大化し、ノードのストレージ容量を圧迫する。あくまで「クエリの特定のパターンを爆速化するため」という目的意識を持ち、クエリの実行計画(`EXPLAIN`)を見て、ルックアップが消滅したことを確認してから実装せよ。
—
4. チーフアーキテクトからの提言
システム設計において「銀の弾丸」は存在しない。しかし、カバリングインデックスはそれに最も近いツールの一つだ。
私が設計レビューでコードを見る際、「このクエリはテーブルルックアップなしで完結しているか?」という点を常にチェックする。もし、インデックスがあるのにルックアップが発生しているなら、それは設計の敗北だ。
Spannerを使う以上、単にデータを格納するのではなく、「データがどう配置され、どう読み出されるか」という物理層の挙動を脳内に描け。`STORING` 句を使いこなせるかどうかで、そのエンジニアが「SpannerをただのRDBとして扱っているか」それとも「分散アーキテクチャの本質を理解しているか」が明確に分かれる。
さあ、今すぐ `EXPLAIN` を叩け。貴方のシステムのボトルネックは、そこにあるかもしれない。
コメント