設定1つでDatabricksの課金を半分にした話
BIツールの接続先をSnowflakeからDatabricksへ移行したところ、移行後の月額コストが事前の見積もりの約4倍になりました。ウェアハウスは最小サイズで、クエリの量も移行前とほぼ変わっていないのに、です。
この記事は、その原因を課金データを分解しながら特定し、最終的に設定1箇所の変更で解消の目処を立てるまでの調査ログです。読み終わる頃には、サーバレスDWHの請求が「単価×クエリ量」だけでは説明できない理由と、同じ問題が自分の環境で起きていないかの確かめ方がわかるはずです。
何が起きたか
BIツールのデータソースをSnowflakeからDatabricksへ移行しました。ウェアハウスは移行前がX-Small、移行後が2X-SmallのServerless SQL Warehouse。どちらも最小サイズで、ダッシュボードやクエリはほぼそのまま持っていきました。
移行後に請求を見ると、月額が見積もりの約4倍。移行が進むにつれて差は開いていき、日次ペースでは5倍近くになっていました。「Databricksは高いから」で済ませるには差が大きすぎます。
単価を疑う → 違った
最初に疑ったのは単価です。DBUという単位が直感的じゃないので、なんとなく「実は時間あたりが高いのでは」と思っていました。
計算してみると、2X-Small Serverlessは4 DBU/時で、東京リージョンの定価が$1.00/DBU。つまり稼働1時間あたり約$4です。SnowflakeのX-Smallは1クレジット/時、クレジット単価は契約次第なので具体額は伏せますが、うちの場合ほぼ同水準でした。単価はシロ。
次にクエリ量。移行前後それぞれのquery historyを丸1ヶ月分集計したら、どちらも月3万件前後・平均数秒で、移行後のほうがむしろ1割少ない。同じダッシュボードを移したので当然ではあります。
Serverlessなので「DBUとは別にEC2代がかかっている」パターンも存在しません(インフラ込み単価)。ここで一度行き詰まりました。単価も量も同じなのに4倍って何。
課金「時間」を出したら一発だった
単価×時間の「時間」の側、つまり課金対象になっている稼働時間を集計したらこうなりました。
| 移行前(Snowflake) | 移行後(Databricks) | |
|---|---|---|
| 月間の課金対象時間 | 約76時間 | 約301時間 |
| うち実際にクエリを処理していた時間 | 約36時間 | 約48時間 |
| アイドル比率 | 約53% | 約84% |
76時間と301時間で、請求額の差とほぼ同じ4倍。そして301時間のうちクエリを処理していたのは48時間だけで、残りの253時間は起きて待っているだけでした。課金の84%がアイドルです。さすがに笑いました。
からくりは単純です。BIツールは深夜も含めて24時間ぱらぱらとクエリを投げてきます。1件は数秒。ウェアハウス側は起こされるたびに「クエリを数秒処理して、次が来るかもしれないからしばらく起きて待つ」を繰り返します。この待ち時間が、Snowflakeはauto-suspendの60秒、Databricksはauto-stopの5分。ちなみにSnowflakeの60秒は攻めた設定というより実質的な下限で、resumeのたびに最低60秒分は課金される仕様上、これ以上短くしても請求は変わりません。Snowflake側にもう伸びしろはない、フェアな比較です。
Snowflake : [クエリ数秒][待機60秒] → 1回あたり約1.2分
Databricks: [クエリ数秒][======== 待機5分 ========] → 1回あたり約5.2分
1回起こされるごとの課金が約4.3倍。実測したクエリ1件あたりコストの差ともほぼ一致したので、これで確定です。軽いクエリが散発的に飛ぶBIワークロードでは、請求を決めるのは処理性能ではなく寝付きの速さでした。
GUIの下限は5分。でもAPIなら1分
原因がわかれば話は早くて、auto-stopを縮めれば済みます。ところがGUIを開くと最小5分で、それ以上短くできません。60秒 vs 5分の勝負を60秒 vs 5分のまま続けるしかないのか、と一度諦めかけたんですが、ドキュメントにしれっとこう書いてありました。
The minimum is 5 minutes when you use the UI. Note that you can create a serverless SQL warehouse using the SQL warehouses API, in which case you can set the Auto Stop value as low as 1 minute.
(Create a SQL warehouse | Databricks Documentation)
APIならServerlessに限り1分まで下げられる。実際にやることは1行です。
databricks api post /api/2.0/sql/warehouses/<warehouse-id>/edit \
--json '{"auto_stop_mins": 1}'
稼働中のウェアハウスにそのまま投げられて、再起動もなく即反映されました。auto_stop_minsだけを渡しても、サイズなど未指定の設定は既存値のまま残ることも、適用後にgetで確認しています。
注意することは以下です。
- 1分にできるのはServerlessだけです。Pro / Classicは最小10分で動かせません。
-
Warehouses APIリファレンスの
auto_stop_mins欄には、いまも "Must be == 0 or >= 10 mins" と書いてあります。Pro/Classic前提の記述が残っているだけで、Serverlessには当てはまりません。自分はリファレンス側を先に読んでいたら「1分は無理」と判断していたと思います。ドキュメント同士が食い違っているときは新しい方(この場合はガイド側)を信じて手を動かすのが正解でした。 - APIで入れた1分設定は永続しますが、UIの編集画面は1分を選択肢に持ちません。あとから誰かがUIでウェアハウス設定を保存し直すと5分以上に戻る可能性があるので、チーム運用ならIaCで値を固定するか、変更したことを共有しておくと安全です。
どれくらい下がったか
結果から言うと、およそ半分になりました。日次のDBU消費でいうと、約53→約25です。
適用直後の3日間は21前後まで下がり、その後は25〜26で安定しました。クエリ件数も起動回数も横ばいのままなので、リバウンドしたわけではなく、これが定常値のようです。
事前の試算は「アイドルの大半は5分待ちなのだから、1分にすれば月額は1/3になるはず」でした。半分止まりだった理由は単純で、クエリ履歴と起動イベントを突き合わせたところ、起動回数が1日約40回→約92回に倍増していました。いままでは5分の待機が「5分以内に来る次のクエリ」を橋渡ししていたのが、すべて停止→再起動に置き換わったためです。尻尾の長さは1/5になったけれど、本数が約2.3倍になった、という構図です。
残った消費の半分以上はすでに純粋なクエリ処理時間で、auto-stopで削れる部分はほぼ使い切りました。ここから先を削るなら、ウェアハウス側の設定ではなくBIツール側のポーリング頻度の話になります。GUIからは変えられない設定1つで半減なら、うちとしては十分でした。
うちだけの話ではなさそう
振り返ってみると、今回の条件に特殊なものは1つもありません。再現に必要なのは次の2つだけです。
- 数秒で終わる軽いクエリを、散発的に・長い時間帯にわたって投げてくる接続元がいる
- 接続先がサーバレスDWHで、「最後のクエリから停止までの待ち時間」が長めに設定されている
BIツールはこの条件の典型例です(ダッシュボードの自動更新、キャッシュのウォームアップ、死活監視など、人が見ていない時間もクエリは飛び続けます)。ただBIに限らず、データ基盤の外側のアプリケーションやバッチが基盤へ直接クエリを発行できる構成なら、同じ形は簡単にできあがります。今回のような移行のタイミングはもちろん、新しい接続元を追加したときも、課金対象時間のアイドル比率を一度見ておくと安心です。
まとめ
移行の見積もりでは単価とクエリ量を並べて「同じくらいのはず」と踏んでいましたが、移行後の請求を支配していたのは、そのどちらにも現れないアイドル待機でした。サーバレスDWHに軽いクエリが常時飛んでくる構成を持っているなら、課金対象時間のうち何%が実処理かをまず出してみてください。想像よりずっと大きい数字が出るかもしれません。
Discussion