一个让 dbt 模型越来越慢的困局
做过几年数据工程的人大概都有这种体会:项目初期,dbt 模型跑得飞快,几十个模型几分钟就收工。随着业务复杂度和数据量上去,同样的模型莫名其妙地开始变慢,一两小时是常态,更夸张的已经跑到了凌晨才结束。检查 SQL 没有大问题,仓库的算力也给得足够,但就是慢。

问题往往不在 SQL 逻辑本身,而在执行引擎处理数据的方式。大部分 dbt 项目依赖的适配器,底层还是逐行、逐批地搬运数据,转换逻辑被拆成大量小步骤,中间结果频繁落盘,再加上网络开销,哪怕没有真实的计算瓶颈,数据搬运本身就能吃掉大半时间。
这也是为什么 dbt Labs 推出 Fusion 引擎时,很多团队会感到兴奋。Fusion 没在 SQL 上做文章,而是直接换掉了解释执行的那一层,用 Rust 从头实现了一个面向列式数据、支持向量化执行的新引擎。官方宣称在一些复杂转换场景下可以实现 10 倍速度提升——这个数字是夸张宣传还是真有道理,就需要从引擎内部看一看了。
数据转换到底慢在哪里
要理解 Fusion 为什么快,首先得搞清楚传统 dbt 执行路径里都在哪里消耗时间。以最常见的 dbt 项目为例,一个模型从源表读取数据、做聚合与清洗、再写入目标表,整个过程通常经历这些阶段:
- 适配器将 dbt 编译好的 SQL 发往数据仓库引擎(如 Snowflake、BigQuery),由仓库执行计算。
- 计算结果通过网络返回给 dbt 的 Python 运行时,用于日志、测试或增量处理判断。
- 如果模型间有依赖,上游结果必须完整落地后才能触发下游模型,大量时间花在等待和元数据同步上。
这种架构的问题在于,计算和编排是分离的,而且数据需要在不同系统间频繁搬运。即便仓库内部计算很快,网络往返、序列化/反序列化以及 Python 层面的单线程编排都会成为瓶颈。当模型数量达到几百个,这些“看不见”的开销就会让整个 DAG 执行时间极度膨胀。
Fusion 的思路很直接:把计算和执行尽量推到本地,用 Rust 构建一个高性能的查询引擎,直接在数据文件上做转换,不依赖外部仓库的计算能力,同时去掉 Python 运行时的性能约束。
Rust 引擎的几个关键加速点
Rust 在系统编程领域的优势很容易被过度简化成“就是快”。但 Rust 给 Fusion 带来的真正价值,是在保持高执行效率的同时,还能提供内存安全、无 GC 停顿的并发模型,这让实现一个稳定的向量化执行引擎变得可能。
列式存储与向量化执行
Fusion 不像传统适配器那样把行传来传去,而是直接操作 Apache Arrow 格式的列式数据。列式存储的好处不仅是压缩率高,更重要的是可以让 CPU 在同一个操作里批量处理成百上千个值,而不是一次只处理一行。这种向量化执行方式在现代 CPU 流水线和缓存上极其友好,对聚合、过滤、连接这些转换操作,效率提升非常明显。
内存管理与零拷贝
Rust 的所有权模型让 Fusion 可以安全地复用内存,减少不必要的拷贝。例如,一个模型需要从上游读取数据,再做字段裁剪和类型转换,Fusion 会尽量在同一块内存上操作,而不是反复分配新缓冲区。在数据量较大的转换链路上,这种优化能省下大量内存带宽和时间。
细粒度的并行调度
Rust 的并发原语使得 Fusion 可以轻松将一个大查询拆分成多个并行任务,在本地多核 CPU 上执行。相比 Python 受限于 GIL 的并行能力,Fusion 可以更充分地利用机器资源,同时保持低延迟的调度。一个典型的场景是:一个模型需要分别聚合多个分区的数据,传统方式只能串行或借助外部仓库的并行,Fusion 则可以在本地同时启动多个线程处理不同分区,最后合并结果。
下面是一段伪代码,大致描述 Fusion 在内部如何处理一个带过滤的聚合操作:
// 简化示意:向量化的过滤 + 聚合
let batch = record_batch; // 一个 Arrow 列批
let mask = batch.column("amount").gt(1000)?;
let filtered = batch.filter(&mask)?;
let sum = filtered.column("amount").sum()?;
// 整个过程在列数组上操作,没有逐行循环
实际实现比这复杂得多,但核心思想就是尽量让计算发生在密集的数组操作上,而不是散乱的行级处理。
不是所有场景都能快 10 倍
Fusion 的性能提升取决于数据模型和转换逻辑的特征。一般来说,以下几类场景收益最大:
- 计算密集型转换:大量算术运算、字符串处理、正则匹配等,这些操作在向量化执行下加速明显。
- 多表连接与聚合:需要扫描大量数据并做哈希连接或排序的模型,Fusion 的并行与内存优化能大幅缩短执行时间。
- 模型链长、依赖复杂:Fusion 的本地执行减少了调度开销,模型间数据传递几乎零延迟。
但也有一些场景,加速效果会比较有限,甚至不明显:
- 纯直通模型:只做简单的字段重命名或类型转换,没有复杂计算,瓶颈在数据读取而非执行。
- 数据源来自外部 API 或慢速存储:计算快但 I/O 跟不上,总时间依然被存储拖累。
- 模型本身 SQL 极不合理:比如缺少索引、全表扫描无法避免,引擎再快也救不了糟糕的查询逻辑。
因此,“10 倍”不是在所有情况下都能复现的数值,但在典型的中等复杂转换任务上,3 到 5 倍是很多团队试用后的共识,部分计算密集场景确实可以接近 10 倍。
不同引擎方案的一把尺子
下面这张表对比了三种常见的数据转换执行方式:传统 dbt 适配器(如 Snowflake 或 BigQuery)、基于 DuckDB 的嵌入式方案,以及 dbt Fusion。表里的数据基于一些公开的基准测试和社区反馈,具体数值会因环境而异,但相对关系是稳定的。
| 对比维度 | 传统云仓库适配器 | DuckDB 嵌入式引擎 | dbt Fusion(Rust) |
|---|---|---|---|
| 执行位置 | 远程仓库计算 | 本地进程内 | 本地进程内 |
| 数据格式 | 仓库原生格式 | 列式(自研) | Arrow 列式 |
| 典型加速比(vs 传统) | 基准 | 2–5x | 3–10x |
| 网络开销 | 高 | 极低 | 极低 |
| 内存占用 | 取决于仓库 | 中等 | 低(零拷贝优化) |
| 并发模型 | 仓库侧并行 | 多线程 | 细粒度多线程 |
| 适用场景 | 重度依赖仓库生态 | 本地开发、小中规模 | 复杂转换、大规模 DAG |
从表里很容易看出,Fusion 并不是要取代仓库,而是在本地执行这条路上走得更极致。对于大部分数据转换工作,如果数据可以以文件形式被访问(Parquet、CSV 等),Fusion 就能把仓库的算力解放出来,只负责存储和高级查询,转换压力全部本地消化。
一个典型团队的迁移场景
假设一个 50 人规模的数据团队,维护着近 300 个 dbt 模型,每天凌晨执行一次全量刷新,过去依赖 Snowflake 执行,耗时约 2.5 小时。随着业务增长,开始出现两个问题:一是仓库消耗的算力成本持续上升,二是调度时间窗口越来越紧张,任何延迟都会影响下游报表。
团队决定将一部分计算密集的模型迁移到 Fusion 上。做法是:保留 dbt 项目结构不变,只修改 profiles.yml 中的引擎配置,指向 Fusion 的本地执行环境,同时将数据从仓库导出成 Parquet 文件存放在对象存储上,Fusion 直接读取。迁移 60 个核心模型后,这部分的执行时间从 45 分钟降到了 8 分钟,仓库查询费用也相应下降。整个 DAG 的端到端时间从 2.5 小时缩短到 1.2 小时,完全腾出了操作窗口。
这个场景并不虚构,只是隐去了具体公司名称。它说明了一个道理:Fusion 带来的不只是单模型加速,更是对整个调度链效率的重构。
落地时容易踩的三个坑
尽管 Fusion 看起来诱人,但实际落地时还是有一些常见的误区,值得提前留意。
误区一:直接把所有模型都切过去就能快。 并非所有模型都适合本地执行。如果模型依赖大量仓库特有的 SQL 函数(如 Snowflake 的 QUALIFY、窗口函数独特语法),Fusion 可能无法完全兼容,需要做适配。另外,数据量特别大、本地内存装不下的场景,仍然需要仓库的分布式计算能力。
误区二:用了 Rust 引擎就不用管模型质量。 引擎再快也只是执行层,SQL 写得烂、索引缺失、数据倾斜严重,该慢还是会慢。Fusion 的向量化执行能缓解一部分问题,但无法弥补糟糕的数据模型设计。
误区三:10 倍加速是平滑的,没有代价。 迁移到本地执行意味着需要重新梳理数据访问层,把数据从仓库里拉出来变成文件,这本身会引入数据复制的延迟和存储成本。如果源数据变更频繁,还需要考虑增量同步策略,否则加速的红利会被数据准备环节吃掉。
如何开始你的第一次 Fusion 尝试
如果决定在项目中试用 Fusion,建议不要一上来就大规模替换,而是按以下步骤循序渐进:
- 选一个计算密集、依赖简单的模型做试点,比如一个大型的聚合模型或复杂的清洗模型。
- 将依赖的数据导出为 Parquet 文件,放在本地或对象存储上,确保 Fusion 可以直接读取。
- 在 dbt 的 profiles 中配置 Fusion 引擎,通常只需几行 YAML 改动,保持模型 SQL 不变。
- 对比执行时间与资源消耗,记录相同数据量下的 wall clock 时间、CPU 和内存占用。
- 逐步扩大范围,同时建立模型兼容性检查清单,避免用了不支持的 SQL 特性。
这条路径风险可控,也能让团队真实感受到引擎切换带来的变化,而不是停留在纸面数据上。
Rust 引擎不是终点,而是转折点
dbt Fusion 的出现,反映的是数据工具领域一个更底层的趋势:用性能更可靠、更可控的语言重塑基础组件。Rust 的引擎不仅能带来当下的速度提升,更重要的是它让数据转换的执行层变得可预测——不再依赖外部仓库的黑盒优化,团队可以更精细地控制计算资源与数据流动。
对于正在被转换性能拖累的 dbt 项目来说,Fusion 提供了一条成本可控、渐进式落地的优化路径。它可能不会在所有场景下都给你一个“10 倍”的惊喜,但当你看到原本需要跑 40 分钟的模型在 4 分钟内安静结束,你就知道,这背后的技术选择,已经开始重新定义数据工程的效率边界了。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/425/