返回
RSS Snowflake Engineering (Medium) AI 逐段翻译 精选 发布 2026-08-24 22:01 收录于 08-25

dbt on Snowflake实践:把编排搬进数仓后真正的问题

DataHot 速览

作者回顾了一次在400行DAG定义中手动追踪依赖的下午,指出代码优先的DAG缺乏可视化反馈,调试如同考古。他将真实项目迁移到dbt Projects on Snowflake,结合Workspaces、Task Graphs和Horizon Catalog,评估其承诺是否兑现。文章也列出了迁移后依然耗时的地方,以及编排移入数仓带来的实际体验。

为什么值得关注:dbt与Snowflake原生编排的集成是数据平台选型的重要信号,文中对可维护性和排障体验的复盘对数据从业者有直接参考价值。

本文目录 12 节
  1. Snowflake 上的 dbt Projects、Workspaces 和 Task Graphs,以及当你把编排移入数仓内部时,什么会真正出问题。
  2. 为什么纯代码 DAG 会让你疲惫不堪
  3. 现在在 Snowflake 内部实际运行的是什么
  4. 阅读大型图而不淹没其中
  5. 部署项目
  6. 执行图的一部分
  7. 使用任务图进行调度
  8. 重试而不重新运行所有内容
  9. 这与外部编排器的比较
  10. 迁移前值得了解的局限性
  11. 综合应用
  12. 下一步做的一件事

译文

AI 逐段翻译

Snowflake 上的 dbt Projects、Workspaces 和 Task Graphs,以及当你把编排移入数仓内部时,什么会真正出问题。

我曾花了大半个下午盯着一份 400 行的 DAG 定义,手动追踪依赖关系,因为编排器的图形视图是一团无法点击的框。某个模型在上游某处失败了。找出失败位置意味着要在三个系统中搜索日志。

那个下午是我为在数据实际所在的平台之外构建管道所付出的代价。每一次上下文切换,从 IDE 到编排器界面,到查询编辑器,再到监控仪表板,都是又一个可能出错的地方。

所以当 Snowflake 上的 dbt Projects 于 2025 年 11 月全面可用,并且 DAG 在 2026 年 5 月加入了列级血缘时,我把一个真实项目迁移上去,以验证这一承诺是否兑现。这就是我学到的,包括那些让我付出时间代价的部分。

在我们开始之前,有一个命名说明,因为它一开始让我措手不及。没有一个单一产品叫做“管道构建器”。你实际组装的是四个协同工作的命名组件:dbt Projects on Snowflake(作为 Snowflake 一等对象的项目),Workspaces(带 Git 集成的 Snowsight IDE),Task Graphs(原生调度),以及Horizon Catalog(血缘引擎)。知道真实名称很重要,因为文档就是按照这些名称归档的。

为什么纯代码 DAG 会让你疲惫不堪

我合作过的大多数团队在一个地方构建转换逻辑,在另一个地方进行编排。dbt 模型位于 Git 仓库中。编排器 DAG 位于另一个仓库中。监控则在第三个系统中。管道的可视化表示(如果存在的话)是一个只读的事后产物,它在现实变化之后才更新。

由此产生三个问题,而这三个问题我都遇到过。

  • 在构建时没有视觉反馈。你编写 SQL,推送,触发运行,等待其失败,然后通过文本日志追溯错误。你从未看到正在构建的形态,直到它已经崩溃。
  • 调试变成了考古。当收入事实表在凌晨 3 点失败时,你需要知道是哪个上游模型引发了级联。这意味着要打开编排器,找到任务,读取日志,将其映射回模型名称,打开 IDE,并读取 SQL。回答一个问题需要五个工具。
  • 调度偏离了逻辑。转换图和执行计划位于不同的系统中,具有不同的心智模型。你的 ref() 关系不会自动成为任务依赖。你手动构建该映射,然后手动维护它。

现在在 Snowflake 内部实际运行的是什么

部署到 Snowflake 的 dbt 项目成为一个具有基于角色访问控制的模式级对象,Snowsight 将其血缘渲染为交互式 DAG。

这不是一个附加在旁边的可视化层。该图是根据真实项目元数据计算出来的,并且自 2026 年 5 月起,每个模型节点都可以展开以显示其列,列级血缘由 Horizon Catalog 作为事实来源。同一个 DAG 出现在三个位置:在开发时的 Workspace 中,在已部署项目对象的详情页上,以及在针对特定执行的查询历史中。

关于该模型,有几件事我花了一点时间才内化:

  • 项目对象是有版本的。重新部署会增加一个版本。CREATE OR REPLACE 会将版本标识重置为 version$1 并删除版本别名,这在生产对象上不是你想要的。
  • 没有需要生成或托管的文档站点。项目详情页始终反映当前部署的版本,如果你的项目定义了 overview 文档块,则包括 Markdown 渲染的概览。
  • 没有许可或按用户费用。执行在虚拟仓库上以标准计算费率运行。
  • 两种运行时都可用。Snowflake 提供托管的 dbt Core 运行时,以及自 2026 年 5 月起的基于 Rust 的 dbt Fusion 引擎,该引擎旨在随项目增长而改进编译时间。你可以通过 DBT_VERSION 固定版本,或设置账户级默认值。

模型是物化为表或视图的 SQL 转换。Sources 是项目读取的上游表。Seeds 是项目中作为表加载的 CSV 文件。Tests 是数据质量断言。Snapshots 在某个时间点捕获缓慢变化的维度。Macros 是在模型间共享的可复用 Jinja 逻辑。

阅读大型图而不淹没其中

DAG 一次最多显示 300 个模型,从 2026 年 5 月版本中的 100 个提高而来。这是一个显示限制,而不是项目限制,对于更大的图,你要导航而不是滚动。

有两个控件真正发挥作用。搜索栏按名称查找模型并将图锚定在其上。深度控件然后决定看到多少其相邻节点。锚定默认显示上游两层和下游两层,并且当你锚定不同模型时,深度设置会延续,这是一个小细节,使探索大型项目感觉一致。

选择节点会打开一个侧面板,显示模型类型和文件路径、目标对象的链接、行数和列数、描述(如果项目定义了的话)、上游和下游依赖作为可点击链接,以及源代码和编译后的 SQL。在 Workspace 和 Query History 版本的 DAG 中,该面板还携带来自最近的 manifest.json 和 run_results.json 的运行时数据,因此慢速和失败的模型无需打开日志即可查看。

列级血缘有一个值得规划的先决条件。在节点上选择显示列,选择一列,所有使用它的上游和下游模型都会亮起。这回答了我最常被问到的两个问题:这个数字从哪里来,以及如果我重命名字段会发生什么。问题是,项目必须在列级血缘存在之前至少成功运行过。来自 ref() 声明的模型级血缘立即可用。

部署项目

源位置是我最初弄错的部分,所以这里是准确的版本。dbt 项目对象可以从命名的内部暂存区、工作区 URI、现有的 dbt 项目暂存区或 Git 仓库暂存区创建。不支持内部用户暂存区和表暂存区。

CREATE DBT PROJECT sales_db.dbt_projects_schema.sales_model
  FROM '@sales_db.integrations_schema.sales_dbt_git_stage/branches/main'
  DBT_VERSION = '1.11.11'
  DEFAULT_TARGET = 'prod'
  EXTERNAL_ACCESS_INTEGRATIONS = 'my_external_access_integration'
  COMMENT = 'Generates sales data models.';

DEFAULT_TARGET 设置用于编译和后续运行的配置文件,您仍然可以通过 --target 在每次执行时覆盖它。EXTERNAL_ACCESS_INTEGRATIONS 允许项目从 dbt 包中心或 GitHub 拉取远程依赖项,在对象上声明它意味着 dbt deps 在部署时自动运行。

从命令行:

snow dbt deploy sales_model --source ./my_dbt_project

名称是位置参数,标志是 --source。该命令将本地文件上传到临时暂存区,然后创建对象、添加版本或重新创建它。

两个前置条件,如果遗漏它们,会立刻阻止您。您的项目需要包含 profiles.yml,其中 type: snowflake 指定目标仓库、数据库、模式和角色。account 和 user 字段可以包含任意字符串,因为项目在当前 Snowflake 上下文中运行。更重要的是,目标模式必须已存在项目才能编译或执行,这与 dbt Core 的行为不同,也是我在新环境中遇到的第一个错误。注意:早期版本可能要求;当前行为在执行时自动创建模式。

执行图的一部分

部署后,您可以通过打开模型节点上的菜单直接运行子集。

Execute model 仅运行选定的模型(--select model_name)。Execute model+ 添加所有下游依赖项(model_name+)。Execute +model 添加所有上游父项(+model_name)。Execute +model+ 运行模型及其父项和子项(+model_name+)。

选择任何选项都会打开一个执行对话框,其中预填了匹配的 --select 值,您可以在其中选择操作(run、test 或 build)、选择配置文件目标并在运行前编辑标志。这个预填且可编辑的步骤是我最欣赏的部分,因为它教您选择器语法,而不是隐藏它。

相同的选择器在 SQL 中有效:

EXECUTE DBT PROJECT my_dbt_project
  ARGS = 'build --select +stg_customers+ --target dev';

这将对 stg_customers 及其完整谱系链运行 build(包括测试),针对 dev 目标。

使用任务图进行调度

项目执行是一次性操作。要调度它,您可以将其包装在任务中,并将任务链接成图。

最重要的约束:您不能在这里使用无服务器任务。运行 EXECUTE DBT PROJECT 的任务必须指定用户管理的仓库。如果您的标准模式是无服务器任务,这是不适用的一处。

-- Root task: staging layer, hourly, on a user-managed warehouse
CREATE OR REPLACE TASK refresh_staging
  WAREHOUSE = TRANSFORM_WH
  SCHEDULE = 'USING CRON 0 * * * * America/New_York'
  TASK_AUTO_RETRY_ATTEMPTS = 2
  SUSPEND_TASK_AFTER_NUM_FAILURES = 3
AS
  EXECUTE DBT PROJECT my_dbt_project
    ARGS = 'run --select tag:staging';
-- Dependent task: marts run after staging succeeds
CREATE OR REPLACE TASK refresh_marts
  WAREHOUSE = TRANSFORM_WH
  AFTER refresh_staging
AS
  EXECUTE DBT PROJECT my_dbt_project
    ARGS = 'run --select tag:marts';
-- Finalizer: runs once the graph completes, whatever the outcome
CREATE OR REPLACE TASK notify_on_completion
  WAREHOUSE = TRANSFORM_WH
  FINALIZE = refresh_staging
AS
  CALL SYSTEM$SEND_EMAIL(...);

这两个重试参数执行不同的工作,我自己的笔记草稿把它们混淆了。TASK_AUTO_RETRY_ATTEMPTS 控制失败的图运行重试自身的次数。SUSPEND_TASK_AFTER_NUM_FAILURES 控制在图暂停之前容忍的连续失败次数。一个是恢复,另一个是断路器。

任务创建时处于暂停状态。首先恢复子任务,然后是根任务,或使用 SYSTEM$TASK_DEPENDENTS_ENABLE 遍历树。根任务在其子任务仍暂停时恢复将简单地跳过它们,这会产生一个几乎没做任何事情的绿色运行。

持续时间趋势视图在最小-最大范围内绘制中位数线,因此稳定、漂移和异常运行一目了然。

监控位于 Snowsight 的“任务”页面。任务图显示先前运行计数器,您可以悬停查看最近状态,以及持续时间趋势图,该图在您选择的日期范围内绘制最小-最大带内的中位运行持续时间。该范围过滤器默认为最近 7 天,并适用于运行计数器和趋势,而不是图本身。您还可以按上次运行状态过滤,按数据库和模式过滤(在大型账户上推荐,因为它减少加载时间),并按根任务名称搜索。

重试而不重新运行所有内容

这是本地编排对我来说真正有用的地方。

-- Restart from wherever the last run failed
EXECUTE TASK refresh_staging RETRY LAST;
-- Or retry one specific historical graph run
EXECUTE TASK refresh_staging
  RETRY GRAPH RUN GROUP '33af30ab-f960-4ba0-a5c8-d132e5623468';

RETRY LAST 创建一个从失败任务开始的新图运行。每个 FAILED 或 CANCELED 任务立即重新执行,子任务在其前驱成功时被调度,新运行共享原始 GRAPH_RUN_GROUP_ID,并带有递增的尝试次数。

必须满足三个条件,其中第二个条件是最棘手的:

  • 上次图运行必须处于 FAILED 或 CANCELED 状态。
  • 自上次运行以来,任务图不得被修改。
  • 该运行的第一次尝试必须发生在最近 14 天内。

第二个条件意味着自然的直觉——修复失败的任务然后从停止的地方重试——行不通。编辑图会使重试失效。您修复底层数据或代码,重新部署项目对象,并开始新的运行。

这与外部编排器的比较

外部编排器将 DAG 作为 Python 或 YAML 保存在单独的仓库中,在单独的 UI 中可视化(通常落后于现实),从外部触发进入 Snowflake 的执行,依赖单独的工具进行列级血缘,并且需要工作节点、元数据数据库和队列。Snowflake 上的 dbt 项目将 DAG 作为已部署的 Snowflake 对象,并具有交互式 Snowsight 图,通过 EXECUTE DBT PROJECT 原生执行,通过 Horizon Catalog 提供列级血缘,通过用户管理的仓库上的任务进行调度,通过运行历史和持续时间趋势进行监控,在 14 天内使用 RETRY LAST 重试,并且不需要基础设施。决定性的行是最后一行:Snowflake 之外的步骤由外部编排器支持,本机不覆盖。

我在这里想谨慎一些,因为诚实的答案不是“拆掉你的编排器”。如果你的管道跨越多个系统,除了仓库工作之外还涉及API调用、文件传输或Spark作业,那么外部编排器仍然有其价值,Snowflake Tasks可以成为其中的一步。真正改变的是管道确实是“转换已经在Snowflake中的数据”这种情况。对于这种形态的工作,不再需要一整层基础设施和大量的上下文切换。

前后对比:一个独立的编排栈,拥有自己的 worker、元数据存储和监控,对比一个包含项目、任务图和可观测性的 Snowflake 边界。

迁移前值得了解的局限性

诚实比停滞的迁移更廉价,所以这里是我会首先对照你自己的项目进行检查的内容。

  • 不支持 dbt Cloud 项目。 只支持 dbt Core 项目,且必须与支持的 dbt Core 版本兼容。
  • 现有的 env_var() 调用必须重命名为使用 DBT_ 前缀(例如,my_schema → DBT_MY_SCHEMA),并在 env.yml 中声明。像以前一样在模型中使用 env_var(‘DBT_MY_SCHEMA’)。如果你的项目依赖环境变量,这是真正的迁移工作,不是配置调整。
  • 无服务器任务无法运行 EXECUTE DBT PROJECT。 需要用户管理的仓库。
  • 最多 20,000 个文件 每个工作区中的 dbt 项目,包括目录树中的所有内容,如 target、dbt_packages 和 logs。
  • profiles.yml 中的目标 schema 必须事先存在,这不同于 dbt Core。
  • 一次显示 300 个模型。 更大的项目需要搜索和深度控制,而不是一个巨大的视图。
  • 列级血缘需要先成功运行一次。
  • 重试因图编辑而失效,如上所述。
  • 不编排 Snowflake 之外的任何内容。 API 调用、文件下载和外部计算仍需要其他工具。

关于历史保留,更正一个我见到的重复数字:Information Schema 表函数保留 7 天,但等价的 Account Usage 视图保留 365 天。如果你想要长期运行分析,请查询 Account Usage 视图,而不是按 7 天周期导出。

综合应用

完整的生命周期用一句话概括:在 Workspace 中使用 Git 开发,作为项目对象部署,读取 DAG,执行切片,使用任务图调度,然后监控运行历史和执行时长趋势。

这是我常打开的参考:

-- Deploy from a Git repository stage
CREATE DBT PROJECT mydb.myschema.my_project
  FROM '@mydb.integrations.my_git_stage/branches/main'
  DEFAULT_TARGET = 'prod'
  EXTERNAL_ACCESS_INTEGRATIONS = 'my_ext_integration';
-- Add a version instead of replacing the object
ALTER DBT PROJECT mydb.myschema.my_project
  ADD VERSION FROM 'snow://workspace/user$.public."my_workspace"/versions/live';
-- Execute
EXECUTE DBT PROJECT my_project ARGS = 'build';
EXECUTE DBT PROJECT my_project ARGS = 'build --select +my_model --target dev';
-- Schedule (user-managed warehouse is required)
CREATE TASK hourly_refresh
  WAREHOUSE = TRANSFORM_WH
  SCHEDULE = 'USING CRON 0 * * * * UTC'
  TASK_AUTO_RETRY_ATTEMPTS = 2
AS EXECUTE DBT PROJECT my_project ARGS = 'run';
ALTER TASK hourly_refresh RESUME;   -- tasks are created suspended
-- Retry from the point of failure
EXECUTE TASK hourly_refresh RETRY LAST;
-- Inspect
DESCRIBE DBT PROJECT my_project;
SHOW DBT PROJECTS IN DATABASE mydb;
SELECT * FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY())
  WHERE NAME = 'HOURLY_REFRESH'
  ORDER BY SCHEDULED_TIME DESC;
一份涵盖部署、执行、调度、重试和检查命令的一页参考。

下一步做的一件事

打开 Snowsight,使用 snow dbt deploy <name> --source <path> 部署现有的 dbt 项目,运行一次以便 Horizon Catalog 计算列血缘,然后打开 DAG,展开一个下游模型的列,并点击单个列。

看着所有上游和下游模型触及该列并亮起的那一刻,价值就不再是理论上的。我花了大约十五分钟,大部分时间用于修复尚不存在的目标 schema。

你的数据管道不应该需要 YAML 博士学位 最初发布于 Snowflake Builders Blog: Data Engineers, App Developers, AI, & Data Science 在 Medium 上,人们在那里通过强调和回应这个故事继续对话。

这篇内容对你有用吗?

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

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