第4章 パフォーマンス最適化・クエリ・変換(Performance & Transformation, 21%)
🎯 この節の学習目標
クエリが遅いとき、当てずっぽうでウェアハウスを大きくするのは正しいアプローチではありません。Snowflake には、実行済み(または実行中)のクエリが内部でどのように処理されたかを可視化する Query Profile(クエリプロファイル)があります。Snowsight の Monitoring › Query History(クエリ履歴)から対象クエリを選び、「Query Profile」タブを開くと確認できます。
Query Profile には主に次の情報が表示されます。
| 表示要素 | 内容 |
|---|---|
| 演算子ツリー(Operator Tree) | TableScan、Filter、Join、Aggregate、Sort、Result などの演算子(オペレーター)がどの順序で実行されたかを示すグラフ。データは下(スキャン)から上(結果)へ流れる |
| 実行時間の割合 | 各演算子がクエリ全体の実行時間の何%を占めたか。最も割合が大きいノードがボトルネックの第一候補になる |
| 統計(Statistics) | スキャンしたパーティション数、スキャンバイト数、スピル(あふれ)量、ネットワーク転送量などの詳細な数値 |
| プロファイル概要 | 処理時間の内訳(Processing、Local/Remote Disk I/O、Synchronization など) |
📝 試験のポイント
試験では「クエリのパフォーマンス問題を調査するには何を使うか」という形で問われます。個々のクエリの内部動作を演算子レベルで診断するのは Query Profile、多数のクエリを横断的に検索・集計するのは QUERY_HISTORY、という役割分担を押さえておきましょう。
統計欄の Partitions scanned(スキャンしたパーティション数)と Partitions total(テーブルの総パーティション数)の比率は、1-6 で学んだパーティションプルーニングがどれだけ効いているかを直接示します。
💡 具体例:プルーニングが効かない書き方
-- 効きにくい:フィルタ列を関数で加工すると、min/max メタデータで絞り込めない
SELECT * FROM sales
WHERE TO_CHAR(order_date, 'YYYY-MM') = '2026-08';
-- 効きやすい:列はそのまま、範囲条件で書く
SELECT * FROM sales
WHERE order_date >= '2026-08-01' AND order_date < '2026-09-01';
Query Profile で TableScan ノードの Partitions scanned / total を見比べれば、書き換えの効果を数値で確認できます。
ソートや大きな集計・結合の途中結果がウェアハウスのメモリに収まらないと、Snowflake はデータを一時的にディスクへ書き出します。これをスピル(spilling)と呼び、統計欄に次の2段階で表示されます。
| 統計項目 | 意味 | 深刻度 |
|---|---|---|
| Bytes spilled to local storage | メモリに収まらず、ウェアハウスノードのローカルディスクへ書き出した量 | 性能低下のサイン |
| Bytes spilled to remote storage | ローカルディスクにも収まらず、リモートのクラウドストレージへ書き出した量 | より深刻。大幅な性能低下 |
スピルの根本原因は「処理データ量に対してメモリが不足している」ことなので、対策の基本はウェアハウスサイズを1段階上げる(スケールアップ)ことです。サイズを上げるとノード数とともに総メモリ量が増えるため、スピルが解消してかえって実行時間が短くなり、課金時間の増加を相殺できる場合もあります。あわせて、不要な列を SELECT しない、早い段階で行を絞り込むといったクエリ自体の見直しも有効です。
Join ノードの出力行数が入力行数より大幅に多い場合、結合条件の不足や誤りにより行が掛け算的に膨らんでいる可能性があります。これは Exploding join と呼ばれる典型的なアンチパターンで、結合条件を書き忘れたデカルト積(Cartesian product)が極端な例です。
-- 結合条件が欠けており、行数が m × n に爆発する
SELECT *
FROM orders o
JOIN customers c; -- ON 句がない!
-- 正しい結合条件を指定する
SELECT *
FROM orders o
JOIN customers c
ON o.customer_id = c.customer_id;
Query Profile では、Join ノードの入力行数と出力行数を見比べることで発見できます。対策はウェアハウスの増強ではなく、結合条件(結合キー)の見直しです。重複キーによる意図しない多対多結合も同じ症状を引き起こします。
クエリ履歴やプロファイル概要で Queued(キュー待ち)時間が長い場合、クエリ自体は悪くなく、ウェアハウスが過負荷で実行開始を待たされています。同時実行数が多すぎるのが原因なので、対策はサイズアップではなくマルチクラスタウェアハウスによるスケールアウトや、ワークロードを別ウェアハウスに分離することです(詳細は 4-3)。
また、演算子ツリーで TableScan が実行時間の大半を占める場合は、読み取りデータ量そのものが支配的です。フィルタ条件の追加・SELECT する列の削減・クラスタリングの改善など、スキャン量を減らす対策を検討します。
ここまでのシグナルを「症状から対策を引く」形に整理します。この対応付けは実務でもそのまま使えます。
| 症状(Query Profileの所見) | 診断 | 主な対策 |
|---|---|---|
| Bytes spilled to local/remote storage が大きい | メモリ不足 | ウェアハウスサイズを上げる(スケールアップ)、クエリの見直し |
| Queued 時間が長い | ウェアハウス過負荷(同時実行が多い) | マルチクラスタでスケールアウト、ワークロードの別WH分離 |
| Partitions scanned ≒ total | プルーニングが効いていない | クラスタリングキーの検討、フィルタの書き方修正 |
| Join の出力行数が異常に多い | Exploding join(結合条件の不足・重複キー) | 結合条件の修正、データの重複排除 |
| TableScan が実行時間の大半 | 読み取りデータ量が支配的 | フィルタ・列の絞り込み、クラスタリング、検索最適化などの検討(4-4) |
図:症状 → 診断 → 対策の分岐。症状ごとに対策が異なるため、まずプロファイルで診断する
個別クエリの深掘りは Query Profile が担当しますが、「昨日遅かったクエリを一覧したい」「アプリ別に実行状況を集計したい」といった横断調査には QUERY_HISTORY を使います。同名で2系統ある点に注意してください。
| INFORMATION_SCHEMA.QUERY_HISTORY(テーブル関数) | ACCOUNT_USAGE.QUERY_HISTORY(ビュー) | |
|---|---|---|
| 保持期間 | 直近7日間 | 365日間 |
| 遅延 | ほぼリアルタイム | 最大45分程度の遅延あり |
| 用途 | 直近のクエリの即時調査 | 長期のトレンド分析・監査 |
💡 具体例:クエリタグで対象を絞り込む
-- セッションにクエリタグを設定しておく(アプリ名やバッチ名など)
ALTER SESSION SET QUERY_TAG = 'nightly_etl';
-- タグを手がかりに、実行時間の長い順に調査する
SELECT query_id, query_text, total_elapsed_time, warehouse_name
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE query_tag = 'nightly_etl'
ORDER BY total_elapsed_time DESC
LIMIT 10;
QUERY_TAG はセッションパラメータで、設定しておくとそのセッションのクエリ履歴にタグが記録されます。バッチ処理やアプリケーションごとにタグを付けておくと、後からの調査・コスト配賦が容易になります。
✅ この節のまとめ
問1. Query Profile の統計に「Bytes spilled to remote storage」が大量に記録されていた。最も適切な対策はどれか。
正解:B
スピルは処理途中のデータがメモリに収まらないことが原因なので、総メモリ量を増やすスケールアップ(サイズ変更)が基本対策です。Aのスケールアウトは同時実行数の問題(キュー待ち)への対策であり、1つのクエリのメモリ不足は解決しません。Cの結果キャッシュはスピルと無関係です。Dはコスト管理の設定であり、性能改善にはつながりません。
問2. あるクエリの Query Profile を確認したところ、WHERE 句で日付を絞り込んでいるにもかかわらず「Partitions scanned」が「Partitions total」とほぼ同じ値だった。この所見が示すことと対策として最も適切なものはどれか。
正解:B
scanned ≒ total は、フィルタがあるのにほぼ全マイクロパーティションを読んでいる=プルーニングが効いていない状態です。フィルタ列に沿ってデータが並ぶようクラスタリングキーを検討したり、フィルタ列を関数で加工しない書き方に直すことが対策になります。Aの結果キャッシュはスキャン量の問題とは別軸です。Cはキュー待ちが長い場合、Dはスピルが出ている場合の対策です。
問3. ピーク時間帯に多数のユーザーのクエリで「Queued」の時間が長くなっている。クエリ個々の実行時間は短い。最も適切な対策はどれか。
正解:B
キュー待ちは同時実行数がウェアハウスの処理能力を超えているサインなので、クラスタ数を増やして同時実行を捌くスケールアウト(マルチクラスタ)が適切です。Aのサイズアップは個々の重いクエリを速くする手段で、同時実行の詰まりの解決策としては適切ではありません。C・Dはクエリ単体の工夫であり、キュー待ちの根本原因(同時実行の過多)に対処できません。
問4. QUERY_HISTORY に関する説明として正しいものを2つ選べ。
正解:A、B
2系統の QUERY_HISTORY の違い(7日・即時 vs 365日・遅延あり)はそのとおりです。Cの演算子ツリーのグラフィカル表示は Query Profile の機能であり、QUERY_HISTORY はクエリのメタデータ(実行時間・状態・タグなど)を行として返します。Dは誤りで、QUERY_TAG はセッションに設定するパラメータで、設定した値はクエリ履歴の query_tag 列で参照・絞り込みできます。