変換処理を で管理し、実行基盤を にする構成は広く使われています。ただし両者の設計を噛み合わせないと、性能も費用も損をします。
この回は、その噛み合わせを扱います。dbt 自体の使い方は dbt ハンドブック にまとめてあるので、ここではSnowflake 側との接続部分に絞ります。
1. モデルごとにウェアハウスを変える
EP.2 で用途ごとにウェアハウスを分ける話をしました。dbt の実行でも同じで、全モデルを同じサイズで流す必要はありません。
問題は、重いモデルに合わせてサイズを決めると、軽いモデルもその費用で流れるという点です。モデル単位で指定できるので、必要な箇所だけ大きくします。
models: my_project: # 既定は小さいウェアハウス +snowflake_warehouse: wh_transform_xs
staging: +materialized: view # 実体化しない。計算資源をほぼ使わない
marts: +materialized: table # 重い集計だけ大きいウェアハウスで流す fct_orders_daily: +snowflake_warehouse: wh_transform_l`staging` をビューにしているのが要点です。実体化しなければ、そのモデル自体の計算費用は発生しません。参照されたときに元の表を読むだけになります。中間層をすべて表として作ると、その分の計算と保管が積み上がります。
| 実体化の方式 | 作るとき | 参照するとき | 向く層 |
|---|---|---|---|
| ビュー | 費用なし | 毎回元を読む | 軽い整形。staging |
| テーブル | 毎回全件作る | 速い | 何度も参照される marts |
| 増分 | 差分だけ | 速い | 大きく、追記が中心の事実表 |
2. 増分更新をいつ入れるか
テーブルとして毎回全件作り直すのは単純で確実ですが、データが増えると時間も費用も比例して増えます。ここで増分更新を検討します。
ただし増分には固有のリスクがあります。取りこぼしたときに気づけないという点です。パフォーマンスチューニング実践 EP.07 で扱ったとおり、更新日時が正しく動かないデータがあると、その行は永久に反映されません。
{{ config( materialized='incremental', unique_key='order_id', incremental_strategy='merge', on_schema_change='append_new_columns') }}
SELECT order_id, customer_id, order_date, amount, updated_atFROM {{ source('raw', 'orders') }}
{% if is_incremental() %} -- 前回取り込んだ最大時刻より新しいものだけ -- 余裕を持たせて、境界での取りこぼしを防ぐ WHERE updated_at >= ( SELECT DATEADD(hour, -3, MAX(updated_at)) FROM {{ this }} ){% endif %}`DATEADD(hour, -3, ...)` で余裕を持たせているのが実務上の要点です。ちょうど最大値以降だけを取ると、処理中に到着したデータを取りこぼします。少し重複して取り、`unique_key` で吸収する形にします。
増分だけで運用すると、取りこぼしが蓄積しても気づけません。週次や月次で全件を作り直し、件数と合計値を突き合わせる手順を用意してください。これがないと、数か月後に「数字が合わない」という形で発覚します。
3. 開発環境の作り方
EP.7 で扱った が、ここで直接効きます。本番の複製に対して開発者ごとのスキーマを切る構成です。
-- 本番の複製を作る(一瞬で終わり、保管費用もほぼゼロ)CREATE DATABASE dev CLONE prod;
-- 開発者ごとにスキーマを分ける-- dbt 側の profiles.yml で schema を dbt_yamada などにしておくと-- 互いの作業が干渉しないGRANT USAGE ON DATABASE dev TO ROLE fr_developer;GRANT CREATE SCHEMA ON DATABASE dev TO ROLE fr_developer;この構成の利点は、本番と同じデータ量・同じ偏りで確認できることです。少量のテストデータでは、EP.5 で扱った読み飛ばしの効きも、増分の挙動も再現しません。本番相当でなければ意味のない検証が、この領域には多くあります。
EP.7 でも書きましたが、複製した瞬間から本番と同じ内容です。開発用だからと権限を緩めると、実質的に本番データを開放したことになります。機微な列は加工したうえで配るか、複製にも本番と同じ扱いを適用してください。
4. 変更の影響を確認する
変換処理を変更したとき、結果が意図せず変わっていないかの確認が要ります。EP.7 で示した比較が、そのまま使えます。
手順は、本番の複製に対して変更後のモデルを流し、本番の結果と突き合わせる。差分がゼロ件であることを確認します。差分が出るなら、それが意図した変更かを1件ずつ確認します。
- 1本番を複製する — 変更前の状態を確保
- 2複製側で変更後のモデルを流す — 本番には影響しない
- 3主要な集計値を突き合わせる — 件数、合計、粒度ごとの値
- 4差分を1件ずつ確認する — 意図した変更か、事故か
- 5確認できてから本番へ適用する
この手順は LLM時代のテスト戦略 EP.05 で扱ったリグレッションそのものです。変えていないものが変わっていないことを確認する。本番相当の環境が安価に作れるからこそ、現実的な手順として成立します。
5. 費用の見え方を揃える
EP.3 で費用の可視化を扱いましたが、dbt の実行分がどこに計上されるかを把握しておく必要があります。
変換用のウェアハウスを分けていれば、その消費がそのまま変換処理の費用になります。分けていないと、分析の利用と混ざって内訳が出せません。EP.2 で分けることを勧めた理由の一つがこれです。
-- 変換用ウェアハウスで実行された、時間のかかるクエリSELECT LEFT(query_text, 80) AS クエリ, COUNT(*) AS 実行数, ROUND(SUM(total_elapsed_time) / 1000 / 60, 1) AS 合計分, ROUND(AVG(total_elapsed_time) / 1000, 1) AS 平均秒, ROUND(SUM(bytes_scanned) / POWER(1024, 3), 1) AS 読取GBFROM snowflake.account_usage.query_historyWHERE start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP()) AND warehouse_name LIKE 'WH_TRANSFORM%' AND query_type IN ('CREATE_TABLE_AS_SELECT', 'MERGE', 'INSERT')GROUP BY 1ORDER BY 合計分 DESCLIMIT 20;上位に出たモデルが、増分化やサイズ調整の候補です。ここでも EP.3 と同じく、平均ではなく合計で見ます。1回が重いモデルより、軽いが毎時走るモデルが上回ることがあります。
6. 噛み合わせの整理
両者の設計をどう対応させるか、まとめます。
| dbt 側 | Snowflake 側 | 噛み合わせ |
|---|---|---|
| モデルの層(staging / marts) | 実体化の方式 | 軽い層はビューにして費用を発生させない |
| 重いモデル | ウェアハウスのサイズ | そのモデルだけ大きくする |
| 増分モデル | 並び順・ | 取り込み順が絞り込みと噛み合っているか |
| 開発環境 | ゼロコピークローン | 本番相当で検証できる |
| 実行の記録 | 変換用を分けて内訳を出す |
モデルごとにウェアハウスを変える(既定は小さく、重い箇所だけ大きく)。軽い層はビューにすれば計算費用が発生しない。増分は余裕を持たせて重複取得し、定期的な全件再構築とセットで運用する。開発環境は本番の複製にして、本番相当で検証する。費用の内訳を出すには、変換用ウェアハウスを分けることが前提になります。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。