【実務・中級編】 Query Insights – Cloud Spanner

こんにちは。Cloud Spannerのテックリードとして、日々数千億行規模のトランザクションをさばくシステムの設計・運用に立ち会っている者だ。

コードレビューや設計レビューの場で、こんな会話をしたことはないだろうか。

  • 「なんか最近、特定のAPIのレイテンシが悪化しているんだけど……」
  • 「どのクエリがCPUを食いつぶしているのか、Cloud Monitoringのグラフじゃわからないよ」
  • 「とりあえず `EXPLAIN` 引いてみたけど、この実行計画のどこがボトルネックなのか判断がつかない」

分散リレーショナルデータベースであるCloud Spannerは、適切に設計すれば無限に近いスケーラビリティを発揮する。しかし、「適当に書いたクエリ」や「アンチパターンなスキーマ設計」は、容赦なくSpannerのCPUを焼き尽くす。

ここで登場するのが Query Insights だ。
単なる「クエリのログビューア」だと思っていないなら、今すぐその認識を改めよう。Query Insightsは、分散環境におけるクエリの「どこが詰まっているのか」を可視化し、外科手術のようなパフォーマンスチューニングを可能にする最強の武器だ。

今回は、実務でQuery Insightsをどう使い倒し、高負荷クエリを炙り出し、いかにして堅牢なクエリ設計に昇華させるか、私の知見のすべてを伝授しよう。

—

1. Query Insightsとは何か?(なぜ従来の監視では不十分なのか)

従来のRDBであれば、スロークエリログを有効化し、実行時間が長いものをピックアップすればそれで済んだ。しかし、Cloud Spannerは完全分散アーキテクチャである。

1つのクエリが裏側で数百のスプリット(データシャード)に並行分散され、それぞれのノードでCPUやストレージI/Oを消費してマージされる。つまり、「単に実行時間が長い」だけでなく、「どのノードで、どれだけのCPUサイクルを消費し、どれだけのバイト数をスキャンしたか」を追跡しなければ、真のボトルネックには辿り着かない。

Query Insightsは、以下のメトリクスを正規化されたクエリパターンごとに自動集計し、ダッシュボード上で視覚化してくれる。

  • CPU使用量 (CPU Time): クエリの実行にどれだけCPUコアが割かれたか。
  • 待機時間 (Lock Wait Time / Latency): ロック待ちやRPCのオーバーヘッドでどれだけ足止めを食ったか。
  • スキャンバイト数 (Bytes Scanned): 不要なデータをどれだけ読み込んでしまったか(フルスキャン検知の要)。
  • サンプリングされた実行計画 (Execution Plan): そのクエリが実際にどういうプランで動いたか。

特筆すべきは、本番環境で常時有効にしておいてもオーバーヘッドが極めて低い(1%未満)点だ。躊躇せずに本番環境で有効化すべきである。

—

2. 【実践】Query Insightsで「致命傷」を特定する手順

実務でパフォーマンスインシデントが発生した際、私がテックリードとしてチームに指示する調査フローはこうだ。

Step 1: GCPコンソールで「CPU Consumer」を特定する

Cloud Spannerの「Query Insights」タブを開き、ソート順を 「CPU Time (Total)」 または 「CPU Time (Average)」 に設定する。
ここで上位に居座っているクエリこそが、あなたのシステムの財布(CPUリソース)を勝手に空にしている犯人だ。

Step 2: 正規化されたクエリ(Normalized Query)の確認

Query Insightsは、動的なパラメータ(`WHERE id = 123` の `123` など)をプレースホルダー(`?`)に正規化して集計してくれる。
ここで、以下のようなクエリを発見したとする。

— Query Insightsで上位に現れた「怪しいクエリ」のパターン
SELECT
FROM Users@{FORCE_INDEX=UsersByEmail}
@{SST=true}
— ※実際にはプレースホルダー化されている
WHERE Email = @p1 AND Status = @p2

この時点で、「おや、なぜインデックスヒントを使っているんだ?」「ステータスとメールアドレスの複合インデックスが効いていないのか?」という仮説が立つ。

Step 3: サンプリングされた実行計画(Execution Plan)を覗き見する

Query Insightsの優れている点は、そのクエリが実行された実際の実行計画のサンプルを保持していることだ。
ここで見るべきポイントは以下の2点に絞られる。

1. Distributed Union / Distributed Cross Apply の有無:
意図せず全スプリットを舐めに行く(Scatter Read)プランになっていないか。
2. Rows Scanned と Rows Returned の乖離:
100万行スキャンした結果、アプリケーションに返したのがたったの1行だった場合、それは「最悪なインデックス設計(あるいはインデックス不全)」を意味する。

—

3. 現場で遭遇した「ヤバいクエリ」と、その処方箋

では、実際にQuery Insightsが検出し、私のレビューで修正させた具体的なアンチパターンと、堅牢な設計パターンを見ていこう。

アンチパターン①:インターリーブ階層を無視したアドホックな結合

親テーブルと子テーブルの関係(Interleaved Tables)を無視して、親IDを知るために無駄な `JOIN` や `SELECT` を繰り返しているケース。

— 【NG】Query InsightsでCPUが高騰する典型例
— パレントキー(CustomerID)をWHERE句に入れず、グローバルに検索している
SELECT
FROM Orders
WHERE OrderDate > @p1 AND Status = ‘PENDING’;

【なぜNGか】
`Orders` テーブルが `Customers` の子テーブルとしてインターリーブされている場合、`CustomerID`(親の主キー)がプレフィックスとして含まれていないクエリは、すべてのスプリットへのScatter Read(全ノードへの並行問い合わせ)を引き起こす。データ量が増えるにつれて、CPU使用率が天井知らずに跳ね上がる。

【堅牢な設計パターン(修正後)】
必ずインターリーブのプレフィックスを含める、あるいは適切なセカンダリインデックス(ターゲティングインデックス)を張る。

— 【OK】インターリーブの階層構造を意識したクエリ
— もしくは、Statusで効率よく絞り込めるインターリーブインデックスを使用する
SELECT
FROM Orders@{FORCE_INDEX=OrdersByStatusAndDate}
WHERE Status = @p1 AND OrderDate > @p2;

—

アンチパターン②:巨大な `IN` 句によるロック競合とCPU肥大化

アプリケーション側で数千件のIDを配列に詰め込み、そのままSpannerに投げつけるコード。

— 【NG】数千件のIDを配列で渡す
SELECT ItemID, Price, Stock
FROM Inventory
WHERE ItemID UNNEST(@item_ids);

(※ `UNNEST` 自体は強力だが、配列の要素数が数千〜数万に達するとプランニングコストとメモリが爆発する)

【なぜNGか】
Query Insightsで見ると、この手のクエリは「CPU Time」だけでなく、「Lock Wait Time」も同時に跳ね上がることが多い。大量の行を一括ロックしようとしてトランザクションの競合(Contention)が発生している証拠だ。

【堅牢な設計パターン(修正後)】
アプリケーション側でチャンク(例えば100件ずつなど)に分割し、非同期または並行で処理するか、ストリーミング読み取り(Partitioned Query)を検討する。トランザクション内であれば、1バッチあたりのキー数を厳しく制限するコードレビューのルールを設けることだ。

—

4. Query Insightsを組み込んだ「攻めの運用」設計

Query Insightsを「障害が起きたときに見るツール」にしているうちは、まだ二流だ。一流のエンジニアは、これをCI/CDパイプラインや定常的なパフォーマンス監視のガードレールとして組み込む。

1. リリース後の「1時間後チェック」の義務化:
新しいマイクロサービスや新機能の本番リリース直後は、必ずQuery Insightsを開き、「予期せぬ新規クエリがCPUランキングのトップに入っていないか」を目視確認する。
2. アラート連携:
Cloud Monitoringを介して、特定の高負荷クエリパターンや、Spanner全体のCPU使用率(高負荷が続く状態)をSlackやPagerDutyに飛ばす。その際の原因切り分けの第一歩として、Query InsightsのURLをアラート通知に含めると、インシデント対応時間が劇的に短縮される。
3. スキーマ変更(DDL)時のレビュー基準:
新しいインデックスを追加する際は、必ずQuery Insights上で「既存のどの高負荷クエリのプランがどう変わるか」を検証環境でシミュレーションする。インデックスは「とりあえず張っておけ」では絶対に痛い目を見る。

—

最後に:Spannerのポテンシャルを引き出すのは君の設計だ

Cloud Spannerは魔法のデータベースではない。どれほど強固なGoogleの分散インフラストラクチャの上にあろうとも、発行されるSQLが稚拙であれば、その真価を発揮することはできない。

Query Insightsは、あなたの書いたSQLの「通信簿」であり、同時に「未来のボトルネックを予言する水晶玉」だ。
今日のコードレビューから、感覚的な「たぶん大丈夫」を捨て、Query Insightsの冷徹なデータに基づいたロジカルな議論にシフトしてほしい。

君が設計したシステムが、どれほどのトラフィックが来ても涼しい顔をしてスケールし続ける姿を、私は楽しみにしている。

コメント

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