はじめに
ある朝、なんとなくSnowflakeで実行中クエリを確認したら、26時間半も走り続けているクエリが見つかりました。Cortex Analystが生成したSQLにJOIN条件が抜けており、巨大なCROSS JOINを延々と計算していたのが原因です。
「なぜキャンセルされなかったのか?」を調べたら、STATEMENT_TIMEOUT_IN_SECONDS のデフォルト値が 2日(172,800秒) だったというオチでした。
この記事では、同じ事故を防ぐためのSnowflakeクエリタイムアウト設定をまとめます。
この記事で分かること
- なぜ暴走クエリが長時間止まらないのか(デフォルト値の罠)
- ウェアハウスに10分タイムアウトを仕掛ける方法
- 設定の3つのレベルと優先順位ルール
参考リンク
起きたこと:Cortex Analystのクエリが26時間半止まらなかった
事象を整理するとシンプルです。
- Cortex Analystに自然言語で集計を依頼
- 生成されたSQLにJOIN条件が無く、暗黙のCROSS JOIN(デカルト積)になっていた
-
INFORMATION_SCHEMA.QUERY_HISTORYで確認したら26時間30分走行中 - インタラクティブ用ウェアハウスを終日占拠
SYSTEM$CANCEL_QUERY で手動停止しましたが、根本対策は「そもそも勝手に長時間走らせない」ことだと判断しました。
-- 実行中のクエリを確認するクエリ
SELECT
query_id,
query_text,
user_name,
warehouse_name,
start_time,
DATEDIFF(second, start_time, CURRENT_TIMESTAMP) AS run_time_seconds
FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY(
END_TIME_RANGE_START => DATEADD(minute, -5, CURRENT_TIMESTAMP)
))
WHERE execution_status = 'RUNNING'
ORDER BY start_time;
なぜ26時間も放置されたのか
Snowflakeには STATEMENT_TIMEOUT_IN_SECONDS というパラメータがあり、これを超えて走るクエリは自動でキャンセルされます。問題は このデフォルトが172,800秒(=2日) ということ。
デフォルト2日のまま使っていると、JOIN漏れや無限ループ的なSQLが流れたとき、最悪48時間ぶんのクレジットが溶けます。XSウェアハウスでも軽視できる金額ではありません。
要するに、放置設定は「クレジットを2日かけて燃やす」設定と同義です。本番環境ではもっと短く絞るのが定石ですね。
対策:ウェアハウスに10分タイムアウトを仕掛ける
cortex analyst用ウェアハウスに 10分(600秒) のタイムアウトを設定します。10分はDWHの感覚だと長めに思うかもしれませんが、BIツールのアドホック分析やCortex Analyst由来のクエリではこれでも余裕があるくらいです。
-- インタラクティブ用WHに10分タイムアウトを設定
ALTER WAREHOUSE INTERACTIVE_WH
SET STATEMENT_TIMEOUT_IN_SECONDS = 600;
-- キュー待ちが5分を超えたらキャンセル(任意)
ALTER WAREHOUSE INTERACTIVE_WH
SET STATEMENT_QUEUED_TIMEOUT_IN_SECONDS = 300;
STATEMENT_QUEUED_TIMEOUT_IN_SECONDS はキューに積まれたまま放置されることを防ぐパラメータで、デフォルトは0(無効)です。同時実行の多い環境では併用すると安全です。
設定が効いているか確認する
設定後は SHOW PARAMETERS で必ず反映を確認します。
SHOW PARAMETERS LIKE 'STATEMENT%TIMEOUT%' IN WAREHOUSE INTERACTIVE_WH;
注目するのは level 列 です。WAREHOUSE と表示されていればウェアハウス側の設定が効いています。空欄だとアカウントのデフォルトが使われている状態なので、ALTER文がうまく反映されていない可能性があります。
ひと工夫:3つのレベルを使い分ける
STATEMENT_TIMEOUT_IN_SECONDS は4つのレベルで設定可能ですが、運用上重要なのは アカウント・ウェアハウス・セッションの3つ です。
| レベル | コマンド | 役割 |
|---|---|---|
| アカウント | ALTER ACCOUNT SET ... |
全体の最終ライン |
| ウェアハウス | ALTER WAREHOUSE xxx SET ... |
ワークロード別の制御 |
| セッション | ALTER SESSION SET ... |
個別ジョブで一時的に上書き |
複数レベルで設定された場合、最も小さい値が勝つ ルールです。アカウントが1時間、ウェアハウスが10分なら、10分でキャンセルされます。
推奨パターン
ワークロード別に段階的に設定するのが現実的です。
-- 1) アカウント全体の保険として1時間
ALTER ACCOUNT SET STATEMENT_TIMEOUT_IN_SECONDS = 3600;
-- 2) インタラクティブ用WHは10分
ALTER WAREHOUSE INTERACTIVE_WH
SET STATEMENT_TIMEOUT_IN_SECONDS = 600;
-- 3) バッチ用WHは2時間(業務に応じて調整)
ALTER WAREHOUSE BATCH_WH
SET STATEMENT_TIMEOUT_IN_SECONDS = 7200;
バッチ用WHにまで10分を適用すると、大規模MERGEや日次集計が巻き込まれてエラーになります。本番反映前に ACCOUNT_USAGE.QUERY_HISTORY で実行時間の分布を確認することをおすすめします。
キャンセルされたクエリを追跡する
タイムアウトでキャンセルされたクエリは error_message から検知できます。
SELECT
query_id,
user_name,
warehouse_name,
total_elapsed_time / 1000 AS elapsed_sec,
error_message,
start_time
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD(day, -1, CURRENT_TIMESTAMP)
AND error_message ILIKE '%timeout%'
ORDER BY start_time DESC;
ACCOUNT_USAGE は最大45分の遅延があるので、リアルタイムで追いたい場合は INFORMATION_SCHEMA.QUERY_HISTORY を使ってください。
まとめ
- Snowflakeのクエリタイムアウトデフォルトは 2日。本番環境では必ず短く設定する
- インタラクティブ用WHは10分、バッチ用WHは2時間程度を目安に分ける
- アカウントレベルにも保険として1時間を設定しておく
- 複数レベル設定したら 最小値が勝つ ルールを覚えておく
タイムアウトはコスト保護の最後の砦ですが、根本的にはコスト監視の仕組みも欲しいところです。次のステップとして Resource Monitor で月次クレジットの上限を設定しておくと、タイムアウトをすり抜けて積み上がるコスト増にも対応できます。
「AIにSQLを書かせる時代」は便利な反面、人間がレビューしないクエリが流れる頻度が増えるということでもあります。タイムアウトのような最低限のガードレールは、早めに仕込んでおいて損はありません。