はじめに
おはこんばんにちは!データ分析基盤を構築・運用しているエンジニアです。
今回は私が最近取得した「SnowPro Core」資格の学習や、実際の現場での経験から得た「Snowflakeで安くて早いクエリ」について書くことにしました!
現場でSnowflakeを使っていても、「MySQLやOracleと同じRDB感覚」でクエリを書いていませんか?私も最近まで「基本は特に変わらないでしょ!」と思ってました。でも、実はそれ、パフォーマンス的にもコスト的にも、とても損をしている可能性があります。
Snowflakeには独自のアーキテクチャがあり、それを理解してリファクタリングするだけで、見違えるように変わります。今回は、絶対に覚えておきたいポイントだけに焦点を当てました。ぜひ最後まで読んでいってください!
現場のお客さんやチームメンバーに「こんな機能があるんですよ!」と提案して、驚く顔を見てやりましょう~
※ご紹介するいくつかの機能については、お使いの環境によって利用可否が異なります。実際に利用する場合はSnowflakeの公式ドキュメントで最新情報をご確認ください。
マイクロパーティションについて
まず、Snowflakeのパフォーマンスチューニングを理解するための大前提として、データが裏側でどのように保存されているかを確認していきましょう。
Snowflakeでは、テーブル内のデータが「マイクロパーティション」と呼ばれる小さな単位(圧縮後で概ね50〜500MB)に自動的に分割・保存されます。
実はこの仕組みこそが、Snowflakeが「安くて早い」と言われる理由の一つです。ユーザーにとって嬉しいメリットを3つに絞って紹介します!
-
完全自動管理でとにかく「楽」
データの圧縮や整理、パーティションの分割などはすべてSnowflakeがバックグラウンドで自動的に行ってくれます。ユーザーが手動でパーティション設計に頭を悩ませる必要はなく、無駄なストレージ容量も増えません。 -
インデックス不要でとにかく「早い」
従来のRDBで必須だったインデックス(索引)の作成や管理が不要です。Snowflakeはマイクロパーティションごとに「最大値・最小値」などの情報を自動で記録しているため、クエリが実行された際、必要なパーティションだけをピンポイントで読み込みます(プルーニング)。これにより、インデックスなしでも高速検索が可能になります。 -
過去に遡れる「Time Travel(タイムトラベル)」機能
マイクロパーティションは「不変」という性質を持っています。つまり、データが更新・削除されても上書きされず、一定期間の間、古いパーティションが保存され続けます。これにより、「間違えてテーブルのデータを全消ししちゃった!」という大事故が起きても、バックアップから復旧する手間なく、SQL1つで一瞬にして過去のデータにアクセスしたり、復元したりすることが可能です。
※Time Travelで参照できる期間は、オブジェクト設定やエディション等によって異なります。
なので、Snowflakeのパフォーマンスチューニングにおいては、マイクロパーティションの特性(クラスタリングなど)を活用した独自のアプローチをとる必要があるのです。
参考リンク: https://docs.snowflake.com/ja/user-guide/tables-clustering-micropartitions#what-are-micro-partitions
現状の課題を把握する
では、実際のチューニング術に入っていきましょう~
Snowflakeには、実行したクエリの裏側を丸裸にしてくれる「クエリプロファイル」という超便利機能があります。
「ここでめっちゃ時間かかってますよ〜」とか「メモリが足りなくて、処理がディスクに溢れちゃってますよ(泣)」といった分析を、Snowflakeが自動で図解してくれるんです。なので、まずはこれを見て「どこが悪いのか」を特定するのがチューニングの第一歩!
ただ、いくらグラフや図になっていても、「結局どの数字を見ればいいの?」ってなりますよね。そこで、クエリプロファイル画面で絶対に確認すべき重要指標をピックアップして解説します!
1. どこに時間がかかっているか?
-
各オペレータ(ノード)の処理時間・割合
- Query Profile上で、どのオペレータ(例:Join / Aggregate / Sort / Scan)が処理時間の大部分を占めているかを確認します。
- まず“犯人ノード”を特定するのがチューニングの出発点です。
2. 無駄なデータを読み込んでいないか?(プルーニングの確認)
-
Partitions total(全体のパーティション数) と Partitions scanned(スキャンした数)
- 前の章で説明した「マイクロパーティション」がどれだけ読み込まれたかを示します。例えば
Totalが10,000なのにScannedが10,000なら、全件検索が発生しています!逆にScannedが小さいほど、プルーニングが効いて読み取り量が減りやすく、速く/安くなりやすいサインです。
- 前の章で説明した「マイクロパーティション」がどれだけ読み込まれたかを示します。例えば
3. キャッシュを活用できているか?
-
Bytes scanned(スキャンしたバイト数)
- 実際にストレージから読み込んだデータ量です。ここが大きすぎる場合は、クエリの書き方やテーブル設計を見直すサインです。
-
Percentage scanned from cache(キャッシュからのスキャン割合)
- 一度読み込んだデータはウェアハウスのSSD(ローカルディスク)にキャッシュされます。後に記載しますが、キャッシュを活用できるかどうかがエンジニアの腕の見せ所です。「いかにこの数字を上げるか」を目指してみてください!
4. メモリ不足を起こしていないか?(スピルの確認)
- Bytes spilled to local storage(ローカルへのスピル)
-
Bytes spilled to remote storage(リモートへのスピル)
- 「スピル(Spill)」とは、処理のためのメモリが足りず、一時的にデータをディスクに退避させてしまう現象のことです。これが発生すると、クエリは劇的に遅くなります(特にリモートへのスピルは致命傷!)
- ここに数字が入っていたら「ウェアハウスのサイズ(SとかMとか)を大きくする」か、「無駄な巨大JOINやORDER BYを減らす」などの対策が必須になります。
参考リンク: https://docs.snowflake.com/ja/user-guide/ui-snowsight-activity#query-profile-reference
無駄なデータは不要!最強のプルーニング戦略
クエリプロファイルで「うわ、こんなに無駄なデータをフルスキャンしてたのか…」と絶望した皆さん、安心してください。ここからは、Snowflakeに「必要なデータだけをピンポイントで読み込ませる(プルーニングさせる)魔法の機能」をご紹介します。
これを理解して使いこなせると、クエリのチューニングがめちゃくちゃ楽しくなりますよ!!やりたい処理(ユースケース)に合わせて、以下の3つの武器を使い分けましょう。
1. クラスタリングキー(Clustering Key)
【得意なこと】日付や特定のIDによる「範囲検索」「等価検索」
テーブルのデータが大きくなってきたら、まずはコレを検討します。
日付、テナントID、地域コードなど、WHERE句やJOIN句で頻繁に絞り込みに使われる列を「クラスタリングキー」として設定します。すると、Snowflakeが裏側で自動的にマイクロパーティションを再クラスタリングしてくれます。
結果として、不要なブロックを読み込むこと(プルーニングの失敗)を最小限に抑えられます!
- プロのワンポイント: 「よし、検索しそうな列を全部キーにしちゃえ!」はNGです。列が多すぎると並べ替えの効率が落ちるため、**適切なキーの数は「1〜3個まで」**にするのが鉄則です。
2. マテリアライズドビュー(Materialized View)
【得意なこと】重たい「集計処理(SUMやCOUNTなど)」の爆速化
毎回実行するのに時間がかかる重たい集計クエリってありますよね?それを事前に計算して、結果を保存しておく仕組みです。
元のテーブルのデータが更新されると、Snowflakeが自動で再計算してくれるため、BIツールからの定常的なダッシュボード参照などを劇的に高速化できます!
- **現場での注意点:**元テーブルが頻繁に更新される(INSERT/UPDATEが激しい)環境だと、裏側で自動再計算が走りまくり、とんでもないコスト(クレジット消費)が発生する可能性があるので要注意です。
3. 検索最適化サービス(Search Optimization Service / SOS)
【得意なこと】広大な砂漠から一粒の砂を見つける「特定条件の検索」
数億〜数十億行もある巨大テーブルから、「特定の一意なID」や「少数の行」だけをピンポイントで探す(Point Lookup)クエリが遅い場合の最終兵器です。
これをONにすると、バックグラウンドで検索用の特殊な経路(インデックスのようなもの)を作成し、目的のデータへ直接ジャンプできるようになります。等価検索など「選択性の高い検索(少数ヒット)」で効果が出やすい機能です。対応する述語・データ型・演算子には制約があるため、導入前に公式ドキュメントで「自分のクエリが適用範囲か」を確認しましょう。
- **現場での注意点:**データの更新に伴うバックグラウンドの維持管理コスト(サーバーレスコンピュート費用)がかかるため、「本当に費用対効果が見合うか?」を見極めてから投入しましょう!
チート級に強力な「3つのキャッシュ機能」
Snowflakeには、エンジニアが特に意識して設定しなくても、裏側で勝手に(しかも超強力に)働いてくれる3種類のキャッシュが存在します。
このキャッシュの仕組みを理解してクエリを書けば、計算コストを劇的に削減…どころか、「コンピュート課金ゼロ(0円)」で結果を返すことも夢じゃありません!それぞれ役割が違うので、しっかり押さえておきましょう。
1. メタデータキャッシュ(クラウドサービス層)
【恩恵】メタデータによる高速化(例:MIN/MAXが速くなりやすい)
Snowflakeはマイクロパーティションのメタデータ(例:列の最小値/最大値など)を保持しており、条件によっては一部の集計やフィルタが高速化されます。
例えば MIN() / MAX() などは、データ全体を読み切らずに済むケースがあり、クエリが軽くなりやすいです。
※ただし、すべての集計(例:COUNT(*))が常にメタデータだけで返るわけではありません。クエリ内容や状態によっては通常通りスキャンが発生します。
2. クエリ結果キャッシュ(クラウドサービス層)
【恩恵】昨日と同じダッシュボードを開くなら高速化される
最大24時間以内に実行された「全く同じクエリ」の結果を、クラウドサービス層が丸ごと保持してくれています。
条件を満たすと、結果キャッシュによりコンピュートをほぼ使わず返る場合がある
毎朝、大勢の社員が一斉に同じBIダッシュボードを開く…みたいなシチュエーションで、データベースが死なずに爆速で表示されるのはコイツのおかげです。
3. データキャッシュ / ローカルディスクキャッシュ(ウェアハウス層)
【恩恵】2回目以降の検索が爆速になる
クエリを実行するためにストレージから読み込んできた「マイクロパーティションのデータ」を、仮想ウェアハウス(コンピュートノード)のローカルSSDに一時的に保存(キャッシュ)してくれます。
次に同じデータを読み込むクエリが来たとき、遠くのストレージまでわざわざ取りに行かずに、手元のSSDから爆速で読み込んでくれます(クエリプロファイルの Percentage scanned from cache がこれです)。
- **現場での注意点:**このキャッシュは仮想ウェアハウスのSSD上にあるため、ウェアハウスがサスペンド(一時停止)されたり、サイズ変更されたりすると綺麗サッパリ消滅します! 「あれ?さっきは早かったのに、今実行したら遅いぞ?」という時は、大体ウェアハウスがサスペンドしてキャッシュが飛んだ後です(笑)。
参考リンク: https://docs.snowflake.com/ja/user-guide/querying-persisted-results
ウェアハウス(コンピュート)の最適化
クエリも直した!テーブル設計も見直した!「それでも遅いんじゃい!」という時の最終手段。
それはSnowflakeのエンジンである「仮想ウェアハウス」のパワーを調整することです。課金(コスト)に直結する部分なので、闇雲にイジる前に以下の4つの戦略を理解しておきましょう。
1. 筋肉を増やす「スケールアップ(サイズ拡大)」
【いつやるの?】重いクエリ(大規模なJOINや集計)の処理そのものが遅いとき
ウェアハウスのサイズを「S → M → L」と大きくするアプローチです。
サイズを上げるとサーバーの数が増え、物理的なメモリ容量も倍増します。クエリプロファイルを見て「ローカルストレージへのスピル(メモリ不足)」が発生していたら、迷わずサイズアップを検討しましょう!処理が早く終われば、結果的に小さいサイズでダラダラ回すより安く済むこともあります。
2. 分身の術「スケールアウト(マルチクラスター)」
【いつやるの?】大勢の人が同時にアクセスして、順番待ち(キューイング)が発生しているとき
朝9時など、BIツールから一斉にクエリが飛んできて「処理待ち」が起きているならコレの出番です。
最大クラスター数を増やしておくと、混雑時にSnowflakeが勝手にウェアハウスを「分身」させて並列処理の枠を広げてくれます。そして暇になったら勝手に消えます。賢すぎる!
3. コスパ最強のブースト「クエリアクセラレーションサービス(QAS)」
【いつやるの?】普段は軽いクエリばかりだけど、たまーに超巨大なスキャンクエリが来るとき
これ、個人的にめちゃくちゃ推し機能です!
たまに来る重いクエリのために、常にLサイズのウェアハウスを起動しておくのって、お金の無駄ですよね?QASを有効にしておくと、「普段はSサイズで安く稼働させつつ、重いクエリが来たその瞬間だけ、Snowflakeの空きリソースを一時的に借りて爆速処理(ブースト)」してくれます。使った分だけの課金なので、お財布にめちゃくちゃ優しいです。
※上限や適用条件があります。
4. キャッシュ寿命の調整「自動サスペンド時間」
【いつやるの?】前の章で紹介した「データキャッシュ」をどうしても維持したいとき
ウェアハウスが自動で一時停止(サスペンド)するまでの時間を、デフォルトの10分から伸ばすアプローチです。ウェアハウスが起動しっぱなしになるので、ローカルディスクのキャッシュが消えずに維持され、次のクエリが爆速になります。
- **恐怖のコストトラップ(超注意!):キャッシュが維持されるということは、「誰もクエリを投げていなくても、起動している間はずーっと課金メーターが回り続ける」**ということです!ただ単にサスペンド時間を長くすると月末の請求書で泡を吹くことになるので、「本当にキャッシュの恩恵とコストが見合っているか」は慎重に判断してくださいね!
参考リンク: https://docs.snowflake.com/ja/user-guide/performance-query-warehouse
絶対にやってはいけない!恐怖の「お札束燃やし」アンチパターン
ここまでSnowflakeの強力な機能を活かしたチューニングの正攻法を語ってきましたが、最後に「これをやったら一発でパフォーマンスが死ぬ&コスト(お金)が爆発する」という、絶対に書いてはいけないSQLのアンチパターンを4つ紹介します。
もしチームメンバーのコードレビューでこれを見つけたら、全力で止めてください!(笑)
1. 宇宙の果てまでデータが増える「爆発結合(デカルト積)」
JOIN の条件(ON 句)を書き忘れたり、条件が甘くて1つのレコードが別テーブルの無数のレコードと一致してしまったりするミスです。
これ、Snowflakeの課金体系においては本当に危険です。1万行と1万行のテーブルを条件なしで結合すると、1億行の無駄なデータが生成されます。クエリプロファイルを見たとき、Join 演算子の出力結果が元のテーブルより桁違いに膨れ上がって(爆発して)いたら要注意!ウェアハウスのリソースを無駄に食い潰す最悪のクエリです。
2. 隠れた激重処理「ALL なしの UNION」
2つのテーブルのデータを縦にくっつけるとき、何気なく UNION を使っていませんか?
実はこれ、SQL初心者から中級者まで本当によくやる間違いです。
UNION ALL はただデータを連結するだけ(一瞬で終わる)ですが、ただの UNION は裏側で「全データをくっつけた後に、わざわざ重複がないか全件チェックして削除する(Aggregate演算子が追加される)」という、めちゃくちゃ重い処理が走ります。重複を消す必要がない、もしくは重複が存在しないと分かっているなら、絶対に UNION ALL を使ってください。 これだけで処理時間が劇的に変わります。
3. ウェアハウスの悲鳴「メモリに収まらない巨大クエリ(スピル)」
巨大なデータセットの重複排除やソートを、小さすぎるウェアハウス(X-Smallなど)で無理やり実行すると起こる悲劇です。
処理に使うメモリが足りなくなると、Snowflakeはデータをローカルディスクへ退避(スピル)させます。それでも足りないと、さらに遠くのリモートディスクへスピルさせます。この「リモートディスクへのスピル」が発生すると、クエリの速度は絶望的に遅くなります。
クエリプロファイルでこの現象(Bytes spilled to...)を見つけたら、素直に「ウェアハウスのサイズを上げる(S → Mなど)」か、「処理するデータを期間などでバッチ分割する」対処をしましょう。
4. 読んでから捨てる無駄遣い「非効率なプルーニング」
クエリプロファイルで「スキャンしたパーティション数」と「全体のパーティション数」がほぼ同じ(=フルスキャンしている)なのに、その直後の Filter 演算子で大量のレコードを除外(捨てている)しているパターンです。
これは例えるなら、**「1冊の辞書を最初のページから最後のページまで全部めくって、必要な1単語だけ見つけて、残りのページを全部捨てる」**ようなものです。探すためのスキャン費用(コンピュート時間)が完全にお札束を燃やしている状態です!
こうなっている場合は、データの物理的な並び順がWHERE句の条件と噛み合っていません。前の章で紹介した「クラスタリングキー」を設定して、検索効率を上げてあげましょう!
おわりに(まとめ)
ここまで長文にお付き合いいただき、本当にありがとうございました!
今回は「脱RDB思考のためのチューニング術」と題して、Snowflakeならではのクエリ最適化について語り尽くしました。
改めて、今回の重要ポイントを振り返ってみましょう。
- まずはクエリプロファイルを見る!(すべては現状把握から)
- マイクロパーティションの仕組みを活かして無駄なスキャンを減らす(プルーニングの鬼になる)
- 3つのキャッシュ機能を使い倒す(0円クエリは偉大)
- ウェアハウスは最適化してから大きくする(札束で殴るのは最終手段)
- デカルト積などの「お札束燃やしクエリ」は絶対に書かない
従来のRDBと同じ感覚でSnowflakeを触ってしまうと、どうしても「とりあえずインデックスを貼ろう」「とりあえずサーバーを大きくしよう」となりがちです。しかし、Snowflakeの裏側の仕組み(アーキテクチャ)を少し理解するだけで、「クエリの書き方をちょっと変えるだけで、処理速度が10倍速くなって、コストが半分になった!」なんていう魔法のようなことが本当に起こります。
この記事で紹介したテクニックは、明日からすぐに実務で使えるものばかりです。
ぜひ現場で「クエリプロファイル見たら、ここフルスキャンしてましたよ(ニヤリ)」とか、「この処理、キャッシュ効かせるように直しておきました!(ドヤァ)」とお客さんやチームメンバーに提案してみてください。きっと「おっ、こいつSnowflake分かってるな!」と一目置かれるはずです。
私自身もまだまだ勉強中の身ですが、こうして学んだことをアウトプットすることで、少しでも皆さんのデータ分析基盤ライフが快適になれば嬉しいです。
それでは、良きSnowflakeライフを!おつかれさまでした〜!