❄️

【新旧比較】「ダイナミックテーブル」はStreams & Tasksを本当に置き換えるか?

に公開

🚀 はじめに

https://zenn.dev/yujmatsu/articles/20251024_sf_mv_vs_dt

以前の記事で マテリアライズドビューとダイナミックテーブル について取り上げました。
今回はさらに ダイナミックテーブル にフォーカスしていこうと思います。

早速ですが、Snowflakeでデータパイプラインを構築していて、次のような経験はありませんか?

  • 「Raw層からStaging層へ、変更分だけをマージ(MERGE)したい」
  • 「そのために STREAM を作り、TASK を定義し、MERGE 文を書き、SCHEDULE を設定し…」
  • 「パイプラインが3段階、4段階と深くなるにつれ、AFTER で繋いだタスクの依存関係(DAG)が複雑になり、管理・デバッグが地獄になる」

この“従来型”の強力なやり方(Streams & Tasks: 以下 S/T)は、手続き的(Imperative)でオブジェクトも増えがちです。そこで登場したのがダイナミックテーブル(Dynamic Tables: 以下 DT)。宣言的(Declarative)に「欲しい最終形」と「目標鮮度(Target Lag)」だけを定義すれば、Snowflakeが更新スケジュールを自動決定・実行します。ターゲットラグは 目標値 であり、処理量やWHサイズなどによって超過する可能性がある点は押さえておきましょう。

⏳ Snowflakeパイプラインの歴史的背景

Snowflake登場当初から主流はELTです。Rawにロード→SQLで変換してStagingやMartsへ、という流れ。ここで変更分だけ処理したいニーズに応えるビルディングブロックが Stream(CDC) と Task(スケジューラ/依存関係) でした。Taskは WHEN SYSTEM$STREAM_HAS_DATA('<stream>') で「差分がある時だけ走らせる」ことも可能です。

🔧 従来の方法:Streams & Tasks(S/T)の仕組み

  1. Streamの作成(CDCの提示)
  2. Taskの作成(スケジュールやAFTERで依存関係を表現)
  3. MERGEでターゲットへ反映(DMLで消費された時にストリームのオフセットが前進。SELECTで読むだけでは前進しません)
-- Streamの作成
CREATE STREAM stream_on_raw_sales ON TABLE raw_sales;

-- Taskの作成 (以下は例です)
CREATE TASK task_process_raw_sales
WAREHOUSE = etl_wh
SCHEDULE  = '5 MINUTE'
WHEN SYSTEM$STREAM_HAS_DATA('stream_on_raw_sales')
AS
MERGE INTO staging_sales s
USING stream_on_raw_sales t
ON s.id = t.id
WHEN MATCHED AND t.METADATA$ACTION = 'DELETE'
AND NOT t.METADATA$ISUPDATE THEN -- UPDATEイベントのDELETE側を除外
  DELETE
WHEN MATCHED AND t.METADATA$ACTION = 'INSERT' THEN
  UPDATE SET s.amount = t.amount, ...
WHEN NOT MATCHED AND t.METADATA$ACTION = 'INSERT' THEN
  INSERT (id, amount, ...) VALUES (t.id, t.amount, ...);

DELETE 句に AND NOT METADATA$ISUPDATE を入れる点が重要です(UPDATEイベントの“DELETE側”を誤って削除扱いしないため)。
S/Tの課題は、DAG管理の煩雑さ・オブジェクト増殖・「いつ/どう」更新するかを逐一定義する手続き性にありました。

✨ 新しい方法:ダイナミックテーブル(DT)

DTは宣言的です。CREATE DYNAMIC TABLE で欲しい最終形(SELECT)と目標鮮度(TARGET_LAG)、更新に使う WAREHOUSE を指定するだけで、Snowflakeが依存関係の解析と自動スケジューリングを行い、増分/フルを選択してリフレッシュします(増分にはChange Trackingが必要。未有効なら可能な範囲で自動有効化を試みます)。

-- ダイナミックテーブル(DT)の作成
CREATE OR REPLACE DYNAMIC TABLE analytics.product_summary
TARGET_LAG = '1 HOUR'
WAREHOUSE  = etl_wh
AS
SELECT
    s.product_id,
    r.product_name,
    SUM(s.quantity) AS total_quantity
FROM staging.sales AS s
JOIN raw.products  AS r
ON r.product_id = s.product_id
GROUP BY 1, 2;

📊 徹底比較:S/T vs. DT

比較軸 Streams & Tasks (従来型) ダイナミックテーブル (新型)
パラダイム 手続き型 (Imperative) 宣言型 (Declarative)
主な関心事 いつ」「どう」更新するかを定義 」を「どれくらいの鮮度」で欲しいかを定義
依存関係(DAG) AFTER句で手動管理 SnowflakeがSQLから自動解析・管理
差分検知(CDC) CREATE STREAM手動作成 自動 (内部でChange Trackingを利用)
実装の複雑さ 高い (Stream, Task, MERGE文がそれぞれ必要) 低い (CREATE DYNAMIC TABLE のみ)
SQLの制約 ほぼ無し (Taskで任意のSQL/手続きを実行可) 一部あり (外部関数不可。増分更新がフル更新にフォールバックするケースあり)
更新トリガー スケジュール / 条件 (SYSTEM$STREAM_HAS_DATA) TARGET_LAG に基づく自動スケジュール
更新方式 MERGE 等で手動実装 増分/フル をSnowflakeが自動選択・実行

🤔 結論:DTはS/Tを「完全に」置き換えるか?

結論は「完全な置き換えは無理で要件にハマれば1つの選択肢」になると思います。
その理由として、Snowflake内で完結する標準的なELTでは、DTのほうがシンプルで堅牢かつ運用が容易となるためです。

ただし次のようなケースではS/Tが有効と考えまます:

  • 外部API連携や複雑な手続きロジック(プロシージャ呼び出し等)が必要。
  • Snowpipe + Stream + Task の準リアルタイム連携を既に確立している。

DTは単一SELECT定義に基づく自動更新であり、外部関数/ストアドプロシージャ呼び出しを含む手続き的制御は範囲外となっているためです。

😌 おわりに

ELTはDTで宣言的に、外部連携・高度な手続きはS/Tで。
DTとS/Tは置き換え関係というより、目的別の最適ツールです。
まずはDTを第一選択に、要件に応じてS/Tなど別の方法を併用していくのがよさそうです。
※あくまで、DTは1つの手段として覚えておくことが良いでしょう!

📚 参考出典(主要ドキュメント)

Discussion