dbt on Snowflake实践:把编排搬进数仓后真正的问题
DataHot 速览
作者回顾了一次在400行DAG定义中手动追踪依赖的下午,指出代码优先的DAG缺乏可视化反馈,调试如同考古。他将真实项目迁移到dbt Projects on Snowflake,结合Workspaces、Task Graphs和Horizon Catalog,评估其承诺是否兑现。文章也列出了迁移后依然耗时的地方,以及编排移入数仓带来的实际体验。
为什么值得关注:dbt与Snowflake原生编排的集成是数据平台选型的重要信号,文中对可维护性和排障体验的复盘对数据从业者有直接参考价值。
本文目录 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 上,人们在那里通过强调和回应这个故事继续对话。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏