EP.1 で触れたとおり、 には利用者が作る索引がありません。代わりに効くのが と です。
この回は、効く条件と効かない条件、そして を設定すべきかの判断を扱います。性能問題の相当部分がここに帰着します。
1. 読み飛ばしの仕組み
データは自動的に区画へ分割され、各区画には含まれる値の範囲が記録されています。検索条件と突き合わせて、範囲外の区画は読まない。これだけです。
重要なのは、この効きが「データの並び順」に依存するという点です。同じ検索条件でも、データがどう配置されているかで読む量が変わります。
| 配置 | 日付で絞ったとき | 読み飛ばし |
|---|---|---|
| 日付順に並んでいる | 該当区画は連続して存在 | 大きく効く |
| ばらばらに散っている | 全区画に少しずつ含まれる | ほぼ効かない |
下の行が問題です。どの区画にも該当データが少しずつ入っていると、結局すべて読むことになります。区画の情報だけでは「含まれていない」と判断できないためです。1件だけ含まれている区画も、1万件含まれている区画も、読む必要があるという点では同じです。この「少しだけ含まれている」状態が積み重なると、実質的に全件走査になります。
特に設定しなければ、取り込んだ順にデータが配置されます。日次で追加していれば日付順に並ぶため、日付での絞り込みは自然に効きます。多くの現場でクラスタリングキーが不要なのは、この既定の挙動が十分機能しているからです。
2. 効いているかを確認する
判断は必ず実測からです。クエリ履歴に読んだ区画数と全区画数が記録されています。
SELECT LEFT(query_text, 70) AS クエリ, partitions_scanned AS 読んだ区画, partitions_total AS 全区画, ROUND(100.0 * partitions_scanned / NULLIF(partitions_total, 0), 1) AS 読んだ割合, rows_produced AS 返した行, ROUND(bytes_scanned / POWER(1024, 3), 2) AS 読取GB, ROUND(total_elapsed_time / 1000, 1) AS 秒FROM snowflake.account_usage.query_historyWHERE start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP()) AND partitions_total > 100 -- 小さい表は対象外 AND rows_produced < 100000 -- 返す行は少ないのに AND partitions_scanned > partitions_total * 0.8 -- ほぼ全部読んでいるORDER BY bytes_scanned DESCLIMIT 20;条件が要点です。返す行は少ないのに、ほぼ全区画を読んでいるものを探しています。これが改善余地の大きいクエリです。逆に、全区画を読んでいても返す行が多いなら、それは正当な全件処理です。
3. 効かなくなる書き方
並び順が良くても、書き方によって読み飛ばしが無効になります。これは索引の話(パフォーマンスチューニング実践 EP.03)と同じ構造です。
-- 効かない: 列に関数を適用しているSELECT * FROM ordersWHERE TO_DATE(created_at) = '2026-10-01';
-- 効く: 列はそのまま、範囲で指定するSELECT * FROM ordersWHERE created_at >= '2026-10-01' AND created_at < '2026-10-02';
-- 効きにくい: 前方一致でない文字列検索SELECT * FROM customers WHERE name LIKE '%商店%';
-- 効く: 前方一致なら範囲として扱えるSELECT * FROM customers WHERE name LIKE '田中%';原則は単純で、区画に記録された「値の範囲」と比較できる形かどうかです。列を加工すると、その値がどの範囲に入るか判断できなくなり、全部読むしかなくなります。
| 条件の書き方 | 読み飛ばし | 理由 |
|---|---|---|
| `col >= X AND col < Y` | 効く | 範囲として比較できる |
| `col = X` | 効く | 範囲に含まれるか判断できる |
| `col IN (X, Y, Z)` | 効く | それぞれ判断できる |
| `FUNC(col) = X` | 効かない | 加工後の値の範囲は不明 |
| `col LIKE '%X%'` | 効かない | 先頭が不定で範囲にならない |
| `col != X` | 効かない | ほぼ全区画が該当しうる |
4. クラスタリングキーを設定すべきか
並び順を意図的に保ちたい場合に使うのが クラスタリングキー です。ただし維持のための処理が継続的に発生し、その分の費用がかかります。設定は慎重に判断します。
- 1表が十分大きいか — 小さい表なら全部読んでも一瞬。効果がない
- 2その列で絞る検索が繰り返されるか — 一度きりの調査のためには設定しない
- 3現状の並び順で効いていないか — 取り込み順で足りているなら不要
- 4更新の頻度は高すぎないか — 頻繁に書き換わると維持費用が嵩む
- 5効果は維持費用を上回るか — 実測で確認する
3番目で止まることが非常に多いです。日付で絞る検索なら、取り込み順で既に効いています。あえて設定する必要があるのは、取り込み順とは別の軸で絞る場合 ── 例えば顧客IDで絞る検索が主体、といったケースです。この判断を飛ばして設定すると、効果がないのに維持費用だけを払い続ける状態になります。しかも効いていないことに気づきにくい。
並び順を保つには、書き換わるたびに再配置が要ります。頻繁に更新される表では、維持費用が効果を食い潰します。追記が中心で、絞り込みの軸が明確な表 ── これが適した条件です。
5. 設定せずに済ませる方法
クラスタリングキー を設定する前に、同じ効果を別の方法で得られないかを検討する価値があります。
| 方法 | 内容 | 向く場面 |
|---|---|---|
| 取り込み順を整える | 投入時に並べてから入れる | 定期的にまとめて入れる場合 |
| 表を分ける | 軸ごとに別テーブルにする | 軸が少数に固定されている場合 |
| 事前に集計しておく | よく使う粒度で別表を作る | 同じ集計が繰り返される場合 |
| 絞り込み条件を見直す | そもそも読む範囲を減らす | 無条件の全件検索がある場合 |
3番目が実務では効きます。同じ集計が何度も走るなら、結果を持っておくほうが確実です。読み飛ばしの最適化より、読む必要そのものをなくすほうが効果が大きい。EP.4 で扱った が効く条件を整えるのも、同じ方向の打ち手です。
これは パフォーマンスチューニング実践 EP.03 で書いた「最も効く改善は、そのクエリを実行しないこと」と同じ発想です。
6. 設定したら効果を測る
設定したら、本当に効いたかを確認します。読んだ区画の割合が下がっているか、そして維持費用がどれだけ発生しているかの両方を見ます。
SELECT table_name AS テーブル, DATE_TRUNC('day', start_time) AS 日付, ROUND(SUM(credits_used), 3) AS 維持クレジット, SUM(num_rows_reclustered) AS 再配置行数FROM snowflake.account_usage.automatic_clustering_historyWHERE start_time >= DATEADD(day, -30, CURRENT_TIMESTAMP())GROUP BY 1, 2ORDER BY 維持クレジット DESCLIMIT 20;維持費用が、削減できた読み取り分を上回っていないかを確認します。上回っているなら外す判断になります。設定して終わりにすると、効いていないのに費用だけ払い続ける状態が残ります。
読み飛ばしの効きはデータの並び順で決まり、既定では取り込み順になる。日付での絞り込みが自然に効くのはこのため。列を加工すると効かなくなるのは索引と同じ。クラスタリングキー は取り込み順と別の軸で絞る場合に限り検討し、維持費用と効果を実測で比較する。設定前に「そもそも読まずに済ませる」選択肢も検討してください。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。