【実務・中級編】 クエリコンパイルパイプライン – Cloud Spanner

Cloud Spannerクエリコンパイルパイプラインの深層:なぜ「適当なSQL」が巨大クラスタを沈めるのか

テックリードの私だ。コードレビューや設計レビューの場で、次のようなSQLを平然と書いてくるエンジニアが後を絶たない。

「とりあえず結合しておけばSpannerがうまくやってくれると思った」
「WHERE句で関数を噛ませているけど、インデックスが効くはずだ」

甘い。Cloud Spannerは、単なる「マネージドな分散RDBMS」ではない。数千、数万のコアとペタバイト級のストレージが協調して動く、緻密な分散コンピューティングエンジンだ。その頭脳中枢であるクエリコンパイルパイプラインの挙動を理解せずして、Spannerの真のパフォーマンスを引き出すことなど不可能である。

今回は、SQL文が入力されてから、分散物理プランへと昇華し、スプリット(Split)を跨いで並列実行されるまでの全プロセスを、私の知見のすべてを注いで解剖する。

—

1. クエリコンパイルパイプラインの全体像

Cloud Spannerに投げられたSQLは、分散データベースの歴史と最先端の分散クエリ最適化技術が詰まったパイプラインを猛烈な速度で駆け抜ける。

[SQL Text]
↓ (1. パース & AST生成)
[Abstract Syntax Tree (AST)]
↓ (2. 意味解析 & カタログ照合)
[Annotated AST]
↓ (3. 論理プラン生成 (Logical Plan))
[Logical Operator Tree]
↓ (4. 分散最適化 & 物理プラン生成 (Physical Plan))
[Distributed Physical Plan]
↓ (5. 分散実行エンジン (Roster / Dremel系))
[Result Streams]

このパイプラインの各段階で、何が起きているのか。実務でパフォーマンスチューニングを行う際に直結する知識として、レイヤーごとに紐解いていこう。

—

2. パースから論理プランまで(フロントエンドの仕事)

ステップ1 & 2: パース・意味解析・カタログ照合

SQL文字列はまずLexerとParserによって字句解析され、AST(抽象構文木)へと変換される。しかし、これだけでは単なる「文法の正しさ」を証明したに過ぎない。

次に、Spannerの集中型(またはキャッシュされた)システムカタログと突合され、以下が検証される。

  • 参照されているテーブルやカラムは実際に存在するか?
  • クエリを発行したユーザーに権限はあるか?
  • 型の整合性は取れているか?(暗黙の型変換が必要か?)

ここで生成されるのが、スキーマ情報で注釈された Annotated AST だ。

ステップ3: 論理プラン(Logical Plan)の生成

Annotated ASTは、リレーショナル代数のオペレータ(Scan, Filter, Project, Join, GroupByなど)のツリー構造である論理プランに変換される。

この段階では、「データの物理的な配置(どこにデータがあるか)」は一切考慮されない。 純粋に数学的・論理的に最も効率よく結果を導き出すための構造化が行われる。

—

3. 核心:分散物理プランへの変換と最適化

ここからがCloud Spannerの真骨頂だ。通常のRDBMSであれば、論理プランをそのまま自マシンのストレージエンジン向けに最適化するが、Spannerは「ペタバイト級のデータを数千のノードに分散して保持している」という制約に向き合わなければならない。

ステップ4: 分散物理プラン(Distributed Physical Plan)の生成

論理プランは、Spannerの分散実行エンジンが理解できる物理プランへとコンパイルされる。ここで重要なのが、以下の2点だ。

1. スプリット境界(Split Boundaries)の意識
Spannerのストレージは「スプリット」という単位で物理的に分割され、世界中の異なるノード(リーダーおよびロガーレプリカ)に配置されている。物理プランは、どのスプリットに対してどの処理をプッシュダウン(Push-down)すべきかを決定する。
2. 分散Joinとシャッフル(Distributed Joins & Shuffling)
データが単一ノードに収まらないため、テーブル結合はローカルで行えない場合がある。コンパイラは、コストベースオプティマイザ(CBO)の統計情報に基づき、以下のいずれかの戦略を選択する。

  • Distributed Hash Join: データをネットワーク経由でシャッフルし、結合キーの一致するノードへ集約する。
  • Interleave Join (Colocated Join): 親子関係(Interleave構造)が定義されている場合、物理的に同じスプリットにデータが存在することが保証されているため、ネットワーク転送(シャッフル)を完全にゼロにしてローカルで結合を実行する。

—

4. 【アンチパターンと実例】オプティマイザを迷わせる愚行

実務のコードレビューで私が一番絶望するのは、コンパイラやCBOの挙動を無視したクエリを書かれた時だ。典型的な失敗例を見てみよう。

❌ 悪い例:非効率なスキャンを引き起こすWHERE句

— レビュー指摘対象のクエリ
SELECT
FROM Users
WHERE MOD(UserId, 10) = 5;

何が問題か?
`UserId` にプライマリキーのインデックスが貼られていたとしても、`MOD` 関数という「式(Expression)」でラップされているため、物理プランの生成時にコンパイラはインデックスのレンジスキャン(Range Scan)を選択できない。結果として、テーブル全体のフルスキャン(Full Table Scan)となり、全スプリットに対して並列スキャンリクエストが走る。データ量が増えた瞬間にレイテンシが爆発する典型例だ。

⭕ 正しい設計:インデックスを利かせるプレディケート

— 改善されたクエリ
SELECT
FROM Users
WHERE UserId >= @start_id AND UserId <= @end_id AND MOD(UserId, 10) = 5; あるいは、そもそもハッシュ値によるパーティショニングが必要なら、計算済みのカラムをあらかじめ保持し、そこにセカンダリインデックスを貼るべきだ。コンパイラが「インデックスのどこからどこまでを読めばいいか(Seek)」を判断できるクエリを書くこと。これがエンジニアの責務である。 ---

5. 堅牢な設計パターン:オプティマイザを味方につける

Spannerのクエリコンパイルパイプラインをハックし、常に最速の物理プランを引き出すための設計原則を授けよう。

1. インターリーブ(Interleave)構造の徹底活用

前述の通り、物理プランにおいて最もコストが高いのは「ネットワークを跨いだデータのシャッフル」だ。
エンティティ間に「1対多」の親子関係があるならば、必ず `INTERLEAVE IN PARENT` を用いて物理的な近接性を担保しろ。コンパイラはこれを検知すると、Colocated Join という魔法のような高速プランを自動生成する。

— 親テーブル
CREATE TABLE Customers (
CustomerId INT64,
Name STRING(MAX),
) PRIMARY KEY(CustomerId);

— 子テーブル(物理的に親と同一スプリット近傍に配置される)
CREATE TABLE Orders (
CustomerId INT64,
OrderId INT64,
OrderDate DATE,
) PRIMARY KEY(CustomerId, OrderId),
INTERLEAVE IN PARENT Customers ON DELETE CASCADE;

この設計であれば、`Customers` と `Orders` の結合クエリの物理プランは、ネットワークI/Oを極限まで削ぎ落とした最速のツリーになる。

2. オプティマイザバージョンの固定と管理

Cloud Spannerは、クエリコンパイラとオプティマイザを継続的に進化させている。まれに、オプティマイザのバージョンアップによって「昨日まで最速だったプランが、別のプランに変わって性能が落ちた」というケースが起こり得る。
本番環境のクリティカルなクエリでは、セッションレベルまたはデータベースレベルでオプティマイザのバージョンを明示的に指定・ロックし、予期せぬプラン変化を防ぐガバナンスを効かせること。

— セッションごとにオプティマイザバージョンを固定する例
SET OPTIMIZER_VERSION = ‘latest’; — または特定のバージョン番号

—

6. まとめ:コンパイラと対話せよ

Cloud Spannerのクエリコンパイルパイプラインは、単なるコード変換機ではない。あなたの書いたSQLの意図を汲み取り、ペタバイトの海から最小限のコストでデータを回収するための「最高峰の分散参謀」である。

遅いクエリに出会ったとき、文句を言う前に `EXPLAIN`(または `EXPLAIN ANALYZE`)を叩け。
物理プランのツリーを見ろ。

  • 意図しないフルスキャン(Full Scan)になっていないか?
  • 不要なリモート呼び出し(Distributed Union)が発生していないか?
  • インターリーブの恩恵をドブに捨てるような結合をしていないか?

コンパイラの思考プロセスを完全に理解した者だけが、Cloud Spannerを真に使いこなすことができる。次の設計レビューでは、君たちの洗練された物理プランを見せてもらう。期待しているぞ。

コメント

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