[新機能]SnowflakeネイティブのIaC機能「DCM Projects」で同一アカウント内にDEV/PROD環境を構築してみた

[新機能]SnowflakeネイティブのIaC機能「DCM Projects」で同一アカウント内にDEV/PROD環境を構築してみた

2026.03.21

さがらです。

Snowflakeの新機能として、DCM Projectsがリリースされています。SnowflakeオブジェクトをDEFINE文で宣言的に定義し、Plan→Deployのワークフローでデプロイできる、いわばSnowflake版のInfrastructure-as-Code(IaC)機能です。

https://docs.snowflake.com/en/release-notes/2026/other/2026-03-20-dcm-projects

https://docs.snowflake.com/en/user-guide/dcm-projects/dcm-projects-overview

これまでSnowflakeオブジェクトのIaC管理にはTerraformやSchemachangeなどの外部ツールが必要でしたが、DCM ProjectsによりSnowflakeネイティブで宣言的管理ができるようになります。

本記事では、**1つのDCM project folder(定義ファイル群)**に2つのtargetを定義し、DEV用とPROD用の別々のDCM project objectにテンプレート変数を切り替えてデプロイする手順を検証しました。実際に試してみたので、手順と確認結果をまとめます。

機能概要

DCM Projects(Database Change Management Projects)は、Snowflakeオブジェクトの望ましい状態をコードで定義し、宣言的に管理する機能です。

主な特徴は以下のとおりです。

  • 宣言的な定義: DEFINE文でオブジェクトの望ましい状態を定義します。依存関係は自動解決されるため、記述順序を気にする必要はありません
  • Plan→Deployワークフロー: デプロイ前にPLANで差分を確認し、問題なければDEPLOYで反映します。Terraformのplanapplyと同じ考え方です
  • Jinja2テンプレート: 変数の置換、条件分岐、ループ、マクロに対応しています。環境ごとに異なるパラメータを1つの定義ファイルから展開できます
  • サポート対象オブジェクト: Database、Schema、Table、Dynamic Table、(Secure) View、Internal Stage、Warehouse、Role / Database Role、Grant、Data Metric Function、Task、SQL Function、Tag、Authentication Policyなどを管理可能です。ただしSnowflakeオブジェクトの一部のみが対象であり、File Formatなどはサポート対象に含まれていません
  • パイプライン管理: REFRESH ALLでDynamic Tableの一括リフレッシュ、TEST ALLでデータ品質テストの一括実行が可能です
  • 管理手段: Snowsight、Snowflake CLI(v3.16以降)、SQL、Cortex Code CLIの4つのインターフェースから操作できます

https://docs.snowflake.com/en/user-guide/dcm-projects/dcm-projects-supported-entities

制限事項

2026年3月20日時点で確認されている主な制限事項です。公式Docsの内容をベースにまとめています。

全般

  • Previewのため、仕様変更の可能性があります
  • サポート対象はSnowflakeオブジェクトの一部(subset)に限られます
  • 1つのDCM project objectに対して異なるtemplating configurationを切り替えてdeployすると、レンダリング結果に含まれなくなったオブジェクトは次回deployでdrop対象になりえます。そのため、環境ごとに別のDCM project objectを使うことが推奨されます
  • Deploy時の変更はCREATE OR ALTERベースの制約を受けます。少なくとも一部オブジェクト(例: Table)では、失敗時に部分的な変更が適用される可能性があるため注意が必要です
  • 最大1,000 source files / 10,000 rendered object definitionsまでの制限があります。超過すると性能劣化や実行失敗の可能性があります
  • Preview中は、changesetがすべての細かな変更を完全には捕捉しないことがあります
  • _snowは予約識別子です
  • テンプレート変数に機微情報を入れるべきではありません

オブジェクト固有の制限

  • Database / Schema / Table / View / Dynamic Table: renameは未サポートです
  • Table: column rename、互換性のない型変更、Search Optimization追加、列定義へのtag / masking policy / row access policyの追加は未サポートです。列順の変更も未サポートです
  • Dynamic Table: INITIALIZEはimmutableです。bodyの変更やrefresh modeの変更はre-initialization / full refreshが必要な場合があります。列順変更・renameも未サポートです
  • View: rename、列順変更は未サポートです
  • Internal Stage: internal stageのみ対象で、encryption typeはimmutableです
  • Warehouse: INITIALLY_SUSPENDEDはimmutableです
  • Role / Database Role: Application Roleは未サポートです
  • Grant: APPLICATION ROLE grants / CALLER grantsは未サポートです
  • Tag: PROPAGATEは未サポートです

Jinja2テンプレート

  • importextendsinclude構文は未サポートです

前提条件

  • Snowflake: AWS US West(Oregon)リージョン、Enterprise Edition(※AWS東京リージョンの環境では本機能が有効化されていなかったため、AWS US Westのトライアルアカウントを作成しています。)
  • 本機能のステータス: 2026年3月20日時点ではPreview
  • 必要な権限: CREATE DCM PROJECT ON SCHEMA権限。加えて、project ownerにはプロジェクトで定義した全オブジェクトをdeployできる十分な権限が必要です
  • project ownerロール: 本記事ではDEFINE ROLEによるロール作成を含むため、CREATE ROLE権限を持つACCOUNTADMINを使用しています
  • DCM project objectの配置先: DCM project objectはschema-levelオブジェクトであるため、配置先となるdatabase / schemaが必要です(デプロイ先のdatabaseとは別概念です)
  • 操作環境: Snowsight Workspace

事前準備

Workspaceの作成

Snowsight上でWorkspaceを作成します。

左メニューから「Projects」→「Workspaces」に移動し、左上のWorkspace名を押して「Private Workspace」を選択し、Workspaceの名前を入力して作成します。

下図のようにWorkspaceが作成できればOKです。

2026-03-20_18h19_10

DCM project object用のdatabase / schemaの作成

DCM project objectはschema-levelオブジェクトであり、配置先となるdatabaseとschemaが必要です。本記事ではDCM_ADMIN.PROJECTSという専用のdatabase / schemaを作成し、ここにDEV用・PROD用の2つのDCM project objectを配置します。

USE ROLE ACCOUNTADMIN;
USE SECONDARY ROLES NONE;

-- DCM project object配置用
CREATE DATABASE IF NOT EXISTS DCM_ADMIN;
CREATE SCHEMA IF NOT EXISTS DCM_ADMIN.PROJECTS;

クエリ実行後、DCM_ADMINデータベースとPROJECTSスキーマが作成されていればOKです。

2026-03-20_18h21_03

試してみた

ということで早速、DCM Projectsを使ってDEV/PROD環境を構築していきます!

1. DCM project folderの作成

Snowsight Workspace上にDCM project folderを1つ作成します。1つのproject folder内にmanifest.ymlでDEV / PRODの2つのtargetを定義し、同じ定義ファイル群をそれぞれの環境にデプロイする構成です。

作成したWorkspace内で「+」ボタンを押し、「DCM Project」を選択します。プロジェクト名を入力します(本記事ではmy_dcm_projectとします)。

2026-03-21_05h11_12

今回は「Define default target environment」にチェックを入れて、下図のように設定しました。先程作成したデータベースとスキーマを選択し、Target nameは「DEV」としておきます。

2026-03-21_05h25_32

すると、下図のように各フォルダ・ファイルが自動的に生成されました!manifest.ymlの中に、先程設定したロール・データベース・スキーマの情報も含まれています。

2026-03-21_05h26_07

2. デフォルトで作られた各.sqlファイルを削除

今回は検証用の構成が決まっているため、デフォルトで作られた各.sqlファイルを削除します。

参考までに、どういったファイルなのか確認しながら、削除していきます。

sources/definitions/examples.sql

このファイルは、DCM Projectsを用いて各オブジェクトを定義する際の最も一般的な構文での定義方法がまとまっています。defineで各オブジェクトの設定を定義していくのがポイントです。

2026-03-21_05h38_57

sources/definitions/jinja_demo.sql

DCM ProjectsはJinja2テンプレートに対応しているため、forやifを用いてプログラム的に各オブジェクトの定義を行なうことが出来ます。このファイルでは、manifest.ymlで定義していたteamsの各値に沿ってループを回して、複数のteam用のテーブルを作成する処理が書かれています。

2026-03-21_05h45_53

sources/macros/grants_macro.sql

Jinja2テンプレートが利用できるため、dbtのようにmacroを作成することも出来ます。このmacroでは、あるチームに対して特定のDEVELOPERUSAGEのロールを作成し、それぞれに対して権限を付与することを行っています。

2026-03-21_05h56_14

デフォルトで作られた各.sqlファイル削除後のフォルダイメージ

下図のようになっていればOKです。今回はmacrosフォルダも不要のため削除しました。

tmp.sqlは、私が最初にデータベース・スキーマを作成したクエリを記述したファイルです。

2026-03-21_05h57_40

3. manifest.ymlの設定(DEV/PRODターゲット)

同一アカウント内でデータベースレベルでDEV/PROD環境を分離するため、manifest.ymlを以下のように設定します。

manifest_version: 2
type: DCM_PROJECT

default_target: DEV

targets:
  DEV:
    account_identifier: MY_ACCOUNT
    project_name: DCM_ADMIN.PROJECTS.MY_DCM_PROJECT_DEV
    project_owner: ACCOUNTADMIN
    templating_config: DEV
  PROD:
    account_identifier: MY_ACCOUNT
    project_name: DCM_ADMIN.PROJECTS.MY_DCM_PROJECT_PROD
    project_owner: ACCOUNTADMIN
    templating_config: PROD

templating:
  defaults:
    wh_auto_suspend: 60
  configurations:
    DEV:
      db_name: DEV_DB
      wh_name: DEV_WH
      wh_size: X-SMALL
      role_name: DEV_EXPLORER_ROLE
    PROD:
      db_name: PROD_DB
      wh_name: PROD_WH
      wh_size: SMALL
      role_name: PROD_EXPLORER_ROLE

2026-03-21_06h01_13

ポイントは以下のとおりです。

  • targets配下にはaccount_identifier(同一アカウントなので同じ値)、project_name(DCM project objectの完全修飾名)、project_owner(デプロイ時のオーナーロール)、templating_config(使用するconfiguration名)を指定します
  • templating.defaultsでデフォルト値を定義し、templating.configurationsで環境固有の値を設定します
  • 変数の解決順序は「defaults → configuration → runtime override」の3段階です

4. データベース・スキーマ・ウェアハウスの定義

ここから、各オブジェクトの定義を行っていきます。

sources/definitions/ディレクトリ配下に、データベース・スキーマ・ウェアハウスの定義ファイルを作成します。

Jinja2のテンプレート変数{{ db_name }}を使用しているため、DEV configurationではDEV_DB、PROD configurationではPROD_DBに自動で展開されます。

sources/definitions/databases.sql

DEFINE DATABASE {{ db_name }};

2026-03-21_05h59_12

sources/definitions/schemas.sql

DEFINE SCHEMA {{ db_name }}.RAW;

DEFINE SCHEMA {{ db_name }}.STG;

DEFINE SCHEMA {{ db_name }}.MART;

2026-03-21_05h59_54

ウェアハウスのサイズもmanifest.yml{{ wh_size }}でパラメータ化しているため、DEVではX-SMALL、PRODではSMALLとターゲットごとに異なるサイズを指定できます。{{ wh_auto_suspend }}templating.defaultsで定義した共通値が使われます。

sources/definitions/warehouses.sql

DEFINE WAREHOUSE {{ wh_name }}
  WITH
    WAREHOUSE_SIZE = '{{ wh_size }}'
    AUTO_SUSPEND = {{ wh_auto_suspend }}
    AUTO_RESUME = TRUE
    INITIALLY_SUSPENDED = TRUE;

2026-03-21_06h03_33

5. ロール・GRANTの定義

環境ごとにデータ参照用のロールを作成し、必要な権限をGRANTで付与します。テンプレート変数{{ role_name }}により、DEVではDEV_EXPLORER_ROLE、PRODではPROD_EXPLORER_ROLEが作成されます。

DCM ProjectsではDEFINE文とGRANT文を同一ファイルに記述できます。

sources/definitions/roles.sql

DEFINE ROLE {{ role_name }};

-- データベースへのUSAGE権限
GRANT USAGE ON DATABASE {{ db_name }} TO ROLE {{ role_name }};

-- 各スキーマへのUSAGE権限
GRANT USAGE ON SCHEMA {{ db_name }}.RAW TO ROLE {{ role_name }};
GRANT USAGE ON SCHEMA {{ db_name }}.STG TO ROLE {{ role_name }};
GRANT USAGE ON SCHEMA {{ db_name }}.MART TO ROLE {{ role_name }};

-- RAWスキーマ: テーブルへのSELECT権限(参照用)
GRANT SELECT ON ALL TABLES IN SCHEMA {{ db_name }}.RAW TO ROLE {{ role_name }};

-- STG/MARTスキーマ: Dynamic TableへのSELECT権限(参照用)
GRANT SELECT ON ALL DYNAMIC TABLES IN SCHEMA {{ db_name }}.STG TO ROLE {{ role_name }};
GRANT SELECT ON ALL DYNAMIC TABLES IN SCHEMA {{ db_name }}.MART TO ROLE {{ role_name }};

-- ウェアハウスのUSAGE権限
GRANT USAGE ON WAREHOUSE {{ wh_name }} TO ROLE {{ role_name }};

-- SYSADMINへのロール階層
GRANT ROLE {{ role_name }} TO ROLE SYSADMIN;

2026-03-21_06h04_48

6. テーブルの定義

RAWスキーマにECサイトの注文データを格納するテーブルを定義します。

sources/definitions/raw_tables.sql

DEFINE TABLE {{ db_name }}.RAW.CUSTOMERS (
  CUSTOMER_ID INT,
  CUSTOMER_NAME VARCHAR(100),
  EMAIL VARCHAR(200),
  REGION VARCHAR(50),
  CREATED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);

DEFINE TABLE {{ db_name }}.RAW.ORDERS (
  ORDER_ID INT,
  CUSTOMER_ID INT,
  ORDER_DATE DATE,
  ORDER_STATUS VARCHAR(20),
  TOTAL_AMOUNT DECIMAL(10,2),
  CREATED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);

DEFINE TABLE {{ db_name }}.RAW.ORDER_ITEMS (
  ORDER_ITEM_ID INT,
  ORDER_ID INT,
  PRODUCT_NAME VARCHAR(200),
  QUANTITY INT,
  UNIT_PRICE DECIMAL(10,2),
  CREATED_AT TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);

2026-03-21_06h07_05

7. Internal Stageの定義

データ取り込み用のInternal Stageを定義します。ディレクトリテーブルの設定も可能です。

sources/definitions/stages.sql

DEFINE STAGE {{ db_name }}.RAW.DATA_STAGE
  DIRECTORY = (ENABLE = TRUE);

2026-03-21_06h09_30

8. Dynamic Tableの定義

STGスキーマとMARTスキーマにDynamic Tableを定義し、RAW→STG→MARTのパイプラインを構成します。

RAW→STG→MARTの3層構成で、STG層ではデータのクレンジング(TRIMLOWERUPPER等)を行い、MART層では集計・結合を行うパイプラインです。STG層のDynamic TableはTARGET_LAG = DOWNSTREAM、MART層はTARGET_LAG = '1 hour'に設定しています。

sources/definitions/stg_dynamic_tables.sql

DEFINE DYNAMIC TABLE {{ db_name }}.STG.STG_CUSTOMERS
  WAREHOUSE = {{ wh_name }}
  TARGET_LAG = DOWNSTREAM
  AS
    SELECT
      CUSTOMER_ID,
      TRIM(CUSTOMER_NAME) AS CUSTOMER_NAME,
      LOWER(EMAIL) AS EMAIL,
      UPPER(REGION) AS REGION,
      CREATED_AT
    FROM {{ db_name }}.RAW.CUSTOMERS;

DEFINE DYNAMIC TABLE {{ db_name }}.STG.STG_ORDERS
  WAREHOUSE = {{ wh_name }}
  TARGET_LAG = DOWNSTREAM
  AS
    SELECT
      O.ORDER_ID,
      O.CUSTOMER_ID,
      O.ORDER_DATE,
      UPPER(O.ORDER_STATUS) AS ORDER_STATUS,
      O.TOTAL_AMOUNT,
      O.CREATED_AT
    FROM {{ db_name }}.RAW.ORDERS O;

DEFINE DYNAMIC TABLE {{ db_name }}.STG.STG_ORDER_ITEMS
  WAREHOUSE = {{ wh_name }}
  TARGET_LAG = DOWNSTREAM
  AS
    SELECT
      OI.ORDER_ITEM_ID,
      OI.ORDER_ID,
      TRIM(OI.PRODUCT_NAME) AS PRODUCT_NAME,
      OI.QUANTITY,
      OI.UNIT_PRICE,
      OI.QUANTITY * OI.UNIT_PRICE AS LINE_TOTAL,
      OI.CREATED_AT
    FROM {{ db_name }}.RAW.ORDER_ITEMS OI;

2026-03-21_06h12_33

sources/definitions/mart_dynamic_tables.sql

DEFINE DYNAMIC TABLE {{ db_name }}.MART.MART_CUSTOMER_ORDERS
  WAREHOUSE = {{ wh_name }}
  TARGET_LAG = '1 hour'
  AS
    SELECT
      C.CUSTOMER_ID,
      C.CUSTOMER_NAME,
      C.EMAIL,
      C.REGION,
      COUNT(O.ORDER_ID) AS TOTAL_ORDERS,
      SUM(O.TOTAL_AMOUNT) AS TOTAL_SPENT,
      MIN(O.ORDER_DATE) AS FIRST_ORDER_DATE,
      MAX(O.ORDER_DATE) AS LAST_ORDER_DATE
    FROM {{ db_name }}.STG.STG_CUSTOMERS C
    LEFT JOIN {{ db_name }}.STG.STG_ORDERS O
      ON C.CUSTOMER_ID = O.CUSTOMER_ID
    GROUP BY C.CUSTOMER_ID, C.CUSTOMER_NAME, C.EMAIL, C.REGION;

DEFINE DYNAMIC TABLE {{ db_name }}.MART.MART_PRODUCT_SALES
  WAREHOUSE = {{ wh_name }}
  TARGET_LAG = '1 hour'
  AS
    SELECT
      OI.PRODUCT_NAME,
      COUNT(DISTINCT OI.ORDER_ID) AS ORDER_COUNT,
      SUM(OI.QUANTITY) AS TOTAL_QUANTITY,
      SUM(OI.LINE_TOTAL) AS TOTAL_REVENUE,
      AVG(OI.UNIT_PRICE) AS AVG_UNIT_PRICE
    FROM {{ db_name }}.STG.STG_ORDER_ITEMS OI
    GROUP BY OI.PRODUCT_NAME;

2026-03-21_06h12_51

9. タスクの定義(COPY INTOによるデータ取り込み)

Internal StageにアップロードされたCSVファイルを各テーブルに取り込むためのタスクをDEFINE TASKで定義します。

各タスクはCRONスケジュールで毎日午前2時(JST)に実行される設定です。PURGE = TRUEを指定しているため、COPY INTO成功後にステージ上のファイルは自動削除されます。

なお、タスクはDeployされた時点ではサスペンド状態で作成されます。

sources/definitions/tasks.sql

-- CUSTOMERSテーブルへのCOPY INTOタスク
DEFINE TASK {{ db_name }}.RAW.LOAD_CUSTOMERS
  WAREHOUSE = {{ wh_name }}
  SCHEDULE = 'USING CRON 0 2 * * * Asia/Tokyo'
  AS
    COPY INTO {{ db_name }}.RAW.CUSTOMERS
    FROM @{{ db_name }}.RAW.DATA_STAGE/customers
    FILE_FORMAT = (TYPE = 'CSV' FIELD_OPTIONALLY_ENCLOSED_BY = '"' SKIP_HEADER = 1 NULL_IF = ('NULL', 'null', ''))
    ON_ERROR = 'CONTINUE'
    PURGE = TRUE;

-- ORDERSテーブルへのCOPY INTOタスク
DEFINE TASK {{ db_name }}.RAW.LOAD_ORDERS
  WAREHOUSE = {{ wh_name }}
  SCHEDULE = 'USING CRON 0 2 * * * Asia/Tokyo'
  AS
    COPY INTO {{ db_name }}.RAW.ORDERS
    FROM @{{ db_name }}.RAW.DATA_STAGE/orders
    FILE_FORMAT = (TYPE = 'CSV' FIELD_OPTIONALLY_ENCLOSED_BY = '"' SKIP_HEADER = 1 NULL_IF = ('NULL', 'null', ''))
    ON_ERROR = 'CONTINUE'
    PURGE = TRUE;

-- ORDER_ITEMSテーブルへのCOPY INTOタスク
DEFINE TASK {{ db_name }}.RAW.LOAD_ORDER_ITEMS
  WAREHOUSE = {{ wh_name }}
  SCHEDULE = 'USING CRON 0 2 * * * Asia/Tokyo'
  AS
    COPY INTO {{ db_name }}.RAW.ORDER_ITEMS
    FROM @{{ db_name }}.RAW.DATA_STAGE/order_items
    FILE_FORMAT = (TYPE = 'CSV' FIELD_OPTIONALLY_ENCLOSED_BY = '"' SKIP_HEADER = 1 NULL_IF = ('NULL', 'null', ''))
    ON_ERROR = 'CONTINUE'
    PURGE = TRUE;

2026-03-21_06h14_29

10. DEVターゲットへのPlan & Deploy

定義ファイルが揃ったので、まずはDEVターゲットに対してPlanを実行します。

Snowsight WorkspaceのDCM Project画面で、ターゲットを「DEV」に切り替え、「Plan」にした状態で、再生マークのボタンを押します。

2026-03-21_06h15_36

すると、作成されるオブジェクトの一覧が表示されます。各オブジェクトをクリックすると、設定値がどのようになっているか詳細も確認可能です。

2026-03-21_06h16_48

ちなみに、右上のSummarizeを押すと、英語にはなりますがどういった変更が行われるかの要約もしてくれます。

2026-03-21_06h18_53

2026-03-21_06h20_17

問題なければ「Deploy」ボタンを押してデプロイを実行します。公式Docを見ると「Alias」は、Think of the deployment alias like a commit message for your code change.と書いてあったので、「first deploy」と入れておきます。

2026-03-21_06h20_56

2026-03-21_06h23_49

デプロイが正常に完了すると、下図のように表示されます。

2026-03-21_06h24_41

デプロイ後、実際にオブジェクトが作成されているか確認します。(以下はクエリを用いた確認方法の例です。)

USE DATABASE DEV_DB;
SHOW SCHEMAS;
SHOW TABLES IN SCHEMA RAW;
SHOW TASKS IN SCHEMA RAW;
SHOW DYNAMIC TABLES IN SCHEMA STG;
SHOW DYNAMIC TABLES IN SCHEMA MART;

2026-03-21_06h25_52

-- ロールとグラントの確認
SHOW ROLES LIKE 'DEV_EXPLORER_ROLE';
SHOW GRANTS TO ROLE DEV_EXPLORER_ROLE;

2026-03-21_06h26_28

11. サンプルデータの投入と動作確認

サンプルCSVの準備

DEV環境にサンプルデータを投入して、タスクによるCOPY INTOとDynamic Tableのパイプラインが正しく動作するか確認します。

まず、以下の3つのCSVファイルをローカルに用意します。

customers.csv

CUSTOMER_ID,CUSTOMER_NAME,EMAIL,REGION,CREATED_AT
1,田中太郎,tanaka@example.com,tokyo,2025-12-01 10:00:00
2,佐藤花子,sato@example.com,osaka,2025-12-05 11:30:00
3,鈴木一郎,suzuki@example.com,tokyo,2025-12-10 09:15:00
4,高橋美咲,takahashi@example.com,fukuoka,2025-12-15 14:00:00
5,伊藤健太,ito@example.com,osaka,2025-12-20 16:45:00
6,渡辺由美,watanabe@example.com,nagoya,2026-01-03 08:30:00
7,山本拓也,yamamoto@example.com,tokyo,2026-01-08 13:00:00
8,中村さくら,nakamura@example.com,sapporo,2026-01-12 10:20:00
9,小林大輔,kobayashi@example.com,fukuoka,2026-01-18 15:10:00
10,加藤愛,kato@example.com,tokyo,2026-01-25 11:00:00

orders.csv

ORDER_ID,CUSTOMER_ID,ORDER_DATE,ORDER_STATUS,TOTAL_AMOUNT,CREATED_AT
1001,1,2026-01-15,completed,15000.00,2026-01-15 10:30:00
1002,2,2026-01-18,completed,8500.00,2026-01-18 14:00:00
1003,1,2026-01-22,completed,23000.00,2026-01-22 09:45:00
1004,3,2026-02-01,completed,4200.00,2026-02-01 11:20:00
1005,4,2026-02-05,shipped,31500.00,2026-02-05 16:00:00
1006,5,2026-02-10,completed,9800.00,2026-02-10 13:30:00
1007,2,2026-02-14,completed,12000.00,2026-02-14 10:15:00
1008,6,2026-02-20,shipped,7600.00,2026-02-20 15:45:00
1009,7,2026-03-01,completed,18500.00,2026-03-01 08:00:00
1010,8,2026-03-05,pending,5400.00,2026-03-05 12:30:00
1011,3,2026-03-08,completed,11200.00,2026-03-08 14:20:00
1012,9,2026-03-10,shipped,26800.00,2026-03-10 09:00:00
1013,10,2026-03-12,completed,3900.00,2026-03-12 11:45:00
1014,1,2026-03-15,pending,14700.00,2026-03-15 16:30:00
1015,5,2026-03-18,completed,8900.00,2026-03-18 10:00:00

order_items.csv

ORDER_ITEM_ID,ORDER_ID,PRODUCT_NAME,QUANTITY,UNIT_PRICE,CREATED_AT
1,1001,ワイヤレスイヤホン,1,8000.00,2026-01-15 10:30:00
2,1001,スマホケース,2,3500.00,2026-01-15 10:30:00
3,1002,USBケーブル,5,1700.00,2026-01-18 14:00:00
4,1003,モニターアーム,1,15000.00,2026-01-22 09:45:00
5,1003,マウスパッド,2,4000.00,2026-01-22 09:45:00
6,1004,キーボードカバー,3,1400.00,2026-02-01 11:20:00
7,1005,4Kモニター,1,29000.00,2026-02-05 16:00:00
8,1005,HDMIケーブル,1,2500.00,2026-02-05 16:00:00
9,1006,ワイヤレスマウス,2,4900.00,2026-02-10 13:30:00
10,1007,Webカメラ,1,12000.00,2026-02-14 10:15:00
11,1008,USBハブ,2,3800.00,2026-02-20 15:45:00
12,1009,メカニカルキーボード,1,18500.00,2026-03-01 08:00:00
13,1010,マウスパッド,1,4000.00,2026-03-05 12:30:00
14,1010,キーボードカバー,1,1400.00,2026-03-05 12:30:00
15,1011,ワイヤレスイヤホン,1,8000.00,2026-03-08 14:20:00
16,1011,USBケーブル,2,1600.00,2026-03-08 14:20:00
17,1012,4Kモニター,1,29000.00,2026-03-10 09:00:00
18,1013,スマホケース,1,3900.00,2026-03-12 11:45:00
19,1014,Webカメラ,1,12000.00,2026-03-15 16:30:00
20,1014,USBケーブル,3,900.00,2026-03-15 16:30:00
21,1015,ワイヤレスマウス,1,4900.00,2026-03-18 10:00:00
22,1015,マウスパッド,1,4000.00,2026-03-18 10:00:00

CSVファイルのアップロード

用意したCSVファイルをSnowsightから内部ステージにアップロードします。

タスクの定義でFROM @{{ db_name }}.RAW.DATA_STAGE/customersのようにサブパスを指定しているため、アップロード先のパスをファイルごとに分けます。

  • customers.csv → パス: /customers/
  • orders.csv → パス: /orders/
  • order_items.csv → パス: /order_items/

2026-03-21_06h31_40

アップロード後、ステージ上にファイルが配置されていることを確認します。3つのCSVファイルがそれぞれのサブパスに表示されていればOKです。

LIST @DEV_DB.RAW.DATA_STAGE;

2026-03-21_06h32_58

タスクのRESUMEと手動実行

タスクはDeployされた時点ではサスペンド状態(statesuspended)です。まずSHOW TASKSで状態を確認します。

SHOW TASKS IN SCHEMA DEV_DB.RAW;

2026-03-21_06h33_33

state列がsuspendedであることが確認できます。

タスクを有効化するには、ALTER TASK ... RESUMEを実行します。今回は3つの独立したタスク(親子関係なし)なので、それぞれ個別にRESUMEします。

ALTER TASK DEV_DB.RAW.LOAD_CUSTOMERS RESUME;
ALTER TASK DEV_DB.RAW.LOAD_ORDERS RESUME;
ALTER TASK DEV_DB.RAW.LOAD_ORDER_ITEMS RESUME;

再度SHOW TASKSを実行し、state列がstartedになっていればOKです。

SHOW TASKS IN SCHEMA DEV_DB.RAW;

2026-03-21_06h34_23

タスクのスケジュールは毎日午前2時(JST)ですが、今回は動作確認のためにEXECUTE TASKで手動実行します。

EXECUTE TASK DEV_DB.RAW.LOAD_CUSTOMERS;
EXECUTE TASK DEV_DB.RAW.LOAD_ORDERS;
EXECUTE TASK DEV_DB.RAW.LOAD_ORDER_ITEMS;

各テーブルにデータが取り込まれていることを確認します。

SELECT COUNT(*) FROM DEV_DB.RAW.CUSTOMERS;  -- 10件
SELECT COUNT(*) FROM DEV_DB.RAW.ORDERS;     -- 15件
SELECT COUNT(*) FROM DEV_DB.RAW.ORDER_ITEMS; -- 22件

2026-03-21_06h35_09

REFRESH ALLによるDynamic Tableの一括リフレッシュ

COPY INTOの実行後、Dynamic TableがTARGET_LAGに基づいて自動リフレッシュされるのを待つこともできますが、DCM ProjectsのREFRESH ALLコマンドを使うとプロジェクト配下のDynamic Tableを即座に一括リフレッシュできます。

https://docs.snowflake.com/en/user-guide/dcm-projects/dcm-projects-pipelines

SQLで実行する場合は以下のとおりです。REFRESH ALLはプロジェクトが管理する全Dynamic Tableとその上流のDynamic Tableを対象にリフレッシュを実行します。結果はJSON形式で返され、各Dynamic Tableのinserted_rows(挿入行数)、deleted_rows(削除行数)、data_timestamp(データの鮮度を示すタイムスタンプ)が確認できます。

EXECUTE DCM PROJECT DCM_ADMIN.PROJECTS.MY_DCM_PROJECT_DEV
  REFRESH ALL;

2026-03-21_06h38_52

MART層のDynamic Tableにデータが反映されていることを確認します。顧客ごとの注文集計や商品ごとの売上集計が正しく表示されていればOKです。

SELECT * FROM DEV_DB.MART.MART_CUSTOMER_ORDERS ORDER BY TOTAL_SPENT DESC;

SELECT * FROM DEV_DB.MART.MART_PRODUCT_SALES ORDER BY TOTAL_REVENUE DESC;

2026-03-21_06h39_46

12. PRODターゲットへのPlan & Deploy

続いてPRODターゲットにも同様にデプロイします。PRODターゲットでは、manifest.ymlMY_DCM_PROJECT_PRODという別のDCM project objectを指定しているため、DEV環境には影響しません。

まず、以下のクエリを実行して、PROD用のDCM project objectを作成します。

USE ROLE ACCOUNTADMIN;

CREATE DCM PROJECT IF NOT EXISTS DCM_ADMIN.PROJECTS.MY_DCM_PROJECT_PROD
    COMMENT = 'PROD用のDCMプロジェクト';

次に、WorkspaceのDCM Project画面で、ターゲットを「PROD」に切り替え「Plan」の状態で、再生ボタンを押します。(PROD用のDCM project objectが認識されておらずエラーとなる場合は、ブラウザの画面の更新を実施すれば直るはずです。)

2026-03-21_06h45_29

DEVと同様のオブジェクトが、PROD_DBに対して新規作成される差分として表示されます。ウェアハウスサイズがSMALLになっている点も確認してください。

2026-03-21_06h48_03

問題なければ「Deploy」ボタンを押してデプロイを実行します。

2026-03-21_06h48_45

2026-03-21_06h49_09

デプロイが正常に完了したら、PROD環境のオブジェクトを確認します。PROD_DBにもDEV_DBと同じスキーマ構成・オブジェクトが作成されており、ウェアハウスPROD_WHのサイズがSMALLPROD_EXPLORER_ROLEに対して各オブジェクトへの権限が付与されていればOKです。

USE DATABASE PROD_DB;
SHOW SCHEMAS;
SHOW TABLES IN SCHEMA RAW;
SHOW TASKS IN SCHEMA RAW;
SHOW DYNAMIC TABLES IN SCHEMA STG;
SHOW DYNAMIC TABLES IN SCHEMA MART;
SHOW WAREHOUSES LIKE 'PROD_WH';

2026-03-21_06h50_43

-- ロールとグラントの確認
SHOW ROLES LIKE 'PROD_EXPLORER_ROLE';
SHOW GRANTS TO ROLE PROD_EXPLORER_ROLE;

2026-03-21_06h51_08

これで、**1つのDCM project folder(定義ファイル群)**を使いながら、DEV用とPROD用の別々のDCM project objectに対して、テンプレート変数を切り替えて同一構成をデプロイできることが確認できました!

※PROD環境に対して、タスクの有効化とDynamic Tableのリフレッシュは別途必要となるためご注意ください。

おまけ: 定義ファイルからオブジェクトを変更・削除するとどうなるか

DCM Projectsでは、定義ファイルからDEFINE文を削除すると次回のDeployでそのオブジェクトがdrop対象になるとされています。実際にそうなるのか、確かめてみます。

変更点その1:ウェアハウス定義のコメントアウト

sources/definitions/warehouses.sqlの内容をコメントアウトします。

2026-03-21_06h52_24

変更点その2:タスクの定義変更

1つのタスクだけ、毎日3時に実行されるように変更します。

2026-03-21_06h55_31

Planの実行

DEVターゲットに対してPlanを実行します。

2026-03-21_06h56_16

すると、Planの結果は下図のようになりました。DEV_WHがDROP対象として表示され、タスクはALTER対象でどの設定値が変わるのかもわかるようになっています。

2026-03-21_06h57_53

Deployの実行

そのままDeployを実行します。(これもおまけですが、Aliasは日本語でも問題なかったです。)

2026-03-21_06h58_43

Deploy後、実際にウェアハウスが削除されているか確認すると、下図の通り何も表示されなかったので削除されていることがわかります。

SHOW WAREHOUSES LIKE 'DEV_WH';

2026-03-21_06h59_32

タスクについても、設定値が変更され、statestartedのままであることがわかります。これは素晴らしい!

2026-03-21_07h00_59

最後に

Snowflake DCM Projectsを使って、同一アカウント内にDEV/PROD環境を構築する手順を試してみました。

実際に触ってみて感じたのは、テンプレート変数によるDEV/PROD環境の分離が非常にシンプルに実現できるという点です。manifest.ymltemplating.configurationsdb_namewh_sizeを定義するだけで、同じ定義ファイル群から異なるパラメータの環境をデプロイできるのは便利でした。(dbtに慣れている方はスムーズに導入できると思います。)

また、Plan→Deployの安全なワークフローがネイティブに組み込まれているのも良い点です。Terraformのplanapplyに慣れている方であれば、同じ感覚で使えると思います。Dynamic TableのREFRESH ALLやデータ品質テストのTEST ALLがDCM Projectの機能として統合されている点も、データパイプラインの管理において便利だと感じました。

一方で、File FormatなどDCM管理対象外のオブジェクトが存在する点や、プレビュー機能のためrenameの制限をはじめとする各種制限事項がある点も押さえておくべきポイントです(2026年3月20日時点)。

いずれにせよ、今後のアップデートがとても楽しみな機能です!!

この記事をシェアする

FacebookHatena blogX

関連記事