先聊聊增量计算为什么会成为一个问题
数据量一旦涨起来,增量计算基本上是个绕不开的话题。无论是跑数仓报表还是做特征加工,全量重算的成本会越来越让人心疼,于是大家开始琢磨怎么只处理变化的部分。市面上方案不少,有团队基于调度平台自己封装一套增量逻辑,也有团队引入 dbt,用它的 incremental 模型来管理。

真正麻烦的地方往往不在“增量”这两个字本身,而在于增量逻辑和数据的真实变化方式怎么对齐。比如业务表只新增不更新,那一个时间戳就够了;但如果既有更新又有删除,还涉及多表关联和回溯重跑,事情就没那么简单了。很多团队表面上是在选框架,实际上是在选一种对数据变化的治理方式。
增量计算前,先把这几个概念理清楚
增量计算的核心是只处理新增或者被修改的数据,但实现上有几个关键边界必须提前定义:
- 幂等性:同一个增量任务重跑多少次,最终结果都要一致。很多自研方案在这一步就埋了雷,重跑时翻倍或者漏数据。
- 数据变更类型:是 append-only,还是 upsert,还是 delete。不同类型对应的增量策略完全不同。
- 水位(watermark):用来记录已经处理到哪个时间点或哪个业务键,这个值存哪里、怎么更新,直接影响可靠性。
常见误区是把增量计算简单理解为“where 条件里加个时间过滤”。比如有人这样写:
INSERT INTO dw_order
SELECT *
FROM ods_order
WHERE update_time > '2025-01-01 00:00:00'
一段看似合理的 SQL,但如果哪天数据回刷,或者源系统修改了历史数据,这段逻辑就会出问题。因为你没有定义“当前水位是什么”,也没有保证重新执行时不会重复插入。真实的自研增量框架要做的事情远比这条 SQL 多。
自研增量框架:自由度背后的隐性成本
不少团队选择自研,核心动机是灵活。尤其是当数仓模型非常定制化,或者底层引擎是 Hive、Spark、Flink 这类 dbt 原生支持不完善或性能调优要求高的环境时,自己封装一套增量逻辑似乎更有掌控感。
一个典型自研增量框架通常包含:记录水位、根据水位生成 SQL、调度执行、失败重试和数据校验。看起来不复杂,但生产环境里会遇到几个反复出现的坑。
首先是回溯重刷。业务需求变了,要重新计算某一段时间的历史数据,这时候水位要临时调回去,但很多自研框架没有设计“强制重跑”的开关,或者重跑时会和正常的增量任务竞争数据。
其次是多表关联。增量计算不只是一张表的事,事实表和维度表的更新频率往往不同,甚至有的维度表是全量拉取,那么增量任务之间需要依赖顺序。这个依赖关系如果写在调度之外,很难维护。
再者是删除策略。源系统做物理删除,而数仓里已经有历史数据,怎么在增量中感知并同步删除?自研方案一般要在接口里暴露 deleted 标记,或者额外采集 binlog,这已经超出普通 ETL 的范畴了。
下面是一个自研增量框架中常见的状态表设计:
-- 增量状态表
CREATE TABLE job_watermark (
job_name STRING PRIMARY KEY,
last_value STRING,
updated_at TIMESTAMP
);
-- 任务开始前读取水位
SELECT last_value FROM job_watermark WHERE job_name = 'dw_order';
-- 任务成功后更新水位
UPDATE job_watermark
SET last_value = :new_watermark, updated_at = CURRENT_TIMESTAMP
WHERE job_name = 'dw_order';
这个设计本身没问题,但到了实际运行中,很多团队会在这个基础上不断打补丁,比如加上分区重算、加上数据版本号、加上执行历史表。到最后,框架本身的复杂度已经超过原来想解决的问题。
dbt incremental:把增量逻辑标准化
dbt 的核心思路是把数仓里的模型定义成 SQL 文件,由 dbt 负责编译和执行。它的 incremental 策略则是通过 is_incremental() 宏和多个内置策略帮你管理增量逻辑。
用 dbt 写一个增量模型通常是这样:
{{
config(
materialized='incremental',
incremental_strategy='append',
unique_key='id'
)
}}
SELECT *
FROM {{ ref('stage_orders') }}
{% if is_incremental() %}
WHERE updated_at > (SELECT max(updated_at) FROM {{ this }})
{% endif %}
这里 dbt 会自动把第一次执行当作全量,之后的运行自动套上增量条件。你还不需要关心到底是在目标表里做 merge 还是 insert,只要指定策略,dbt 会根据目标数据仓库方言生成对应的 DDL。
dbt incremental 的优势在于它把很多工程细节沉淀成约定。比如通过 unique_key 做 upsert,通过 incremental_strategy='delete+insert' 处理删除分区,通过 incremental_predicates 控制数据裁剪范围。这些能力不是靠写死一段 SQL 就能获得的,而是大量实践后的标准模式。
但 dbt 也不是银弹。它假设你的模型可以相对独立地增量构建,一旦遇到非常复杂的业务逻辑,比如跨多个数据源做合并且需要精确的删除捕获,dbt 也会变得吃力。而且它的性能高度依赖底层数据仓库对 merge、delete+join 的支持能力,像 BigQuery、Snowflake 这些现代数仓支持得很好,但在某些 Hive 版本上,merge 能力很弱,dbt 的优势就发挥不出来。
自研 vs. dbt incremental:一个现实的对比表
| 对比维度 | 自研增量框架 | dbt incremental |
|---|---|---|
| 开发效率 | 需要自己搭框架,落地周期长 | 声明式模型,开箱即用 |
| 维护成本 | 框架逻辑内部维护,容易失控 | 社区版本迭代,但升级时要兼容策略 |
| 性能调优空间 | 完全可控,可针对引擎定制 | 依赖 dbt 内置策略,部分情况下需自定义 |
| 灵活性 | 高度灵活,可处理任何逻辑 | 受限于宏和模型抽象,复杂删除场景较难 |
| 学习曲线 | 需要团队内部统一设计 | 熟悉 SQL 和 dbt 基本概念即可 |
| 适合场景 | 多引擎混合、逻辑高度定制 | 标准化 ELT 流程,以主流数仓为核心 |
这张表不是要告诉你哪个更好,而是让你把选择放到具体的团队约束里去判断。举个例子,一个用 Snowflake 且团队已经跑通 dbt 的部门,完全没必要自研;但一个以 Hive 为主、还涉及大量 Spark 自定义 UDF 的团队,硬上 dbt 可能得不偿失。
决定用哪个之前,先回答这三个问题
我见过几个团队在选型时纠结了很久,最后发现真正决定成败的并不是框架本身,而是下面这几个问题的答案:
- 你的数据变更是否可以被清晰描述成追加、更新、删除中的某一种?如果同时存在好几种,你是否有能力和意愿去维护复杂的增量语义。
- 你所在团队未来是希望更关注业务模型,还是更关注基础架构?dbt 的哲学是让分析师也能维护模型,自研则往往需要更硬核的工程力量。
- 你的数仓平台对增量写入的支持力度如何?先查一下 merge、delete、partition overwrite 这几种能力在所选引擎上的表现。
很多人忽略第三点,结果选了一个看似灵活的框架,实际在跑增量时底层引擎根本不支持高效的更新操作,最后只能退化成每天晚上全表覆盖,那还谈什么增量计算。
回到实际的工程语境里看
举两个常见的场景。第一个场景,一个创业公司的数据团队只有两三个人,数仓搭在 BigQuery 上,每天来自业务库的数据通过 Fivetran 同步到落地层。他们最需要的是快速把核心业务表盘跑起来,如果用自研,光是水位管理和调度就要耗费两周;用 dbt incremental,几个模型定义下去,增量逻辑就自动建立了。这个时候 dbt 是明确的最优解。
另一个场景,某中型公司的数仓底座是 Hive + Spark,数据链路里有很多自定义的向量化解析逻辑,并且要处理来自多个 Kafka topic 的流式数据落湖。dbt 在 Hive 上的支持相对弱,增量策略常常要自己写 SQL 宏去适配,团队最终选择自研了一套基于 Spark 的增量引擎,虽然前期开发成本高,但换来了对每张表处理逻辑的完全掌控,后期运维并没有比使用 dbt 复杂多少。这个选择其实也合理。
再补充一个很容易被忽视的细节:无论用哪种方案,都要考虑模型的演进。自研框架里,一旦表结构发生变化,你可能要手动修改多个任务;dbt 中可以通过 source freshness 和 schema test 帮助你发现变化,但如果你在增量模型里没有正确处理 schema 变更,同样会跑挂。所以不要指望框架能解决所有问题,该有的血缘管理和数据质量监控一样都不能少。
增量计算的几个典型误区
- 误区一:增量一定能省很多计算成本。实际上,增量任务的调度频率、复杂度和数据倾斜会吃掉一部分省下来的成本,有时候全量分区更稳定。
- 误区二:upsert 就是最保险的增量策略。如果传入的数据没有稳定的业务主键,或者主键会复用时,upsert 会造成数据覆盖错误。
- 误区三:自研框架可以轻松适配所有引擎。现实是每个引擎对于事务性写入、分区覆盖的语义都不同,自研一套代码到处跑基本不可能。
这些误区不是理论推演,而是很多团队在复盘时提到的共性痛点。理解了这些,你就明白增量计算框架的选择,本质上是在找一个能够长期承载你数据变更语义的载体,而不只是一段可以重复执行的 SQL。
落到实地的选择建议
如果你的团队还在起步阶段,或者数仓平台对 merge/delete 支持良好,优先试试 dbt incremental。先从一个核心模型开始,建立增量模型规范,让后加入的成员有例可循。要特别注意控制增量模型的写入频率,不要每五分钟刷一次,除非业务真的需要。
如果你已经在自研增量框架且有大量存量任务,不必仓促切换到 dbt。可以把自研框架沉淀成一个独立的模块,明确它的水位存储、重跑机制和错误报警,同时逐步在新模型上引入 dbt 做试点,让两者并存一段时间,再用业务价值去验证取舍。
无论选哪条路,都要为“全量重建”留好后门。增量逻辑再完美,也需要定期用全量数据去校正。你可以设置一个生命周期策略,让关键模型每月全量刷新一次,避免增量偏差越积越大。
数据仓库的增量计算不会只有一个正确答案。它更像一个需要持续审视的架构决策。基于你团队的规模、平台的特性以及对数据质量的容忍度,去调整自研与工具化之间的比例,这才是最务实的姿态。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/832/