目次
※ 本検証は 等価条件・IN 条件 を題材としたものであり、
すべてのクエリパターンに当てはまるものではありません。
1. はじめに
Snowflakeは クエリが速い一方で、コストが分かりにくい と感じられることが多いです。
(同じように見えるSQLでも、実行後の スキャン量 や 課金 が想定より増減することがあります)
本記事では、Snowflakeのコストが 何によって増減するのか を、
Query Profile で検証します。
今回の検証で比較するもの
- 同じSQL
- 同じデータ
- 違うのは 「物理配置」だけ
👉 それでも スキャン量・実行時間・コスト に差が出るのかを確認します。
Snowflakeでは、一般的なRDBの「インデックス有無」ではなく、
データがどう配置されているか(クラスタリング / マイクロパーティション) が
スキャン量や処理効率に影響しやすい、という前提で話を進めます。
2. Snowflakeのコスト構造
Snowflakeのコストは、大きく 次の2つ に分かれます。
① ストレージコスト
(データを 置いておくだけ の料金)
- データを保存している量に対する料金
- 圧縮後サイズ が課金基準
- クエリを実行しても 基本的に増えない
Snowflakeは高い圧縮率を前提としており、
データ量が増えてもストレージコストは比較的読みやすい設計です。
② コンピュートコスト(Virtual Warehouse)
(動かした分だけ 発生する料金)
- クエリを実行している 間だけ 発生
- Virtual Warehouse(計算用エンジン) の使用料
- Warehouseサイズが大きいほど 時間単価が高い
👉 今回の検証では、この「コンピュートコスト」に注目します
コンピュートコストの考え方
コンピュートコストは、ざっくり以下の関係で決まります。
コンピュートコスト ≒ Warehouseサイズ × 実行時間
-
Warehouseサイズ
→ 計算機の「大きさ・馬力」 -
実行時間
→ 計算機を「動かしていた時間」
実行時間を決める最大要因
スキャン量(Bytes scanned)
- どれだけデータを読んだか
- 読む量が多いほど、実行時間は長くなる
プルーニングの効き具合
- 不要なデータを 最初から読まない 仕組み
- 効くほど スキャン量が減る
- 効かないと ほぼ全データを読む
Snowflakeでは
「同じSQLでも、どれだけスキャンしたか」=コストに直結 します。
この違いを生む要因が、後述する 物理配置 です。
3. Query Profileとは何か
Query Profileとは
Query Profile とは、クエリ実行後に確認できる
実行結果の可視化(プロファイリング)画面 です。
- クエリが どの処理を通り
- どこで時間やリソースを使ったか を
- 実測ベースで確認できます
Snowflakeにおける位置づけ
Snowflakeでは、
一般的なRDBのように 「事前に作られる実行計画」 はあまり重視されません。
- クエリ実行前に最適な計画を固定する、という考え方ではない
- 実行後の結果(実測) をもとに挙動を確認する
👉 この役割を担うのが Query Profile
👉 Query Profile が Snowflake における「実行計画」に相当
Query Profileで分かること
Query Profileを見ることで、以下が確認できます。
- クエリ全体の 実行時間
- 各処理(Scan / Join / Aggregate など)の 内訳
- データの読み取り量 や 処理負荷
Snowflakeでは
「どう実行されたか」を実測で理解する ことが重要です。
Query Profileで見るポイント
Table Scan
-
Partitions scanned / total
- どれだけのパーティションを読んだか
-
Bytes scanned
- 実際に読み取ったデータ量
👉 スキャン量が、実行時間・コストに直結
各処理の実行時間
- Scan / Join / Aggregate など
- どの処理で時間を使ったか を確認
👉 コスト増加の原因を特定できる
「SQLが同じ」でも、
Scan量や処理配分が違えば、コストは変わる
これを可視化できるのが Query Profile です。
4. なぜ等価条件でコスト差が出るのか
Snowflakeは「行」ではなく「micro-partition」でデータを読む
Snowflakeでは、
RDBのように 1行ずつ条件判定をしながら読む 仕組みではありません。
- データは自動的に
micro-partition と呼ばれる 物理的なデータの塊 に分割されて保存される - クエリ実行時は
micro-partition 単位 で「読む/読まない」を判断する
micro-partition が持つメタデータ
各 micro-partition には、次のような 要約情報(メタデータ) が保持されています。
- 最小値(MIN)
- 最大値(MAX)
- NULL の有無 など
👉 実データを読まずに、中身の傾向を把握できる
読む/読まないの判断基準(プルーニング)
クエリの条件を見て、
- 「この partition に、条件の値が 入り得るか?」
を判定します。
-
入り得ない と判断された partition は
👉 最初から読み飛ばされる(プルーニング)
ポイント
行を見に行く前に、
不要なデータを除外できる のが Snowflake の特徴です。
等価条件でも差が出る理由
同じ 等価条件(= や IN) であっても、
値の分布状態 によってプルーニングの効き方が変わります。
パターン①:値がまとまっている(プルーニングが効く)
- 同じ値が 限られた partition に集中
- 条件に合わない partition を 丸ごとスキップ
👉 読む partition が少ない
👉 スキャン量が小さい
👉 コストが低い
パターン②:値が散らばっている(プルーニングが効かない)
- 同じ値が 多くの partition に分散
- 多くの partition が
「条件に合う 可能性あり」と判定される
👉 ほぼ全 partition を読む
👉 スキャン量が大きい
👉 コストが高い
重要なまとめ
SQLが同じ・条件も同じ でも、
micro-partition 上の物理配置が違えば、スキャン量が変わる
= 実行時間・コストが変わる
次の章では、
👉 この差を 実際に Query Profile で確認する検証 を行います。
5. 検証
検証の目的
同じ 等価条件 であっても、 物理配置の違い によって
- スキャン量
- 実行時間
- コスト
に差が出るかを確認します。
検証の前提条件
- 同じSQL
-
同じデータ
- 件数
- 値の分布
- 同じWarehouseサイズ
- 違うのは 「物理配置」だけ
👉 差が出た場合、
原因はプルーニング効率の違い と説明できます。
検証方法と見るポイント
検証方法(概要)
- 同じ元データから
配置の異なる2つのテーブル を作成- 値がまとまる配置
- 値が散らばる配置
- 同じ 等価条件クエリ を実行
- Query Profile を比較
検証に使用するデータ量
- 行数:数百万行(約 300万行)
- micro-partition 数:16
- データサイズ:数 MB 程度
- 列構成:
- 等価条件に使用する列:
category(アルファベット 26種類) - 日付列:
created_at - ペイロード列:
payload(文字列・サイズ調整用)
- 等価条件に使用する列:
検証用テーブルの構成(概要)
- 同じ元データから作成した 2つのテーブル
- データ内容・件数・分布は完全に同一
- 違いは データの物理的な並び順のみ
テーブル構成
-
T_ORDERED
-
category列で並び替えて作成 - 同じ値が近くに配置される
-
-
T_RANDOM
- ランダム順で作成
- 同じ値が全体に散らばる
検証に使用するクエリ
① 等価条件(単一値)
- 等価条件(
=)を使用 - 返却行数の影響を避けるため
COUNT(*) - キャッシュの影響を避けて実行
SELECT COUNT(*) FROM T_ORDERED WHERE category = 'A';
SELECT COUNT(*) FROM T_RANDOM WHERE category = 'A';
② 等価条件(複数値 / IN)
- 等価条件の 複数値指定(IN)
- 対象データ範囲を 意図的に拡張
- プルーニング効率が どのように変化するか を確認
- キャッシュの影響を避けて実行
SELECT COUNT(*) FROM T_ORDERED WHERE category IN ('A','B','C','D','E');
SELECT COUNT(*) FROM T_RANDOM WHERE category IN ('A','B','C','D','E');
単一値(=)と比べて
「条件に合致する可能性のある範囲」 が広がるため、
micro-partition の判定精度がどの程度変わるかを確認できます。
クリックして展開(検証用SQL一式)
-- 検証テーブルの元となるデータ
CREATE OR REPLACE TABLE BASE_DATA AS
WITH gen AS (
SELECT
SEQ4() AS id
FROM TABLE(GENERATOR(ROWCOUNT => 3000000))
)
SELECT
id,
CHR(65 + MOD(id, 26)) AS category,
DATEADD('day', MOD(id, 365), '2024-01-01'::DATE) AS created_at,
RPAD(TO_VARCHAR(id), 200, 'x') AS payload
FROM gen;
-- ① 値がまとまる配置(categoryでまとめる)
CREATE OR REPLACE TABLE T_ORDERED AS
SELECT * FROM BASE_DATA
ORDER BY category;
-- ② 値が散らばる配置(ランダム)
CREATE OR REPLACE TABLE T_RANDOM AS
SELECT * FROM BASE_DATA
ORDER BY RANDOM();
-- キャッシュ無効(必要に応じて)
-- ALTER SESSION SET USE_CACHED_RESULT = FALSE;
-- 検証クエリ(等価条件)
SELECT COUNT(*) FROM T_ORDERED WHERE category = 'A';
SELECT COUNT(*) FROM T_RANDOM WHERE category = 'A';
-- 検証クエリ(等価条件・複数値)
SELECT COUNT(*) FROM T_ORDERED WHERE category IN ('A','B','C','D','E');
SELECT COUNT(*) FROM T_RANDOM WHERE IN ('A','B','C','D','E');
検証結果(等価条件)
- 順番どおり
SELECT COUNT(*) FROM T_ORDERED WHERE category = 'A';
- ランダム
SELECT COUNT(*) FROM T_RANDOM WHERE category = 'A';
Query Profile(ORDERED)
観察結果
-
Partitions scanned:
1 / 16 -
Bytes scanned:
0.02 MB - 条件値
category = 'A'は
特定の micro-partition に集中 している
考察
- micro-partition が持つ MIN / MAX メタデータ により
多くの partition が 事前にスキップ された - 物理的に値がまとまっている場合、
等価条件でも高いプルーニング効果 が得られる
Query Profile(RANDOM)
観察結果
-
Partitions scanned:
16 / 16 -
Bytes scanned:
1.76 MB - 同じ値が 全体に散らばっている
考察
- 各 micro-partition に
category = 'A'が 含まれる可能性あり と判定される - MIN / MAX 判定で除外できず、
全 partition をスキャン する結果となった
📌 まとめ(等価条件の検証結果)
- 同じ等価条件
- 同じデータ件数
- 同じWarehouseサイズ
であっても、
👉 物理配置の違い により
👉 読まれる micro-partition 数が大きく変わる
結果として、
- スキャン量
- 実行時間
- コンピュートコスト
に 明確な差が生じる ことが確認できた。
検証結果(等価条件・複数値 / IN)
- 順番どおり
SELECT COUNT(*) FROM T_ORDERED WHERE category IN ('A','B','C','D','E');
- ランダム
SELECT COUNT(*) FROM T_RANDOM WHERE IN ('A','B','C','D','E');
Query Profile(ORDERED)
観察結果
-
Partitions scanned:
4 / 16 -
Bytes scanned:
0.15 MB -
対象行数:約
576.9k
考察
-
categoryが 物理的にまとまって配置 されているため、
IN 条件でも必要な micro-partition のみをスキャン - 単一値(
= 'A')と比べるとスキャン範囲は増えるが、
全体スキャンにはならない
👉 条件が広がっても、プルーニングは段階的に効く
Query Profile(RANDOM)
観察結果
-
Partitions scanned:
16 / 16 -
Bytes scanned:
1.76 MB -
対象行数:約
576.9k
考察
- 複数値が 全体に散らばっている ため、
すべての micro-partition に 該当する可能性あり - MIN / MAX メタデータで除外できず、
全 partition をスキャン
👉 IN 条件により、差がさらに明確化
📌 まとめ(IN 条件の検証結果)
-
IN 条件で対象が増えても
- 物理的にまとまっていれば
👉 プルーニングは段階的に効く - 値が散らばっている場合
👉 最初から全スキャンになる
- 物理的にまとまっていれば
結果として、
等価条件・複数値指定では、物理配置の影響がより顕著に表れる
6. 実務でどう生かすか
よく使う絞り込み条件を意識する
実務では、次のような 頻繁に使う絞り込み条件 をまず意識します。
- 等価条件・IN 条件でよく使う列
- 種別
- ステータス
- 日付 など
これらの列が
物理的に近くに配置されているかどうか で、
micro-partition プルーニングの効き方が大きく変わります。
👉 「WHERE 句で絞れる」よりも
👉 「同じ値が近くに置かれているか」 が効く
Query Profileで「時間ではなく読む量」を見る
実行時間は、次の要因で ブレやすい 指標です。
- キャッシュの有無
- Warehouse の混雑状況
- 同時実行数
そのため実務では、
時間よりも以下を優先して確認 します。
- Partitions scanned
- Bytes scanned
同じ結果・同じSQLでも、
- 読む量が少ない
- 読む量が多い
では、将来のコスト差 につながります。
👉 速さより「どれだけ読んだか」
「今は速いからOK」は危険
データ量が少ないうちは、
物理配置の差が 表に出にくい ことがあります。
しかし将来、データが増えると…
- 無駄な 全スキャン
- クレジット消費の増加
- 並列実行時の Warehouse 競合
といった問題に発展しやすくなります。
👉 Query Profile を見て、早めに気づけるか が重要
- SQL の書き方だけでなく
- 「データがどう置かれているか」
- 「実際にどれだけ読んでいるか」
を Query Profile で確認することで、
将来のコスト増加を未然に防げる。
7. まとめ
今回分かったこと
-
同じ等価条件・同じデータ であっても、
物理配置の違い によって
👉 読まれる micro-partition 数が大きく変わる - 読む量(Partitions / Bytes)が変わることで、
👉 将来的なコスト差 につながる
Snowflakeらしい見方
- Snowflakeでは
実行時間よりも「どれだけ読んだか」 が重要 - その判断は、
Query Profile を見ることで説明できる
実務への持ち帰り
- よく使う 等価条件・IN 条件 が
効きやすい物理配置 になっているかを意識する -
「今は速いからOK」 にせず、
Query Profile で読む量を確認 する
📌 最後に
Snowflakeでは、Query Profile を通して
「読む量」を意識することが、
コスト最適化の第一歩。



