返回

Snowflake Notebooks 参数传递完整指南

RSS Snowflake Engineering (Medium) AI 逐段翻译 发布 2026-10-16 03:01
DataHot 速览

本文系统讲解如何在所有工作区类型和执行模式下为 Snowflake Notebook 流水线传递参数,并附带可复制的 GitHub 完整代码。文章对比了 %run 共享内存变量、Task DAG 结合 SYSTEM$SET_RETURN_VALUE 等场景,并提到 Code Bundle(原 Notebook Project Object/NPO)命名变更及旧 SQL 语法兼容。适合希望构建生产级 Snowflake Notebook 管道的团队参考。

为什么值得关注:对数据平台/数仓工程师有直接价值:给出 Snowflake Notebook 参数传递的多种实现与生产编排模式,可直接用于构建和维护 Notebook 流水线。

本文目录 16 节
  1. 理解整体格局:
  2. 工作区类型
  3. 执行模式
  4. 场景 1:%run —— 内存中的共享变量
  5. 架构
  6. 场景 2:使用 SYSTEM$SET_RETURN_VALUE 的 Task DAG
  7. 场景 3:多个前驱(扇入)
  8. 场景 4:同一个 Notebook 跨不同 DAG
  9. 经验教训
  10. 1. get_active_session() 在无头模式下失败
  11. 2. SYSTEM$SET_RETURN_VALUE 仅在 Task 内有效
  12. 3. JSON 在 ARGUMENTS 中会被破坏
  13. 4. SYSTEM$GET_PREDECESSOR_RETURN_VALUE 需要短任务名
  14. 5. 编辑 Notebook 不会更新 Code Bundle
  15. 快速参考
  16. 监控

译文

AI 逐段翻译

如何构建生产级 Notebook 流水线,并在每种工作区类型和执行模式下进行参数传递。

所有场景的完整可运行代码均可在 GitHub 仓库 中找到。

本指南涵盖每个场景,并提供可复制到你自己账户中的可运行示例。

理解整体格局:

工作区类型

执行模式

这一区分对下面每一个模式都至关重要。

术语: 本指南使用 Code Bundle(原称 Notebook Project Object/NPO)。Snowflake 重命名了该对象;旧版 NOTEBOOK PROJECT SQL 语法仍受支持。参见 Snowflake Code Bundles。

场景 1:%run —— 内存中的共享变量

最适用于: 同一工作区中的顺序 Notebook。这是最简单的方法。

工作原理: %run 在 同一个 Python 进程中执行另一个 Notebook。所有变量、函数和导入都是共享的。

架构

发送方在其最后一个单元格中设置变量:

OUTPUT_TABLE = f'{TARGET_DB}.{TARGET_SCHEMA}.ORDER_SUMMARY'
ROW_COUNT = session.sql(f'SELECT COUNT(*) AS C FROM {OUTPUT_TABLE}').collect()[0]['C']
STATUS = 'SUCCESS'

编排器调用两者,并可在它们之间进行门控:

%run data_prep.ipynb

assert STATUS == 'SUCCESS', f"Data prep failed: {STATUS}"

%run report.ipynb

接收方直接读取变量——无需导入,无需解析:

print(f"Reading from: {OUTPUT_TABLE}")   # Already in memory
report_df = session.sql(f"SELECT * FROM {OUTPUT_TABLE}")

完整 Notebook 代码:GitHub 仓库 / scenario-1-run

场景 2:使用 SYSTEM$SET_RETURN_VALUE 的 Task DAG

最适用于: 作为独立生产单元运行的独立 Notebook。

每个 Notebook 都是自己的 Code Bundle。Task DAG 将它们串联起来。第一个任务设置返回值,第二个任务读取它并将其作为 ARGUMENTS 传递。

发送方 设置一个以竖线分隔的返回值(不是 JSON——参见陷阱 #4):

return_payload = f"{OUTPUT_TABLE}|{ROW_COUNT}|{RUN_DATE}|{STATUS}"

try:
    session.sql(f"SELECT SYSTEM$SET_RETURN_VALUE('{return_payload}')").collect()
except Exception as e:
    if 'must be called from within a task' in str(e):
        print("Interactive mode - skipping")
    else:
        raise

Task DAG 读取并转发该值:

CREATE OR REPLACE TASK report_task
  WAREHOUSE = 'COMPUTE_WH'
  AFTER data_prep_task
AS
BEGIN
  LET predecessor_output VARCHAR := SYSTEM$GET_PREDECESSOR_RETURN_VALUE(
    'DATA_PREP_TASK'    -- Short name only! See Gotcha #5
  );
  EXECUTE CODE BUNDLE "MY_DB"."MY_SCHEMA"."REPORT_BUNDLE"
    ENTRYPOINT = 'report.ipynb'
    ARGUMENTS = (:predecessor_output);
END;

接收方 解析以竖线分隔的字符串:

user_args = [a for a in sys.argv[1:] if not a.startswith('-') and not a.startswith('/')]

parts = user_args[0].split('|')
source_table = parts[0]    # 'MY_DB.MY_SCHEMA.ORDER_SUMMARY'
row_count = parts[1]       # '5'
run_date = parts[2]        # '2026-09-30'
status = parts[3]          # 'SUCCESS'

变体:用于静态参数的 Task CONFIG

如果在创建任务时参数已知,请使用 CONFIG——它允许你在不重新创建任务的情况下更新参数:

CREATE OR REPLACE TASK nightly_run
  SCHEDULE = 'USING CRON 0 9 * * * America/Los_Angeles'
  CONFIG = $${"run_date": "2026-09-30", "target_db": "PROD_DB"}$$
AS
BEGIN
  LET run_date VARCHAR := SYSTEM$GET_TASK_GRAPH_CONFIG('run_date')::VARCHAR;
  LET target_db VARCHAR := SYSTEM$GET_TASK_GRAPH_CONFIG('target_db')::VARCHAR;
  -- ARGUMENTS accepts only literals or variable references, not expressions,
  -- so concatenate into a variable first
  LET args VARCHAR := :run_date || '|' || :target_db;
  EXECUTE CODE BUNDLE ... ARGUMENTS = (:args);
END;

-- Update without recreating the task
ALTER TASK nightly_run SET CONFIG = $${"run_date": "2026-10-31", "target_db": "STAGING_DB"}$$;
完整 Notebook + SQL 代码:GitHub 仓库 / scenario-2-task-dag

场景 3:多个前驱(扇入)

最适用于: 依赖多个上游 Notebook 输出的 Notebook。

对每个前驱调用一次 SYSTEM$GET_PREDECESSOR_RETURN_VALUE,并用双竖线分隔符组合:

CREATE OR REPLACE TASK report_task
  AFTER data_prep_task, enrichment_task, validation_task
AS
BEGIN
  LET prep_out VARCHAR := SYSTEM$GET_PREDECESSOR_RETURN_VALUE('DATA_PREP_TASK');
  LET enrich_out VARCHAR := SYSTEM$GET_PREDECESSOR_RETURN_VALUE('ENRICHMENT_TASK');
  LET valid_out VARCHAR := SYSTEM$GET_PREDECESSOR_RETURN_VALUE('VALIDATION_TASK');

  LET all_args VARCHAR := :prep_out || '||' || :enrich_out || '||' || :valid_out;
  EXECUTE CODE BUNDLE ... ARGUMENTS = (:all_args);
END;

Notebook 先按双竖线拆分,然后对每个前驱按单竖线拆分:

predecessor_outputs = user_args[0].split('||')
prep_parts = predecessor_outputs[0].split('|')       # TABLE|COUNT|DATE|STATUS
enrich_parts = predecessor_outputs[1].split('|')
valid_parts = predecessor_outputs[2].split('|')
完整 Notebook + SQL 代码:GitHub 仓库 / scenario-3-task-dag

场景 4:同一个 Notebook 跨不同 DAG

关键洞见:Notebook 与 DAG 解耦。每个任务向同一个 Code Bundle 传递不同的参数:

-- DAG 1
CREATE OR REPLACE TASK sales_report AFTER sales_prep AS
BEGIN
  LET val VARCHAR := SYSTEM$GET_PREDECESSOR_RETURN_VALUE('SALES_PREP');
  EXECUTE CODE BUNDLE "MY_DB"."MY_SCHEMA"."REPORT_BUNDLE"
    ENTRYPOINT = 'report.ipynb'
    ARGUMENTS = (:val);
END;

-- DAG 2 (same Code Bundle, different predecessor)
CREATE OR REPLACE TASK marketing_report AFTER marketing_prep AS
BEGIN
  LET val VARCHAR := SYSTEM$GET_PREDECESSOR_RETURN_VALUE('MARKETING_PREP');
  EXECUTE CODE BUNDLE "MY_DB"."MY_SCHEMA"."REPORT_BUNDLE"
    ENTRYPOINT = 'report.ipynb'
    ARGUMENTS = (:val);
END;

Notebook 只是读取 sys.argv[1]——它并不知道也不关心是哪个 DAG 调用了它。

完整 Notebook + SQL 代码:GitHub 仓库 / scenario-4-task-dag

经验教训

这些是让我们耗费数小时的问题。其他地方均无相关文档。

1. get_active_session() 在无头模式下失败

无头执行可能创建多个会话,导致 SnowparkSessionException: More than one active session。

from snowflake.snowpark import session as snowpark_session
try:
    session = get_active_session()
except Exception:
    session = next(iter(snowpark_session._active_sessions))

2. SYSTEM$SET_RETURN_VALUE 仅在 Task 内有效

从工作表或交互式运行时抛出:Function SYSTEM$SET_RETURN_VALUE must be called from within a task。

始终用 try/except 包裹 以便 Notebook 在两种模式下都能工作。

3. JSON 在 ARGUMENTS 中会被破坏

如果你设置 JSON 返回值:

SYSTEM$SET_RETURN_VALUE('{"output_table": "MY_TABLE"}')

ARGUMENTS 解析器会剥离双引号。Notebook 收到的是 {output_table: MY_TABLE}——不是有效的 JSON。

修复:改用竖线分隔格式而非 JSON:

# Sender
return_payload = f"{table_name}|{row_count}|{run_date}|{status}"

# Receiver
parts = sys.argv[1].split('|')

4. SYSTEM$GET_PREDECESSOR_RETURN_VALUE 需要短任务名

-- FAILS:
SYSTEM$GET_PREDECESSOR_RETURN_VALUE('MY_DB.MY_SCHEMA.DATA_PREP_TASK')

-- WORKS:
SYSTEM$GET_PREDECESSOR_RETURN_VALUE('DATA_PREP_TASK')

5. 编辑 Notebook 不会更新 Code Bundle

在工作区更改代码后,你必须点击“Deploy Changes”(日历图标 > Deploy Changes)。否则任务会继续运行旧版本。

快速参考

监控

在 Snowflake Notebook 之间传递参数:完整指南 最初发布于 Snowflake Builders Blog: Data Engineers, App Developers, AI, & Data Science(Medium),人们正在那里通过高亮并回应这个故事来继续对话。

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存