![[新機能]Snowflake の Pipeline Builder で SQL + Python パイプラインを GUI で組んで Deploy してみた](https://devio2024-media.developers.io/image/upload/f_auto,q_auto,w_3840/f_auto,q_auto/v1790874280/eyecatch/wsr0koxw01pat9lxglvp.png)
[新機能]Snowflake の Pipeline Builder で SQL + Python パイプラインを GUI で組んで Deploy してみた
かわばたです。
2026年10月22日に Pipeline Builder が Public Preview になりました。Snowsight の Workspaces で task graph を GUI で組み立てる機能です。これまで Notebook や SQL を順番に動かすには CREATE TASK ... AFTER ... を手で書き、変更のたびに DDL の順序や差分も自分で管理していました。
本記事では、dbt のサンプルで知られる jaffle shop のデータを使います。公開 S3 からのロード、SQL による staging / marts の作成、Python による件数チェックと分岐を扱います。これらを Pipeline Builder のキャンバスで 1 つの task graph に組みます。そのうえで DCM project として Deploy し、実行と監視、DEV / PROD の 2 ターゲットへの展開までを確認します。
Pipeline Builder の概要
Pipeline Builder は Workspaces の中で動くビジュアルデザイナーです。Notebook、Python ファイル、ML Job、SQL をキャンバスにカードとして置き、カード同士をつなぐと実行順が決まります。裏側では次の 4 種類のオブジェクトを Pipeline Builder が作って管理します。
| オブジェクト | 役割 |
|---|---|
| Task | キャンバスのカード 1 枚が Task 1 つ。Notebook / Python / ML Job / SQL / .sql ファイルを実行する |
| Code Bundle | .py や .ipynb をキャンバスに置くと、プロジェクトのコードを束ねた Code Bundle を自動で作る。ファイルを自分でデプロイしなくてよい |
| Task graph | 2 つ以上の Task をつなぐと作られる。スケジュール、実行ポリシー、Task の既定値をまとめて持つ |
| DCM project | パイプライン 1 つが DCM project 1 つ。Deploy 前に変更内容(作成 / 変更 / 削除)を一覧で確認でき、同じ定義を別ターゲットにも展開できる |
キャンバスに置ける Task の種類は 4 つです。
| Task の種類 | 実行するもの | 補足 |
|---|---|---|
| Notebook / Python | Code Bundle 内の .ipynb または .py | Notebook は compute pool 必須。Python は warehouse でも動く |
| ML Job | Python ファイルを ML Job として実行 | bundle type が ML の Code Bundle を使う |
| SQL | Task に直接書いた SQL | ストアドプロシージャの呼び出しなどにも使う |
| SQL file | .sql ファイル | Workspace 内のファイル、ステージ上のファイル、ステージ URI から選べる |
CoCo による下書き生成(Create ダイアログの Generate with Cortex)と /pipeline-builder スキルは「coming soon」です。本記事の時点では使えません。
前提条件
- Workspaces にアクセスできること。パイプラインは所有ロールで作った共有 Workspace に置く(理由は「試してみた」の冒頭)
- パイプラインのオブジェクトを置く DB とスキーマ。既存でも、同じ DCM project で定義してもよい
- SQL Task 用の warehouse。Notebook や Container Runtime の Python には compute pool も必要
- 実行するファイルはプロジェクトフォルダの中に置く(
sources/とout/の中は不可) - Deploy するロールに必要な権限は次のとおり
| 権限 | 対象 | 用途 |
|---|---|---|
| CREATE DCM PROJECT | スキーマ | 初回 Deploy で DCM project オブジェクトを作る |
| CREATE TASK | スキーマ | Task と task graph の作成 |
| CREATE CODE BUNDLE | スキーマ | Notebook / Python Task がある場合 |
| EXECUTE TASK | アカウント | Task と task graph の実行 |
| EXECUTE MANAGED TASK | アカウント | serverless の Task を使う場合のみ |
| USAGE | DB、スキーマ、warehouse、compute pool | オブジェクトの置き場所と compute |
| USAGE | notification integration | 通知を設定する場合のみ |
| OWNERSHIP | Task | graph 内の Task はすべて graph と同じロールが所有する |
| CREATE WORKSPACE | スキーマ | 共有 Workspace を作る場合。Task の所有ロールで作れば、ファイルを読む追加権限は不要(ドキュメントの表には無い) |
検証するパイプライン
データは dbt Labs が公開している jaffle shop の 6 年分(s3://dbt-tutorial-public/long_term_dataset/)です。次の 6 つの Task を 1 つの task graph にします。
LOAD_RAW (SQL file) → STAGING (SQL file) → MARTS (SQL file) → CHECK_MARTS (Python)
├─ 成功時 → PUBLISH_SUMMARY (SQL)
└─ 失敗時 → LOG_FAILURE (SQL)
| Task | 種類 | 内容 |
|---|---|---|
| LOAD_RAW | SQL file | 外部ステージから 6 つの raw テーブルへ COPY INTO |
| STAGING | SQL file | dbt の stg_* モデルに相当する 6 つの view を作る |
| MARTS | SQL file | orders / order_items / customers など 6 つのテーブルを作る |
| CHECK_MARTS | Python | orders の件数をチェックし、しきい値未満なら例外で失敗する |
| PUBLISH_SUMMARY | SQL | CHECK_MARTS が成功したときだけ日次サマリーを作る |
| LOG_FAILURE | SQL | CHECK_MARTS が失敗したときだけログテーブルに記録する |
データ量は orders が約 209 万行、order_items が約 302 万行です(2016年9月〜2022年8月)。環境は PB_DEMO.DEV と PB_DEMO.PROD の 2 スキーマを用意し、同じパイプラインを 2 つのターゲットに Deploy します。
事前準備
ACCOUNTADMIN で、専用ロール、warehouse、DB、外部ステージを作ります。公開バケットなので認証情報は不要です。権限は OWNERSHIP でまとめず、ドキュメントの一覧どおり個別に付与して過不足を確かめます。
事前準備のSQL
USE ROLE ACCOUNTADMIN;
CREATE ROLE PB_ENGINEER;
GRANT ROLE PB_ENGINEER TO ROLE SYSADMIN;
GRANT ROLE PB_ENGINEER TO USER <自分のユーザー名>;
CREATE WAREHOUSE PB_WH WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60 AUTO_RESUME = TRUE;
GRANT USAGE ON WAREHOUSE PB_WH TO ROLE PB_ENGINEER;
CREATE DATABASE PB_DEMO;
CREATE SCHEMA PB_DEMO.DEV;
CREATE SCHEMA PB_DEMO.PROD;
-- dbt Labs 公開の jaffle shop データ(6 年分)
CREATE STAGE PB_DEMO.PUBLIC.JAFFLE_S3
URL = 's3://dbt-tutorial-public/long_term_dataset/'
FILE_FORMAT = (TYPE = CSV SKIP_HEADER = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '"' NULL_IF = ('', 'NULL'));
-- ドキュメントの Access control requirements に沿って個別に付与
GRANT USAGE ON DATABASE PB_DEMO TO ROLE PB_ENGINEER;
GRANT USAGE ON SCHEMA PB_DEMO.PUBLIC TO ROLE PB_ENGINEER;
GRANT USAGE ON STAGE PB_DEMO.PUBLIC.JAFFLE_S3 TO ROLE PB_ENGINEER;
GRANT USAGE, CREATE TABLE, CREATE VIEW, CREATE TASK, CREATE CODE BUNDLE, CREATE DCM PROJECT
ON SCHEMA PB_DEMO.DEV TO ROLE PB_ENGINEER;
GRANT USAGE, CREATE TABLE, CREATE VIEW, CREATE TASK, CREATE CODE BUNDLE, CREATE DCM PROJECT
ON SCHEMA PB_DEMO.PROD TO ROLE PB_ENGINEER;
GRANT EXECUTE TASK ON ACCOUNT TO ROLE PB_ENGINEER;
-- パイプラインを置く共有 Workspace を PB_ENGINEER が作れるようにする
GRANT CREATE WORKSPACE ON SCHEMA PB_DEMO.PUBLIC TO ROLE PB_ENGINEER;
-- 失敗時 Task が書き込むログテーブル(環境ごと)
USE ROLE PB_ENGINEER;
CREATE TABLE PB_DEMO.DEV.PIPELINE_RUN_LOG (logged_at TIMESTAMP_LTZ, graph_name VARCHAR, message VARCHAR);
CREATE TABLE PB_DEMO.PROD.PIPELINE_RUN_LOG (logged_at TIMESTAMP_LTZ, graph_name VARCHAR, message VARCHAR);
LIST @PB_DEMO.PUBLIC.JAFFLE_S3; で 6 ファイルが見えれば準備完了です。raw_orders.csv が約 298 MB、raw_order_items.csv が約 248 MB あります。serverless の Task は使わないので、EXECUTE MANAGED TASK は付けていません。
試してみた
共有 Workspace とパイプラインの作成
Workspace は共有 Workspace にします。SQL file タスクは実行のたびに Workspace 上のファイルを読みに行き、Task は所有ロールのセッションで動くためです。今回の検証では、個人 Workspace のファイルは Task から参照できませんでした(「制限事項・注意点」参照)。Projects » Workspaces の Create で Shared workspace を選び、名前 PB_SHARED、Owner role PB_ENGINEER、Database PB_DEMO、Schema PUBLIC で作ります。

Pipeline Builder を選ぶと Create Pipeline Builder ダイアログが開きます。Name と、DCM project オブジェクトを作る Database and schema を指定します。

作成すると Workspace にプロジェクトフォルダができ、空のキャンバスが開きます。フォルダには manifest.yml と sources/ があり、定義ファイル pipeline.yml は sources/definitions/ の下にできます。キャンバスでの操作はすべて pipeline.yml に書き込まれ、ファイルを直接編集した内容もキャンバスに反映されます。

実行するファイルをプロジェクトに置く
Task が実行するファイルは、プロジェクトフォルダの中(sources/ と out/ 以外)に置きます。今回は .sql ファイルを 3 つと .py ファイルを 1 つアップロードします。
01_load_raw.sql は raw テーブルの作成と COPY INTO です。テーブル名を DB やスキーマで修飾していない点がポイントです。Task は自身が属する DB とスキーマのコンテキストで実行されるため、同じファイルを DEV と PROD で使えます。
01_load_raw.sql
CREATE TABLE IF NOT EXISTS raw_customers (
id VARCHAR,
name VARCHAR
);
CREATE TABLE IF NOT EXISTS raw_orders (
id VARCHAR,
customer VARCHAR,
ordered_at TIMESTAMP_NTZ,
store_id VARCHAR,
subtotal NUMBER,
tax_paid NUMBER,
order_total NUMBER
);
CREATE TABLE IF NOT EXISTS raw_order_items (
id VARCHAR,
order_id VARCHAR,
sku VARCHAR
);
CREATE TABLE IF NOT EXISTS raw_products (
sku VARCHAR,
name VARCHAR,
type VARCHAR,
price NUMBER,
description VARCHAR
);
CREATE TABLE IF NOT EXISTS raw_supplies (
id VARCHAR,
name VARCHAR,
cost NUMBER,
perishable BOOLEAN,
sku VARCHAR
);
CREATE TABLE IF NOT EXISTS raw_stores (
id VARCHAR,
name VARCHAR,
opened_at TIMESTAMP_NTZ,
tax_rate FLOAT
);
COPY INTO raw_customers FROM @PB_DEMO.PUBLIC.JAFFLE_S3/raw_customers.csv;
COPY INTO raw_orders FROM @PB_DEMO.PUBLIC.JAFFLE_S3/raw_orders.csv;
COPY INTO raw_order_items FROM @PB_DEMO.PUBLIC.JAFFLE_S3/raw_order_items.csv;
COPY INTO raw_products FROM @PB_DEMO.PUBLIC.JAFFLE_S3/raw_products.csv;
COPY INTO raw_supplies FROM @PB_DEMO.PUBLIC.JAFFLE_S3/raw_supplies.csv;
COPY INTO raw_stores FROM @PB_DEMO.PUBLIC.JAFFLE_S3/raw_stores.csv;
COPY INTO はロード履歴(64 日間)にあるファイルをスキップします。そのため同じファイルに対する 2 回目以降の実行は、初回より短い時間で終わります(今回は初回 35 秒、2 回目以降 4〜10 秒)。
02_staging.sql は dbt の jaffle shop にある stg_* モデルを view にしたものです。6 つの view を作ります。
02_staging.sql
CREATE OR REPLACE VIEW stg_customers AS
SELECT
id AS customer_id,
name AS customer_name
FROM raw_customers;
CREATE OR REPLACE VIEW stg_locations AS
SELECT
id AS location_id,
name AS location_name,
tax_rate,
DATE_TRUNC('day', opened_at) AS opened_date
FROM raw_stores;
CREATE OR REPLACE VIEW stg_order_items AS
SELECT
id AS order_item_id,
order_id,
sku AS product_id
FROM raw_order_items;
CREATE OR REPLACE VIEW stg_orders AS
SELECT
id AS order_id,
store_id AS location_id,
customer AS customer_id,
subtotal AS subtotal_cents,
tax_paid AS tax_paid_cents,
order_total AS order_total_cents,
(subtotal / 100)::NUMBER(16, 2) AS subtotal,
(tax_paid / 100)::NUMBER(16, 2) AS tax_paid,
(order_total / 100)::NUMBER(16, 2) AS order_total,
DATE_TRUNC('day', ordered_at) AS ordered_at
FROM raw_orders;
CREATE OR REPLACE VIEW stg_products AS
SELECT
sku AS product_id,
name AS product_name,
type AS product_type,
description AS product_description,
(price / 100)::NUMBER(16, 2) AS product_price,
COALESCE(type = 'jaffle', FALSE) AS is_food_item,
COALESCE(type = 'beverage', FALSE) AS is_drink_item
FROM raw_products;
CREATE OR REPLACE VIEW stg_supplies AS
SELECT
MD5(COALESCE(id, '') || '-' || COALESCE(sku, '')) AS supply_uuid,
id AS supply_id,
sku AS product_id,
name AS supply_name,
(cost / 100)::NUMBER(16, 2) AS supply_cost,
perishable AS is_perishable_supply
FROM raw_supplies;
03_marts.sql は marts モデルをテーブルにしたものです。order_items、orders、customers の順に並べ、依存する側を後に作ります。
03_marts.sql
CREATE OR REPLACE TABLE order_items AS
WITH order_supplies_summary AS (
SELECT
product_id,
SUM(supply_cost) AS supply_cost
FROM stg_supplies
GROUP BY 1
)
SELECT
oi.*,
o.ordered_at,
p.product_name,
p.product_price,
p.is_food_item,
p.is_drink_item,
s.supply_cost
FROM stg_order_items AS oi
LEFT JOIN stg_orders AS o ON oi.order_id = o.order_id
LEFT JOIN stg_products AS p ON oi.product_id = p.product_id
LEFT JOIN order_supplies_summary AS s ON oi.product_id = s.product_id;
CREATE OR REPLACE TABLE orders AS
WITH order_items_summary AS (
SELECT
order_id,
SUM(supply_cost) AS order_cost,
SUM(product_price) AS order_items_subtotal,
COUNT(order_item_id) AS count_order_items,
SUM(CASE WHEN is_food_item THEN 1 ELSE 0 END) AS count_food_items,
SUM(CASE WHEN is_drink_item THEN 1 ELSE 0 END) AS count_drink_items
FROM order_items
GROUP BY 1
)
SELECT
o.*,
s.order_cost,
s.order_items_subtotal,
s.count_food_items,
s.count_drink_items,
s.count_order_items,
s.count_food_items > 0 AS is_food_order,
s.count_drink_items > 0 AS is_drink_order,
ROW_NUMBER() OVER (PARTITION BY o.customer_id ORDER BY o.ordered_at ASC) AS customer_order_number
FROM stg_orders AS o
LEFT JOIN order_items_summary AS s ON o.order_id = s.order_id;
CREATE OR REPLACE TABLE customers AS
WITH customer_orders_summary AS (
SELECT
customer_id,
COUNT(DISTINCT order_id) AS count_lifetime_orders,
COUNT(DISTINCT order_id) > 1 AS is_repeat_buyer,
MIN(ordered_at) AS first_ordered_at,
MAX(ordered_at) AS last_ordered_at,
SUM(subtotal) AS lifetime_spend_pretax,
SUM(tax_paid) AS lifetime_tax_paid,
SUM(order_total) AS lifetime_spend
FROM orders
GROUP BY 1
)
SELECT
c.*,
s.count_lifetime_orders,
s.first_ordered_at,
s.last_ordered_at,
s.lifetime_spend_pretax,
s.lifetime_tax_paid,
s.lifetime_spend,
CASE WHEN s.is_repeat_buyer THEN 'returning' ELSE 'new' END AS customer_type
FROM stg_customers AS c
LEFT JOIN customer_orders_summary AS s ON c.customer_id = s.customer_id;
CREATE OR REPLACE TABLE products AS SELECT * FROM stg_products;
CREATE OR REPLACE TABLE locations AS SELECT * FROM stg_locations;
CREATE OR REPLACE TABLE supplies AS SELECT * FROM stg_supplies;
check_marts.py は marts の件数チェックです。--min-orders 未満なら例外を送出して Task を失敗させ、成功時は PIPELINE_CHECK_LOG に件数を記録します。Code Bundle で動く Python には Snowpark のセッションが自動で注入されるので、接続情報は書きません。
check_marts.py
"""marts の件数チェック (Python タスク)。
ORDERS の件数が --min-orders 未満なら例外を送出し、タスクを失敗させる。
成功時は PIPELINE_CHECK_LOG に件数を記録する。
"""
import argparse
from snowflake.snowpark.context import get_active_session
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--min-orders", type=int, default=1)
parser.add_argument("--schema", default=None, help="database.schema (default: current schema of the session)")
args = parser.parse_args()
session = get_active_session()
schema = args.schema or f"{session.get_current_database()}.{session.get_current_schema()}"
counts = {t: session.table(f"{schema}.{t}").count() for t in ("ORDERS", "ORDER_ITEMS", "CUSTOMERS")}
print(f"schema={schema} counts={counts} min_orders={args.min_orders}")
if counts["ORDERS"] < args.min_orders:
raise RuntimeError(f"ORDERS has {counts['ORDERS']} rows, less than --min-orders {args.min_orders}")
session.sql(
f"CREATE TABLE IF NOT EXISTS {schema}.PIPELINE_CHECK_LOG "
"(checked_at TIMESTAMP_LTZ, orders NUMBER, order_items NUMBER, customers NUMBER)"
).collect()
session.sql(
f"INSERT INTO {schema}.PIPELINE_CHECK_LOG VALUES "
f"(CURRENT_TIMESTAMP(), {counts['ORDERS']}, {counts['ORDER_ITEMS']}, {counts['CUSTOMERS']})"
).collect()
print("check passed")
if __name__ == "__main__":
main()
SQL file タスクを並べて task graph を作る
ファイルエクスプローラーから 01_load_raw.sql をキャンバスにドラッグすると、SQL file タスクのカードができて設定ドロワーが開きます。General タブで Name(完全修飾名)と Warehouse を設定します。

次に 02_staging.sql を LOAD_RAW カードの右側へドラッグし、Run After のヒントが出たところでドロップします。Task が 1 つ増えて 2 つがつながり、同時に task graph が作られます。確認ダイアログは出ず、先頭の Task が root になります。03_marts.sql も同じ要領で STAGING の後ろに置きます。


つなげない組み合わせは、理由がキャンバスに表示されます。ドキュメントには「スキーマが違う」「root が 2 つになる」「この project 以外の Task」「別の graph に属している」「ループになる」の 5 つが挙がっています。
Python タスクと Code Bundle
check_marts.py を MARTS の後ろにドロップすると、Notebook / Python タスクができます。同時に、プロジェクトのコードを束ねる Code Bundle が自動で作られ、ランタイムと compute の既定値が設定されます。今回は compute pool を使わず warehouse で動かすので、Specification タブを変更します。

Code Bundle の名前は Pipeline Builder が付けたものをそのまま使います。Python ファイルを増やすときは、カードの + から同じ bundle を使う Task を追加し、Entrypoint だけ変えます。

条件付きタスクで成功時と失敗時を分ける
Task は既定では「すべての先行 Task が成功したとき」に動きます。ドキュメントでは、Task タブの Dependencies にある Predecessors condition を変えると、失敗時だけ動く Task を作れるとあります。CHECK_MARTS の後ろに SQL タスクを 2 つ置きます。
PUBLISH_SUMMARY は既定の条件のまま、日次サマリーを作ります。stg_orders の ordered_at は日付に切り詰めた TIMESTAMP_NTZ なので、TO_DATE で日付型にします。
CREATE OR REPLACE TABLE daily_summary AS
SELECT
TO_DATE(ordered_at) AS order_date,
COUNT(*) AS order_count,
SUM(order_total) AS revenue
FROM orders
GROUP BY 1;

LOG_FAILURE は先行 Task が失敗したときだけ動くようにして、ログテーブルに書き込みます。
INSERT INTO pipeline_run_log (logged_at, graph_name, message)
SELECT
CURRENT_TIMESTAMP(),
SYSTEM$TASK_RUNTIME_INFO('CURRENT_TASK_GRAPH_NAME'),
'CHECK_MARTS failed';
検証時の画面にはプルダウンが無く、Predecessors と Additional conditions だけでした。Additional conditions は Task の WHEN 句に相当するので、ここに SYSTEM$PREDECESSORS_STATE('AT_LEAST_ONE_FAILED') を書いて同じ条件にします。

SYSTEM$PREDECESSORS_STATE に指定できる条件は次の 9 種類です。左がドキュメントの Predecessors condition の表記、右が関数に渡す値です。
| Predecessors condition | 引数の値 | 動く条件 |
|---|---|---|
| All succeeded(既定) | ALL_SUCCEEDED |
すべての先行 Task が成功 |
| All done | ALL_DONE |
成功 / 失敗 / skip / キャンセルを問わず、すべて終了 |
| At least one failed | AT_LEAST_ONE_FAILED |
1 つ以上が失敗 |
| All failed | ALL_FAILED |
すべて失敗 |
| At least one succeeded | AT_LEAST_ONE_SUCCEEDED |
1 つ以上が成功 |
| None failed | NONE_FAILED |
失敗がない(成功か skip) |
| None failed, at least one succeeded | NONE_FAILED_MIN_ONE_SUCCEEDED |
失敗がなく、1 つ以上が成功 |
| All skipped | ALL_SKIPPED |
すべて skip |
| None skipped | NONE_SKIPPED |
skip がない |
Predecessors condition と Additional conditions の両方を指定した場合は、双方を満たしたときだけ Task が動きます。Additional conditions には他の SQL の真偽式も書けます。ドキュメントには SYSTEM$STREAM_HAS_DATA(...) でデータがあるときだけ動かす例があります。SYSTEM$SET_RETURN_VALUE と SYSTEM$GET_PREDECESSOR_RETURN_VALUE で分岐する例もあります。root と finalizer には Predecessors condition を設定できません。
task graph のスケジュールと既定値
Objects » Task graphs から graph を開き、名前、スケジュール、Deploy 後の状態、Task の既定値を設定します。今回は Cron を設定しつつ Desired state は Suspended にして、手動実行で確認します。

今回は必要最低限の項目だけ設定しています。設定項目はほかにも多数あります。


graph を作った時点で、スケジュールなど graph 全体の設定は root Task から graph へ移ります。Task が共通で持つ値(warehouse など)は Task defaults へ移り、Task 個別に指定しなければ既定値が使われます。Overlap policy、Auto retry attempts、Suspend after # of failures、通知用の integration も graph 側の設定です。
生成された YAML
ドロワーの YAML トグルで 1 オブジェクト分、sources/definitions/pipeline.yml を開けば全体の定義を見られます。
生成された YAML
definitions:
- resource: task
name: PB_DEMO.DEV.load_raw_task
definition:
type: sql_file
location:
type: stage_uri
uri: >-
snow://workspace/PB_DEMO.PUBLIC.PB_SHARED/versions/last/JAFFLE_PIPELINE/01_load_raw.sql
- resource: task
name: PB_DEMO.DEV.staging_task
definition:
type: sql_file
location:
type: stage_uri
uri: >-
snow://workspace/PB_DEMO.PUBLIC.PB_SHARED/versions/last/JAFFLE_PIPELINE/02_staging.sql
predecessors:
- PB_DEMO.DEV.LOAD_RAW_TASK
- resource: task_graph_object
name: PB_DEMO.DEV.JAFFLE_PIPELINE
roots:
- PB_DEMO.DEV.load_raw_task
schedule:
type: cron
value: 0 9 * * * UTC
state: suspended
task_defaults:
warehouse: PB_WH
- resource: task
name: PB_DEMO.DEV.marts_task
definition:
type: sql_file
location:
type: stage_uri
uri: >-
snow://workspace/PB_DEMO.PUBLIC.PB_SHARED/versions/last/JAFFLE_PIPELINE/03_marts.sql
predecessors:
- PB_DEMO.DEV.STAGING_TASK
- resource: code_bundle
name: PB_DEMO.DEV.jaffle_pipeline_waqvc
from:
type: asset
asset: project
- resource: task
name: PB_DEMO.DEV.check_marts_task
definition:
type: code_bundle
name: PB_DEMO.DEV.jaffle_pipeline_waqvc
entrypoint: check_marts.py
specification:
bundle:
type: custom
compute_type: warehouse
language: python
compute_options:
runtime_version: '3.11'
properties:
requirements_file: ''
arguments:
- '--min-orders'
- '1'
predecessors:
- PB_DEMO.DEV.MARTS_TASK
warehouse: PB_WH
- resource: task
name: PB_DEMO.DEV.PUBLISH_SUMMARY
definition:
type: sql
value: |-
CREATE OR REPLACE TABLE daily_summary AS
SELECT
TO_DATE(ordered_at) AS order_date,
COUNT(*) AS order_count,
SUM(order_total) AS revenue
FROM orders
GROUP BY 1;
predecessors:
- PB_DEMO.DEV.CHECK_MARTS_TASK
warehouse: PB_WH
- resource: task
name: PB_DEMO.DEV.LOG_FAILURE
definition:
type: sql
value: |-
INSERT INTO pipeline_run_log (logged_at, graph_name, message)
SELECT
CURRENT_TIMESTAMP(),
SYSTEM$TASK_RUNTIME_INFO('CURRENT_TASK_GRAPH_NAME'),
'CHECK_MARTS failed';
predecessors:
- PB_DEMO.DEV.CHECK_MARTS_TASK
warehouse: PB_WH
condition: SYSTEM$PREDECESSORS_STATE('AT_LEAST_ONE_FAILED')
Code Bundle は from: asset で、manifest.yml の assets: project: path: '**/*' が指すプロジェクト全体を Deploy 時に束ねます。SQL file タスクは location.uri に Workspace 上のファイルの絶対 URI を持ち、実行のたびにそこを読みます。
生成直後の URI は versions/live でした。これは編集中の自分のビューを指し、Task のセッションからは見えません。Task が読めるのは公開済みの最新版 versions/last です。Deploy の前に、3 つの SQL file タスクの URI を versions/last に書き換えて Publish します。上の YAML は書き換え後のものです。
注意:
versions/liveのまま Deploy すると、実行時に「Content of the provided location can not be read: version live is not found.」で失敗します。
Deploy する
キャンバス右上の Deploy を押すと、DCM project の plan が実行されます。DCM project オブジェクトがまだ無いので、作ってよいか確認するダイアログが出ます。続く Review planned changes で、作成されるオブジェクトの一覧を確認して Deploy します。


Deploy が完了したら、作成されたオブジェクトを確認します。

Deploy はプロジェクトファイルの内容にアカウントの状態を合わせる操作です。Task を追加すれば作成、設定を変えれば ALTER、キャンバスから消せば次の Deploy で DROP されます。Code Bundle に含まれるファイルを変えた場合も、Deploy で新しいバージョンに更新されます。
実行して監視する
Deployed Project を押すと、Snowsight の DCM project 詳細画面が Objects タブで開きます。task graph を選び、Graph タブの Run Task Graph アイコンで実行します。Suspended のままでも手動実行はできます。

SQL からは EXECUTE TASK GRAPH で実行し、TASK_HISTORY で結果を追えます。task graph に属する Task は単独では実行できません。
EXECUTE TASK GRAPH PB_DEMO.DEV.JAFFLE_PIPELINE;
SELECT name, state, error_message, scheduled_time, completed_time
FROM TABLE(PB_DEMO.INFORMATION_SCHEMA.TASK_HISTORY(
SCHEDULED_TIME_RANGE_START => DATEADD(hour, -1, CURRENT_TIMESTAMP())))
WHERE schema_name = 'DEV'
ORDER BY scheduled_time;

graph 全体は 30 秒で終わりました(X-Small)。raw はその前の実行でロード済みだったため、LOAD_RAW_TASK は短時間です。
SELECT graph_name, state, first_error_task_name, scheduled_time, completed_time
FROM TABLE(PB_DEMO.INFORMATION_SCHEMA.COMPLETE_TASK_GRAPHS(RESULT_LIMIT => 10))
ORDER BY scheduled_time DESC;
失敗したときだけ動く Task を確かめる
CHECK_MARTS の引数を変えて、わざと失敗させます。--min-orders を 999999999 にすると、209 万行では足りないので例外になります。

CHECK_MARTS_TASK が失敗すると、後続のうち PUBLISH_SUMMARY は SKIPPED になり、LOG_FAILURE だけが実行されます。その結果、PIPELINE_RUN_LOG に 1 行入ります(graph_name は完全修飾名)。COMPLETE_TASK_GRAPHS では state が FAILED、first_error_task_name が CHECK_MARTS_TASK でした。

SELECT * FROM PB_DEMO.DEV.PIPELINE_RUN_LOG;

DEV と PROD の 2 ターゲットに展開する
パイプラインは DCM project なので、manifest.yml にターゲットを増やせば同じ定義を別の DCM project オブジェクトへ Deploy できます。今回は同じアカウントの PB_DEMO.PROD を PROD ターゲットにします。
スキーマ名を変数にするため、manifest.yml に PROD ターゲットと templating の env を足します。生成された assets の指定はそのまま残します。
manifest_version: 2
type: DCM_PROJECT
default_target: DEV
targets:
DEV:
account_identifier: <ORG>-<ACCOUNT>
project_name: PB_DEMO.DEV.JAFFLE_PIPELINE
project_owner: PB_ENGINEER
templating_config: DEV
PROD:
account_identifier: <ORG>-<ACCOUNT>
project_name: PB_DEMO.PROD.JAFFLE_PIPELINE
project_owner: PB_ENGINEER
templating_config: PROD
templating:
configurations:
DEV:
env: DEV
PROD:
env: PROD
assets:
project:
path: '**/*'
pipeline.yml では、オブジェクト名と参照名に含まれる PB_DEMO.DEV. をすべて PB_DEMO.{{ env }}. に置き換えます。Task、task graph、Code Bundle の名前だけでなく、roots、predecessors、Code Bundle の参照先も対象です。SQL file タスクの URI は共有 Workspace を指しているので、そのままにします。
- resource: task
name: PB_DEMO.{{ env }}.staging_task
predecessors:
- PB_DEMO.{{ env }}.LOAD_RAW_TASK
definition:
type: sql_file
location:
type: stage_uri
uri: >-
snow://workspace/PB_DEMO.PUBLIC.PB_SHARED/versions/last/JAFFLE_PIPELINE/02_staging.sql
PROD 側の DCM project オブジェクトは先に作っておきます。キャンバスの Deploy は無ければ作るか確認してきますが、下部ペインからの Deploy は「object does not exist, or the project_owner role does not have access」で止まりました。
USE ROLE PB_ENGINEER;
CREATE DCM PROJECT PB_DEMO.PROD.JAFFLE_PIPELINE;
Deploy は Workspace 下部ペインの DCM project コントロールから行います。target メニューを PROD に切り替え、操作メニューの Deploy を実行します。

SQL ファイルの中はテーブル名を修飾していないので、ファイル側は何も変えずに PROD でも動きます。

制限事項・注意点
Public Preview の時点(2026年10月27日)で確認した内容です。
制限事項・注意点
- キャンバスで作れるのは Task、task graph、Code Bundle、DCM project だけです
- Dynamic Tables や view は、同じ DCM project の定義ファイルに
DEFINEで書きます - 1 つの task graph の Task は同じスキーマ、同じ DCM project、同じ所有ロールに限られます
- Task 名とオブジェクト名は完全修飾で書きます
- Code Bundle のソースはプロジェクト内のファイルだけで、ステージは選べません
- プロジェクト外で作った既存の Code Bundle を Task から呼ぶことはできます
- Notebook タスクは compute pool が必須です
- Python ファイルは Specification タブで Compute type を Warehouse にすれば warehouse で動きます
- warehouse での Code Bundle 実行は Public Preview です
- Task の SQL は Task 自身の DB とスキーマをコンテキストに実行されます(
CURRENT_SCHEMA()で確認) - SQL file タスクは実行のたびに Workspace 上のファイルを読みます。Deploy 時に版が固定される Code Bundle とは違います
- Task は SYSTEM ユーザーと所有ロールのセッションで動きます。個人 Workspace のファイルは
GRANT READ ON WORKSPACEを付けても Task から読めませんでした - 共有 Workspace でも Task が読めるのは公開済みの
versions/lastだけです。生成されるversions/liveは書き換えが要ります - SQL file タスクの URI はファイルを置いた Workspace の絶対パスです。プロジェクトを別の Workspace へ移しても自動では変わりません
- root の Task が失敗すると後続はすべて skip になり、At least one failed の Task も動きません(skip は失敗ではない)
- 定義ファイルの実体は
sources/definitions/pipeline.ymlです(ドキュメントはプロジェクト直下のpipeline.ymlと説明) - 下部ペインから別ターゲットへ Deploy するときは、DCM project オブジェクトを先に作っておきます
- 分岐の後ろで合流する Task は、既定の All succeeded のままだと動きません(片方が常に skip のため)
- 合流する Task の条件は None failed, at least one succeeded にします
- Code Bundle を管理する DCM project は
DROP DCM PROJECTだけでは消せません EXECUTE DCM PROJECT <name> PURGEで管理対象を削除してから DROP します- CoCo によるパイプライン生成と
/pipeline-builderスキルは「coming soon」です - task graph は最大 1,000 Task、1 Task あたりの親と子はそれぞれ最大 100 です
最後に
Pipeline Builder では CREATE TASK を書かずに SQL と Python の task graph を組め、plan で差分を見てから Deploy できました。
Task のセッションが Task 自身のスキーマで動く性質と DCM のターゲットを組み合わせると、同じファイルで DEV / PROD を分けられます。
Task 間の依存関係を SQL ではなく画面で設計できる点は、大きな利点だと感じました!
この記事が何かの参考になれば幸いです!









