EP.1 で書いたとおり、費用はクエリの重さではなく起動時間で決まります。したがって、止め方の設計がそのまま費用の設計になります。
ところが「短く止めれば安い」という単純な話ではありません。止めると失うものがあるためです。この回はその交換を扱います。
1. 止めると何が失われるか
で を止めると、そのウェアハウスが手元に保持していたデータの一部が失われます。次に起動したときは、また取りに行くところから始まります。
| 止めると | 得るもの | 失うもの |
|---|---|---|
| すぐ止める | アイドル時間の費用がゼロ | 保持していた内容が失われる |
| 長めに待つ | 再利用が効き、続く処理が速い | 使っていない時間も課金される |
断続的に使われる用途 ── 分析画面のように、数分おきにクエリが飛ぶような使い方 ── では、すぐ止めると毎回取り直しになります。結果として、処理時間が伸び、その伸びたぶんも課金されます。短くしたのに高くなるという事態が起こり得ます。
費用削減の文脈で、自動停止を一律で最短に設定する運用を見かけますが、これは用途によっては逆効果です。使われ方の間隔を見て決める必要があります。
2. 使われ方の間隔を測る
では何を基準に決めるか。クエリとクエリの間隔です。間隔が停止までの時間より短ければ起動したまま、長ければ止まります。
WITH q AS ( SELECT warehouse_name, start_time, LAG(end_time) OVER ( PARTITION BY warehouse_name ORDER BY start_time ) AS prev_end FROM snowflake.account_usage.query_history WHERE start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP()) AND warehouse_name IS NOT NULL)SELECT warehouse_name AS ウェアハウス, COUNT(*) AS 件数, ROUND(MEDIAN(TIMESTAMPDIFF(second, prev_end, start_time)), 0) AS 中央値秒, ROUND(AVG(TIMESTAMPDIFF(second, prev_end, start_time)), 0) AS 平均秒FROM qWHERE prev_end IS NOT NULL AND start_time > prev_endGROUP BY 1ORDER BY 件数 DESC;中央値を見るのが要点です。平均は、夜間の長い空白に引っ張られて実態より大きく出ます。中央値が数十秒なら、停止までの時間をそれより長くすれば、日中はほぼ起動したままになります。
逆に中央値が数時間なら、そもそも断続的ではないので短く設定して構いません。バッチ用途がこれにあたります。この場合、停止までの時間を長くしても再利用される機会がないため、待っているぶんが純粋な無駄になります。
3. 用途ごとの目安
測ったうえでの一般的な方針を整理します。絶対値は環境によるので、考え方として読んでください。
| 用途 | 使われ方 | 止め方の方針 |
|---|---|---|
| 分析・BI | 日中は断続的、夜間は皆無 | やや長め。再利用を効かせる |
| バッチ・変換 | 決まった時間にまとめて | 短く。終わったら即止める |
| 外部連携 | 不定期、単発 | 短く。起動時間を許容する |
| 調査・アドホック | 人が触っている間だけ | やや短め。放置による課金を防ぐ |
4行目に注意が必要です。調査用途は「開いたまま席を立つ」ことが起きます。EP.2 で「調査用途は費用が膨らむ最大の要因」と書きましたが、サイズだけでなく止め忘れという形でも効いてきます。
4. 起動しないで済ませる
止め方の話の先に、そもそも起動しないという選択肢があります。 が効く場合、計算資源を起こさずに結果が返ります。この場合、費用は発生しません。
条件は、同じクエリで、元のデータが変わっていないことです。定期的に同じ集計を見るダッシュボードのような用途では、これがよく効きます。
- クエリ文が完全に一致すること — 空白やコメントの差でも別扱いになる場合がある
- 参照しているデータが変わっていないこと — 更新があれば無効になる
- 現在時刻などを含めないこと — 毎回違う結果になり、使い回せない
3番目が実務でよく効きます。クエリの中で現在時刻を取っていると、毎回異なるクエリ扱いになり、結果を使い回せません。日付単位で足りるなら、日付に丸めるだけで使い回しが効くようになります。
-- 使い回せない: 実行のたびに条件が変わるSELECT COUNT(*) FROM ordersWHERE created_at >= DATEADD(hour, -24, CURRENT_TIMESTAMP());
-- 使い回せる: 日付に丸めれば、その日のうちは同じ条件になるSELECT COUNT(*) FROM ordersWHERE created_at >= DATEADD(day, -1, CURRENT_DATE());「直近24時間」が本当に必要な場面は、実は多くありません。日次で足りるなら日次にする。それだけで結果を使い回せるようになり、費用も応答速度も改善します。
5. 上限を設けて事故を止める
止め方を設計しても、想定外の使われ方は起こります。誤って巨大な処理を流す、停止設定を誰かが変える、外部連携が暴走する。上限を設けておくと、被害が限定されます。
- 1消費量の上限を設定する — 一定量を超えたら通知、または停止させる
- 21クエリあたりの実行時間に上限を設ける — 暴走を自動で打ち切る
- 3調査用途のサイズに上限を設ける — 大きなサイズを与えない
- 4設定変更を検知する — 停止設定が変わったら気づけるように
2番目は特に有効です。終わらないクエリが起動を維持し続けるのが、想定外の費用の典型的な形です。上限を設けておけば、そこで打ち切られます。業務に支障が出ない範囲で、必ず上限を入れることをお勧めします。上限に当たったこと自体が、調査すべき処理の発見にもつながります。
この考え方は パフォーマンスチューニング実践 EP.11 で扱った「後から入れにくい制約は最初に入れる」と同じです。上限は最初に入れておくべきもので、事故が起きてから入れるものではありません。
6. 決めたことを記録する
止め方の設定は、根拠が残らないと必ず誰かが変えます。「遅いから」と長くされ、「高いから」と短くされ、どちらの根拠も残らない。
- 測ったクエリ間隔の中央値 — 設定の根拠になった数字
- その用途で優先したもの — 応答速度か、費用か
- 上限の設定値と、その理由 — 業務上どこまで許容できるか
- 見直す条件 — 「利用者数が倍になったら」など
止め方が費用を決めるが、最短が最適とは限らない。判断の基準はクエリ間隔の中央値(平均ではない)。起動しないで済ませる選択もあり、そのためには毎回違う結果にしない書き方が効く。そして実行時間の上限は、事故が起きる前に入れておく。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。