[新機能]Dynamic TableのSCHEDULER attributeが一般提供となったのでdbtと合わせて試してみた

[新機能]Dynamic TableのSCHEDULER attributeが一般提供となったのでdbtと合わせて試してみた

2026.03.31

かわばたです。

2026年3月26日にDynamic TableのSCHEDULER attributeが一般提供(GA)となりました。

従来、Dynamic Table は作成すると必ず自動リフレッシュが有効になっていましたが、SCHEDULER = DISABLE を指定することで 自動リフレッシュを無効化 し、手動(ALTER DYNAMIC TABLE ... REFRESH)でのみ更新する運用が可能になりました。

本記事では、この新機能を Snowflakeネイティブ および dbt連携 の両面から検証します。

https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table

概要

背景

従来の Dynamic Table は、作成時に TARGET_LAG を指定すると Snowflake のスケジューラが自動的にリフレッシュを管理していました。この仕組みは便利ですが、外部オーケストレーター(dbt、Airflow など)でリフレッシュタイミングを制御したい 場合には以下の課題がありました。

  • 自動リフレッシュが走るため、上流データが未準備のままリフレッシュが実行される可能性がある
  • パイプライン全体の実行順序を外部から制御できない
  • 不要なリフレッシュによるウェアハウスコストが発生する

新機能の内容

2026年3月26日にGAとなった SCHEDULER 属性により、Dynamic Table の自動リフレッシュを ENABLE / DISABLE で制御 できるようになりました。

設定 動作
SCHEDULER = ENABLE Snowflake のスケジューラが TARGET_LAG に基づき自動リフレッシュを実行(従来動作)
SCHEDULER = DISABLE 自動リフレッシュを無効化。ALTER DYNAMIC TABLE ... REFRESH による手動リフレッシュのみ
未指定 ENABLE と同じ(既存のDynamic Tableとの後方互換性を維持)

構文

-- 作成時に指定
CREATE DYNAMIC TABLE my_dt
  WAREHOUSE = my_wh
  SCHEDULER = DISABLE          -- TARGET_LAG は指定しない
  AS SELECT ...;

-- 既存DTの変更
ALTER DYNAMIC TABLE my_dt SET SCHEDULER = DISABLE;

-- ENABLE に戻す場合は TARGET_LAG も再設定
ALTER DYNAMIC TABLE my_dt SET SCHEDULER = ENABLE TARGET_LAG = '10 minutes';

-- 手動リフレッシュ
ALTER DYNAMIC TABLE my_dt REFRESH;

検証環境

項目
ロール ACCOUNTADMIN
ウェアハウス COMPUTE_WH
データベース KAWABATA_MART_DB
dbt Core 1.9.4
dbt-snowflake 1.9.2
データ jaffle-shop サンプルデータ(RAW_ORDERS: 686件, RAW_ITEMS: 997件)

https://github.com/dbt-labs/jaffle-shop

SCHEDULER の基本動作検証(SQL)

SCHEDULER = ENABLE(従来動作)

CREATE OR REPLACE DYNAMIC TABLE KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_ENABLED
  TARGET_LAG = '10 minutes'
  WAREHOUSE = COMPUTE_WH
  REFRESH_MODE = FULL
  INITIALIZE = ON_CREATE
  SCHEDULER = ENABLE
  AS
    SELECT
      o.ID AS ORDER_ID,
      o.CUSTOMER AS CUSTOMER_ID,
      o.ORDERED_AT,
      o.STORE_ID,
      o.ORDER_TOTAL,
      c.NAME AS CUSTOMER_NAME
    FROM KAWABATA_MART_DB.KAWABATA_RAW.RAW_ORDERS o
    JOIN KAWABATA_MART_DB.KAWABATA_RAW.RAW_CUSTOMERS c
      ON o.CUSTOMER = c.ID;

2026-03-31_09h05_09
結果: scheduler = ENABLEscheduling_state = ACTIVEtarget_lag = 10 minutes となり、自動リフレッシュが有効であることを確認した。

SCHEDULER = DISABLE

CREATE OR REPLACE DYNAMIC TABLE KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_DISABLED
  WAREHOUSE = COMPUTE_WH
  REFRESH_MODE = FULL
  INITIALIZE = ON_CREATE
  SCHEDULER = DISABLE
  AS
    SELECT
      o.ID AS ORDER_ID,
      o.CUSTOMER AS CUSTOMER_ID,
      o.ORDERED_AT,
      o.STORE_ID,
      o.ORDER_TOTAL,
      c.NAME AS CUSTOMER_NAME
    FROM KAWABATA_MART_DB.KAWABATA_RAW.RAW_ORDERS o
    JOIN KAWABATA_MART_DB.KAWABATA_RAW.RAW_CUSTOMERS c
      ON o.CUSTOMER = c.ID;

2026-03-31_09h05_52
結果: scheduler = DISABLEtarget_lag = 未設定。自動リフレッシュは行われない。

注意: SCHEDULER = DISABLETARGET_LAG を同時に指定するとエラーになります。

手動リフレッシュ

テストデータを挿入して、更新がされているかを確認します。

-- =============================================
-- 検証3: SCHEDULER=DISABLE のDTを手動リフレッシュ
--   → テストデータ挿入後に手動リフレッシュし、更新結果の反映を確認
-- =============================================

-- 3a: リフレッシュ前の件数を確認
SELECT COUNT(*) AS ROW_COUNT_BEFORE_INSERT FROM KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_DISABLED;

-- 3b: ソーステーブルにテストデータを挿入
INSERT INTO KAWABATA_MART_DB.KAWABATA_RAW.RAW_CUSTOMERS (ID, NAME)
VALUES ('test-customer-001', 'Test User For DT Verification');

INSERT INTO KAWABATA_MART_DB.KAWABATA_RAW.RAW_ORDERS (ID, CUSTOMER, ORDERED_AT, STORE_ID, SUBTOTAL, TAX_PAID, ORDER_TOTAL)
VALUES
  ('test-order-001', 'test-customer-001', '2026-03-31', '1', 1000, 100, 1100),
  ('test-order-002', 'test-customer-001', '2026-03-31', '2', 2000, 200, 2200);

-- 3c: 手動リフレッシュ実行
ALTER DYNAMIC TABLE KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_DISABLED REFRESH;

-- 3d: リフレッシュ後の件数を確認(+2 されていれば手動リフレッシュにより更新結果が反映されたと判断できる)
SELECT COUNT(*) AS ROW_COUNT_AFTER_REFRESH FROM KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_DISABLED;

-- 3e: 挿入したテストデータがDTに反映されているか確認
SELECT * FROM KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_DISABLED
WHERE CUSTOMER_ID = 'test-customer-001';
  • 件数チェック
    2026-03-31_09h31_29

  • テストデータを挿入後、手動実行
    2026-03-31_09h32_19

  • テストデータが更新されていることを確認
    2026-03-31_09h34_32

結果: REFRESH_TRIGGER = MANUAL で正常にリフレッシュが実行された。

タスクでの設定

タスクで設定を行い、任意のスケジュールで更新を行います。

-- =============================================
-- 検証4: タスクによる SCHEDULER=DISABLE DTの定期リフレッシュ
--   → CRON スケジュールで手動リフレッシュを自動化
-- =============================================

-- 4a: タスクを作成(1時間ごとに手動リフレッシュを実行)
CREATE OR REPLACE TASK KAWABATA_MART_DB.PUBLIC.TASK_REFRESH_DT_ORDERS
  WAREHOUSE = COMPUTE_WH
  SCHEDULE = 'USING CRON 0 * * * * UTC'
  COMMENT = 'Hourly manual refresh of DT_ORDERS_SCHEDULER_DISABLED'
  AS
    ALTER DYNAMIC TABLE KAWABATA_MART_DB.PUBLIC.DT_ORDERS_SCHEDULER_DISABLED REFRESH;

-- 4b: タスクを有効化
ALTER TASK KAWABATA_MART_DB.PUBLIC.TASK_REFRESH_DT_ORDERS RESUME;

-- 4c: タスクの状態確認
SHOW TASKS LIKE 'TASK_REFRESH_DT_ORDERS' IN SCHEMA KAWABATA_MART_DB.PUBLIC;

-- 4d: タスクを即時実行(スケジュールを待たずにすぐ実行)
EXECUTE TASK KAWABATA_MART_DB.PUBLIC.TASK_REFRESH_DT_ORDERS;

-- 4e: タスク実行履歴の確認(実行後しばらくしてから確認)
SELECT NAME, STATE, SCHEDULED_TIME, COMPLETED_TIME, ERROR_CODE, ERROR_MESSAGE
FROM TABLE(KAWABATA_MART_DB.INFORMATION_SCHEMA.TASK_HISTORY(
  TASK_NAME => 'TASK_REFRESH_DT_ORDERS',
  SCHEDULED_TIME_RANGE_START => DATEADD('hour', -2, CURRENT_TIMESTAMP())
))
ORDER BY SCHEDULED_TIME DESC
LIMIT 5;

-- 4f: タスクを停止(検証完了後)
ALTER TASK KAWABATA_MART_DB.PUBLIC.TASK_REFRESH_DT_ORDERS SUSPEND;

タスクが稼働して、Dynamic Tableが更新されていることを確認できました。
2026-03-31_09h51_50

ENABLE/DISABLE 切替

-- ENABLE → DISABLE
ALTER DYNAMIC TABLE ... SET SCHEDULER = DISABLE;

-- DISABLE → ENABLE(TARGET_LAG の再設定が必要)
ALTER DYNAMIC TABLE ... SET SCHEDULER = ENABLE TARGET_LAG = '10 minutes';

結果: 双方向に動的変更が可能です。

基本動作まとめ

検証項目 結果
SCHEDULER = ENABLE で作成 自動リフレッシュ有効(従来通り)
SCHEDULER = DISABLE で作成 自動リフレッシュ無効、TARGET_LAG 指定不可
DISABLE の手動リフレッシュ ALTER ... REFRESH で正常動作
ALTER で ENABLE↔DISABLE 切替 双方向に変更可能

dbt プロジェクト構成

jaffle-shop のサンプルデータを使い、 Dynamic Table を定義して検証しました。

プロジェクト構造

jaffle_shop_incr_dt/
├── dbt_project.yml
├── profiles.yml
├── packages.yml
├── macros/
│   └── refresh_dynamic_table.sql
└── models/
    ├── sources.yml
    ├── schema.yml
    ├── staging/
    │   ├── stg_orders.sql          (view)
    │   ├── stg_customers.sql       (view)
    │   ├── stg_items.sql           (view)
    │   └── stg_products.sql        (view)
    ├── intermediate/
    │   └── int_order_items_dynamic_table.sql   ← dynamic_table(実行後に SCHEDULER = DISABLE へ変更)
    └── marts/
        └── fct_orders_from_dynamic_table.sql   (view)

ソース定義(sources.yml)

version: 2

sources:
  - name: jaffle_raw
    database: KAWABATA_MART_DB
    schema: KAWABATA_RAW
    tables:
      - name: RAW_CUSTOMERS
      - name: RAW_ORDERS
      - name: RAW_ITEMS
      - name: RAW_PRODUCTS
      - name: RAW_STORES
      - name: RAW_SUPPLIES

dynamic_table モデル(実行後に SCHEDULER = DISABLE へ変更する運用)

dbt-snowflake v1.9.2 は SCHEDULER 属性をネイティブにサポートしていないため、作成時点では SCHEDULER 指定なし(= ENABLE 相当)で Dynamic Table が作られます。post_hookSCHEDULER = DISABLE に変更する構成です。

-- models/intermediate/int_order_items_dynamic_table.sql
{{
    config(
        materialized='dynamic_table',
        snowflake_warehouse='COMPUTE_WH',
        target_lag='DOWNSTREAM',
        pre_hook=[
            "ALTER DYNAMIC TABLE IF EXISTS {{ this }} SET SCHEDULER = ENABLE TARGET_LAG = DOWNSTREAM"
        ],
        post_hook=[
            "ALTER DYNAMIC TABLE {{ this }} REFRESH",
            "ALTER DYNAMIC TABLE {{ this }} SET SCHEDULER = DISABLE"
        ]
    )
}}

SELECT
    i.ITEM_ID,
    i.ORDER_ID,
    i.SKU,
    o.CUSTOMER_ID,
    o.ORDERED_AT,
    o.STORE_ID,
    o.ORDER_TOTAL,
    p.PRODUCT_NAME,
    p.PRODUCT_TYPE,
    p.PRICE AS UNIT_PRICE
FROM {{ ref('stg_items') }} i
JOIN {{ ref('stg_orders') }} o ON i.ORDER_ID = o.ORDER_ID
JOIN {{ ref('stg_products') }} p ON i.SKU = p.SKU

重要: IF EXISTS について
dbt-snowflake の dynamic_table マテリアライゼーションでは、pre_hook は Dynamic Table の作成(CREATE)より前に実行されます。初回作成時(--full-refresh)にはまだ対象テーブルが存在しないため、IF EXISTS を付けないと ALTER が失敗します。IF EXISTS を付けることで、初回は pre_hook がスキップされ、2回目以降は正常に動作します。

dbt × SCHEDULER = DISABLE の課題と解決策

dbt-snowflake v1.9.2 の制約

dbt-snowflake アダプタ(v1.9.2)は SCHEDULER 属性をまだ認識していません。そのため以下の制約があります。

制約 詳細
target_lag が必須 dbt の dynamic_table マテリアライゼーションで省略するとコンパイルエラー
dbt run(通常)で ALTER エラー dbt が ALTER ... SET TARGET_LAG=... を発行 → SCHEDULER = DISABLE と矛盾
scheduler config 未サポート config(scheduler='DISABLE') と書いても無視される

実行手順

# 初回作成(pre_hook は IF EXISTS により安全にスキップされる)
dbt run --select int_order_items_dynamic_table --full-refresh

# 2回目以降の差分更新(dbt run だけでOK)
dbt run --select int_order_items_dynamic_table

実行フロー(2回目以降)

1. pre_hook:   ALTER ... IF EXISTS ... SET SCHEDULER = ENABLE TARGET_LAG = DOWNSTREAM
               → dbt の ALTER 比較でエラーにならないようにする
2. dbt 本体:   設定比較 → 変更なし → skip(DDL 発行なし)
3. post_hook①: ALTER DYNAMIC TABLE ... REFRESH
               → 手動リフレッシュ実行(差分データ更新)
4. post_hook②: ALTER ... SET SCHEDULER = DISABLE
               → 自動リフレッシュを再度無効化

検証結果

  • ソースに3件追加後
    2026-03-31_10h21_19

  • 更新前件数チェック
    2026-03-31_10h21_32

  • dbt run(--full-refreshなし)を実行

2026-03-31_10h24_02

  • 件数チェック

2026-03-31_10h24_12

確認項目 Before After
DT行数 997 1000(+3件反映)
REFRESH_TRIGGER - MANUAL
自動リフレッシュの発火 - なし(今回の検証では、pre_hookで一時的にENABLEにしてもscheduled refreshの発火は確認されなかった)
scheduler DISABLE DISABLE(維持)

メリット・デメリット

メリット デメリット
dbt run だけで差分更新が完結 hook の仕組みに依存(dbt アダプタ更新で挙動変化の可能性あり)
マクロ不要でシンプル pre_hook で一瞬 ENABLE 状態になる(今回の検証では実害なし。ただし Snowflake はスケジューラの発火タイミングを厳密に保証していないため、観測結果として扱うのが安全)
既存のパイプラインに組み込みやすい hook が3つあり、モデル定義がやや複雑

incrementalモデルとの使い分けの指針

ユースケース 推奨
差分条件を細かく制御したい incremental
差分ロジックを Snowflake に任せたい dynamic_table
dbt run だけで完結させたい どちらも可能(本記事で検証済み)
Snowflake の自動リフレッシュに任せたい dynamic_table(SCHEDULER = ENABLE)
外部オーケストレーターから制御したい どちらも可能

dbtのincrementalモデルはデフォルトでは新規・更新レコードのみ追加/更新する仕組みなので、ソース側で削除されたレコードはincremental先のテーブルに残り続けます。

一方、Dynamic Tableは宣言的に更新を行うためソースデータが削除されていた場合はDynamic Tableのリフレッシュ時に反映されます。

まとめ

Dynamic Table の SCHEDULER 属性により、「DT の定義(クエリ)は保持しつつ、リフレッシュのタイミングは自分で制御する」という柔軟な運用が可能になりました。

なお、ALTER DYNAMIC TABLE ... REFRESH による手動リフレッシュは upstream / downstream の Dynamic Table に cascade しないため、外部オーケストレーターから個別テーブルの更新タイミングを制御しやすい点も大きな利点です。

dbt-snowflakeアダプタ(v1.9.2)はまだ SCHEDULER 属性をネイティブサポートしていませんが、pre_hook / post_hook を活用することで dbt run だけでの更新を実現できました。
しかし、推奨の方法ではないため、dbt-snowflake アダプタのアップデートをお待ちいただくのが無難です。

※dbt-snowflake 1.11.0-rc3 も同様にサポートはされていないことを確認
https://github.com/dbt-labs/dbt-adapters/blob/main/dbt-snowflake/CHANGELOG.md

この記事が何かの参考になれば幸いです!

この記事をシェアする

関連記事