这是【数据库研究前沿】系列的第 23 篇。“流批一体”这个词在工业界被用得极多,但多数语境下只是”同一份作业能跑实时也能跑历史回放”的工程口径。本文关注的是另一个层面的流批一体:是否存在一个数学上干净、系统上可实现的模型,让”查询随输入变化而增量更新”与”查询在某一时刻的批量结果”是同一个语义。这个问题关乎一条技术路线能否长期站住脚——如果流结果与批结果不代数等价,那”流批一体”只是市场口号,不是工程事实。
这条线真正的奠基性工作,是 2023 年 VLDB 的 DBSP: Automatic Incremental View Maintenance for Rich Query Languages(Budiu、McSherry、Chajed、Tannen 等)。它的前身是 Frank McSherry 2013 年之后在 Microsoft / ETH / Materialize 这一串研究里发展出的 Timely Dataflow 与 Differential Dataflow。DBSP 把 differential dataflow 背后的代数用一套紧凑的工具重写:流是 Z-set 值的序列;查询算子是对这些流的线性/双线性函数;增量维护变成求一个”微分算子”。
2024–2025 年三家把这条理论搬进产品的公司,已经形成了明显区分的三种工程风格:Materialize 把 differential dataflow 直接封装成 PostgreSQL 协议外壳;RisingWave 从零实现了一个符合 SQL 标准的流式数据库,状态走对象存储;Feldera 是 DBSP 作者团队直接推出的产品,围绕 DBSP 编译器与循环电路模型构建。
本文上半讲理论:IVM 历史、differential dataflow、DBSP 的
Z-set 与线性化、三家的架构差异;下半讲工程:一个用纯 Python
标准库实现的 Z-set 增量 join demo(demo/ 目录),并在最后划清与
Flink、Kafka Streams 的能力边界。
版本说明 本文写作窗口为 2026 年 Q2:Materialize 0.120、RisingWave 2.1、Feldera 0.30、Apache Flink 1.19、Kafka Streams 3.7、Timely Dataflow / Differential Dataflow 0.13 系列。版本细节可能继续演进,以各家官方文档为准。
一、IVM 二十年:从 Counting Algorithm 到流式数据库
1.1 IVM 问题的朴素陈述
增量视图维护(Incremental View Maintenance, IVM)的问题陈述很直接:
给定一个视图
V = Q(R₁, R₂, …, Rₙ)与底表的变更ΔR_i,在不重新扫全表的前提下,求出ΔV使得新视图V' = V ⊕ ΔV。
这里的 ⊕
不是简单加法:既要支持”新增一条”,也要支持”撤回一条”(delete
/ update 的撤回端)。这一步看似平凡,但它几乎是 IVM
五十年研究的全部技术难点所在。
1.2 为什么”撤回”是技术难点
传统查询语义里,“插入”和”删除”看起来对称;但一到增量语义就不对称。原因是很多常见算子对”撤回”有长程依赖:
count(distinct x):删除一条可能让某个 distinct 值彻底消失,也可能不消失;要能分辨就得维护每个值的引用计数。max(x) / min(x):删除当前极值后要能回溯到”第二大”;需要维护一个排序结构。- 嵌套子查询:撤回外层的一条记录可能要撤回内层产生的多条记录。
这让朴素”反向执行算子”的做法行不通。IVM 的难点本质是:“删除” 要能被算子链整体一致地撤销。DBSP 通过 Z-set(允许负重数)把这个问题变成”对一个代数群做加法”——一步到位地把撤回变成正常运算。
1.3 早期工作:Counting Algorithm 与 Magic Sets
- Blakeley、Larson、Tompa 1986 的 Efficiently Updating Materialized Views 给出了 SPJ(Select-Project-Join)视图的增量公式;
- Gupta、Mumick、Subrahmanian 1993 的 Maintaining Views Incrementally(SIGMOD)把它扩展到 SPJU 与 aggregation;
- Counting Algorithm(Gupta、Mumick 1995)给 Datalog 递归视图引入”行级引用计数”,解决”是否真的该撤回”的问题;
- Magic Sets 技术(Beeri、Ramakrishnan 1987 及后续)让递归查询可改写为能以增量形式求解的结构。
这些工作共同奠定了“状态里带重数 / 带 delta 签名”这件事的必要性。
1.3 为什么工业界直到 2010 后才真正用起来
原因其实很实在:
- 大部分生产 OLTP 数据库的物化视图(Materialized View)是惰性刷新或全量重算;
- 数据仓库主要跑批,日级别刷新就够;
- 真正把”SQL 视图的增量、低延迟刷新”做成产品需要一个同时具备分布式数据流引擎、精细状态管理、事务快照读的执行层——这件事到 Spark、Flink 出现后才有基础。
Flink 的 Dynamic Table / Changelog Stream(Fabian Hueske 等 2017 起)第一次把”流与表双向等价”放进工程语义。但 Flink 内部并不是 DBSP——它的一致性、撤回语义、状态管理都有自己的实现选择(见 §6)。
1.4 近十年的两条主线
2013 年之后,IVM 的研究分成两条显著不同的主线:
- Differential Dataflow(McSherry,
Murray, Isaacs, Isard 2013 SOSP 的 Naiad,后续剥离为库):以
(data, time, diff)三元组为统一表示,所有算子都做”多维时间上的增量”; - DBSP(VLDB 2023):用 Z-set 与线性/双线性算子把 IVM 刻画为一个小代数系统,线性算子天然满足”先差分再应用 = 先应用再差分”,非线性算子用 bilinearization 拆成可增量的形式。
两条线不是并列的:DBSP 在很大程度上是”differential dataflow 的代数化、最小化版本”。理解 DBSP 基本等于理解了 differential dataflow 的核心思想,外加更干净的语言。
1.5 为什么”流批一体”这个词被滥用
“流批一体”在工业语境里常有三种完全不同的意思:
- API 统一:同一套 SQL/DataFrame API 既能写批作业也能写流作业(如 Flink SQL、Spark Structured Streaming);
- 存储统一:批和流读同一份底层数据(如 Iceberg/Delta/Hudi + Flink 的组合);
- 语义统一:批查询的结果和流式视图的结果在任意时间点代数等价(DBSP 的目标)。
只有第三种才是”真正的流批一体”。前两种是工程便利,不是数学等价。DBSP 相关产品(Materialize / RisingWave / Feldera)是目前在第三种意义上做到最彻底的工业实现。
二、Differential Dataflow:时间是多维的
2.1 Timely Dataflow 的 progress tracking
Timely Dataflow(Murray 等 SOSP 2013 的 Naiad)的核心抽象是:
- 每条消息带一个多维时间戳(loop counter、epoch、子 epoch);
- 每个算子能声明”它后续还会产生不晚于某个时间戳的消息”;
- 系统用 progress frontier 做全局推理,保证循环(迭代计算)也能正确终止与递增。
这套 progress tracking 是后来 differential dataflow 能做递归、迭代查询增量化的基础。
2.2
Differential Dataflow 的
(data, time, diff)
Differential Dataflow
把每一条数据都换成一个三元组
(record, time, +1 或 -1 等整数)。两条关键观察:
- 所有 SPJ、group by、连接、递归查询都可以被写成对这些三元组集合的线性或近线性操作;
- 在这个表示下,“新输入来了”就是增加一批正负三元组;“查结果”就是 fold 所有三元组到当前时间点。
这是一个非常直接的”把 IVM
归约到线性代数”的操作。McSherry
团队随后把它打磨成一个工业级 Rust
库(differential-dataflow),支持任意维度时间戳、任意
semiring 的 diff、并行执行。
2.3 什么时候 differential 是贵的
在两个场景下 differential dataflow 会比朴素全量更贵:
- 底表 delta 率 >= 50%:此时做增量的 bookkeeping 比直接重算还贵;
- 很多非线性聚合的高基数 group by:每次都要维护大量 per-group 状态。
这是为什么 Materialize 的最佳场景是大底表 + 小比例 delta + 低基数或可预筛选的 group by。
三、DBSP:把 IVM 变成一个代数练习
3.1 流:Z-set 值的序列
DBSP
的基础对象是流(stream):一个从离散时间到
Z-set 的函数 s : ℤ → Z⟨A⟩,其中
Z⟨A⟩ 是以 A
为元素、以整数为重数的集合(元素可以”负存在”)。
Z-set 上的加法、差、连接(join 是双线性)、投影、选择都有自然定义。关键是:Z-set 构成一个 abelian group。这让”撤回”不再是特殊情况,而是”加一个负元素”。
3.2 线性算子与增量的等价
DBSP 定义了两个算子:
- 延迟算子
z⁻¹ s[t] = s[t−1]; - 积分算子
I(s)[t] = Σ_{i ≤ t} s[i]; - 微分算子
D(s)[t] = s[t] − s[t−1]。
有了这三个算子,所有线性算子
L(如 select、project、union)天然满足:
D(L(I(s))) = L(s)
这句话展开看就是:如果我们把输入看成 delta 流,把算子作用在 delta 上的结果就是输出的 delta 流。线性算子因此零修改就能增量化。
3.3 双线性算子的 bilinearization
连接(join)不是线性的,但它是双线性的:
(a + b) ⋈ c = (a ⋈ c) + (b ⋈ c)
a ⋈ (b + c) = (a ⋈ b) + (a ⋈ c)
DBSP 给出一个把 bilinear 算子增量化的标准公式:
Δ(a ⋈ b) = Δa ⋈ b_old + a_old ⋈ Δb + Δa ⋈ Δb
其中 a_old、b_old
是上一时刻的积分。这就是 §五 demo 要手写实现的那条公式。
3.4 递归:不动点算子 + 嵌套时间
递归视图(如 Datalog reachable)在 DBSP
里表达为:
R = μ X. (Base ∪ Step(X))
DBSP 引入嵌套流(stream-of-streams)和 δ⁰ / ∫(lifted delay / integration)来让递归也能增量化。最终结论是:一整类丰富的 SQL/Datalog 查询(SPJ + aggregation + recursion + window)都有确定性的增量实现——这正是论文标题 “for Rich Query Languages” 的意思。
3.5 与 Differential Dataflow 的关系
一句话:DBSP 是 differential dataflow 背后的代数。DBSP 提供了更紧凑的证明与更小的算子集合,differential dataflow 提供了工业级的并行 runtime。Feldera 实际产品上把 DBSP 编译成 Rust,然后跑在类 timely 的执行器上。
3.6 与 CALM 的对话
DBSP 的每个线性算子都是 CALM 意义下的单调函数;DBSP 的”撤回”是通过把 abelian group 升级到 Z-set 得到的——这是比 CRDT 的 join-semilattice 更强的代数结构(有加法逆元)。两者的关系在 VLDB 2025 Keep CALM and CRDT On 里被明确讨论(见 CRDT 与 CALM 再读)。
3.7 DBSP 与 differential privacy / probabilistic 的可能结合
DBSP 的 delta 流本质上是一组可组合的”输入变化”,这让它天然适合加噪声(differential privacy 的 release mechanism)或用 sketch 替换精确聚合。相关工作(近两年的学术方向)还在早期,但思路清晰:把 Z-set 的加法同态地映射到一个近似语义的半群,上层 SQL 不需要改。这是一个值得长期关注的方向。
3.8 一个小的证明直觉
为什么
Δ(a ⋈ b) = Δa ⋈ b_old + a_old ⋈ Δb + Δa ⋈ Δb?用双线性展开直观:
new_a ⋈ new_b
= (a_old + Δa) ⋈ (b_old + Δb)
= a_old ⋈ b_old + a_old ⋈ Δb + Δa ⋈ b_old + Δa ⋈ Δb
= (a_old ⋈ b_old) + [a_old ⋈ Δb + Δa ⋈ b_old + Δa ⋈ Δb]
把 a_old ⋈ b_old 移到左边就是
Δ(a ⋈ b) = a_old ⋈ Δb + Δa ⋈ b_old + Δa ⋈ Δb。三项一个都不能少——第三项
Δa ⋈ Δb 正是 “两边同时发生变化”
的贡献。这个展开推而广之到 n 路 join 就是 DBSP 里的
bilinearization 公式链。
四、Materialize / RisingWave / Feldera 的架构差异
4.1 Materialize 0.120
- 核心引擎:Rust 实现的 differential dataflow;
- 前端协议:PostgreSQL wire
protocol,用户直接
CREATE MATERIALIZED VIEW; - 状态管理:cluster(计算)+
persist(基于 S3 的持久层,内部称
persist/stash)分离; - 一致性:实时可查询的视图是确定的、幂等的、带时间旅行的(as of);
- 定位:偏强一致、偏 OLAP 化、偏”云上托管的 SQL 视图服务”。
典型陷阱:对 CHANGE DATA CAPTURE
源的依赖要求上游格式稳定;高基数 group by
的内存开销显著。
4.2 RisingWave 2.1
- 核心引擎:自研 Rust 流式 SQL 引擎,参考 differential dataflow 思想但不直接基于 DD 库;
- 前端协议:PostgreSQL 协议 + 自有 connector;
- 状态管理:compute 节点无本地盘,所有状态定期 checkpoint 到对象存储(类似 Pravega/Pulsar 的想法);
- 一致性:算子间保证 exactly-once 状态变更(基于 Chandy-Lamport 风格 barrier);
- 定位:云原生、弹性、偏”流数据库”,主打与 Kafka/Kinesis/PostgreSQL CDC 的集成。
典型陷阱:对象存储的延迟让 checkpoint 间隔不能太短;故障恢复期间会有 staleness。
4.3 Feldera 0.30
- 核心引擎:DBSP 作者团队的 Rust 实现,直接按 DBSP 代数编译;
- 前端:SQL(带扩展)+ REST/HTTP 提交电路;
- 状态管理:偏内存,正在迭代持久化方案(待核实最新版本细节);
- 一致性:DBSP 语义下确定性增量,recursive query 能力是三家最强的;
- 定位:面向需要复杂递归/图查询/反欺诈规则的企业。
典型陷阱:生态最年轻,connector 和运维工具链相对 Materialize / RisingWave 要薄。
4.4 横向对比表
| 维度 | Materialize | RisingWave | Feldera |
|---|---|---|---|
| 理论基础 | Differential Dataflow | 自研 + DD 思想 | DBSP |
| 实现语言 | Rust | Rust | Rust |
| 协议 | PostgreSQL | PostgreSQL | SQL + HTTP |
| 状态存储 | persist (S3) | 对象存储 checkpoint | 内存为主 |
| 递归查询 | 支持 | 部分 | 最完整 |
| 与 Kafka 集成 | 完整 | 最完整 | 基础 |
时间旅行 AS OF |
支持 | 部分 | 有 |
| 最佳场景 | 低延迟物化视图 | 云原生流 ETL + 视图 | 复杂增量规则引擎 |
| 一致性模型 | 强一致(虚拟时间) | checkpoint exactly-once | DBSP 确定性 |
4.5 三家都”不是 Flink”的地方
- 它们的”视图”是可被 SQL 直接查询的持久对象,而不是 Flink 那种 sink 到外部表;
- 它们对撤回(retraction)的处理是代数内置的,不是 Flink 的”upsert / changelog”后加的;
- 它们都能保证相同输入 + 相同查询 → 相同输出,这是 differential dataflow / DBSP 的语义直接给的;Flink 在乱序、迟到数据下需要用 watermark + allowed lateness 显式约束。
4.6 性能形态:何时哪家更快
三家都是 Rust 实现,单机吞吐通常在同一个数量级。真正让它们拉开差距的是特定查询结构:
- 简单 join 流 + 中等状态:三家都在几十万到百万条/秒量级;Materialize 在 PostgreSQL 协议端的元操作(连接建立、查询 plan)更熟;
- 多级递归 / 图查询:Feldera 优势明显(DBSP 对递归有原生优化);
- 多租户 / 自动扩缩容:RisingWave 的存算分离与对象存储 checkpoint 让它更容易水平扩展;
- 小规模、单机上线:三家都可以单机起,Feldera 的镜像最小、Materialize 的 SaaS 入门最简。
4.7 一致性与可调试性
三家一个共同的工程亮点:确定性重放。同一份输入、同一份查询、同一个版本,重放必然得到相同结果。这让 debug 变得远比 Flink 容易——Flink 因为 watermark + timer + 非确定性算子,在复现 bug 时经常要跟”当时的 event time 刻度”搏斗。
对应到事故复盘:
- 流式数据库:保存输入流 + 查询 SQL 就能重现事故;
- Flink:还需要保存 watermark 策略 + 状态 snapshot + JVM 环境。
五、一个最小 demo:Python Z-set 上的增量 join
完整代码见 23-incremental/demo/,目录里包含:
demo/README.md——如何运行;demo/zset_ivm.py——纯标准库,单文件约 200 行。
5.1 数据模型
# Z-set: dict[record, multiplicity]
# 正数 = 存在该条(及重数),负数 = 撤回
G: Dict[Tuple, int]定义三条基本运算:加法(按元素加,零值删除)、投影(聚合键)、双线性 join(按 key hash join,乘重数)。
5.2 增量 join 的核心
按 §3.3 的公式:
def incremental_join(a_old, delta_a, b_old, delta_b, key_a, key_b):
# Δ(a ⋈ b) = Δa ⋈ b_old + a_old ⋈ Δb + Δa ⋈ Δb
t1 = join(delta_a, b_old, key_a, key_b)
t2 = join(a_old, delta_b, key_a, key_b)
t3 = join(delta_a, delta_b, key_a, key_b)
return zset_add(zset_add(t1, t2), t3)运行 demo 可以看到:给定初始
a_old、b_old,当任意一边来了新增(delta = +1)或撤回(delta = -1)时,incremental_join
算出的 ΔV 叠加到之前的 V,与直接对
a_old + delta_a 与 b_old + delta_b
整体做 join 的结果严格一致。
5.3 在 demo 里观察的几件事
- 负重数是一等公民(撤回一条订单会让 join 结果里该条目的重数 -1);
- 同一条 record 从两条路径 join 进来会在 Z-set 里重数累积——这正是 bag semantics 的自然语义;
delta_a ⋈ delta_b这一项看似奇怪,但它保证了”两边同时来了变化”时不重不漏。
demo 最后附一个 sanity
check:state + incr_delta == full_recompute(new_a, new_b),随机输入下跑
10,000 轮断言成立。这就是把 DBSP
论文里那张线性等式的核心直觉压到 200
行之内的验证。
5.4 如何把 demo 扩展成更真实的例子
demo 故意做到 200 行之内,但它的结构允许几个自然延伸,读者可以自己动手:
- 加上
project/filter:两者是线性算子,直接对 Z-set 元素作用即可; - 加上
count(*) by k:按 key 分组,每个 group 的重数就是 count;写成Σ_{r in group} mult(r)即可; - 加上
sum(col) by k:线性聚合,和 count 写法类似; - 加上
max(col) by k:非线性,需要维护 per-group heap;这是一个很好的练习题,可以感受 “通用增量维护” 的代价; - 加上嵌套 join:链式 A ⋈ B ⋈ C 的增量公式展开成六项,验证”双线性可叠加”。
这些小练习完成之后,再去读 DBSP 原文或 differential-dataflow 代码,会有”啊原来是这样”的顺滑感。
六、与 Flink / Kafka Streams 的能力边界
6.1 Flink:通用流处理,但不是天生 IVM
Flink 能做 IVM(通过 Dynamic Table + Changelog Stream),但Flink 的核心抽象不是 Z-set。它把状态表示为 keyed state + operator state + timer,所有增量语义是在 SQL planner 层 rewrite 出来的。这意味着:
- 优点:generic,能做任意用户自定义 operator;大社区 + 成熟运维工具;
- 代价:SQL rewrite 层的 rule 必须显式证明单调性/可撤回,一旦用户 UDF 不满足假设,exactly-once 只能靠外层保证;乱序/迟到数据需要 watermark + allowed lateness 来”近似”正确性。
对比下来,Materialize / RisingWave / Feldera 的做法是从最底层就假设 Z-set 语义,因此不需要依赖 watermark 就能保证确定性。Flink 更适合做”通用流处理 + 状态机式业务”;三家流式数据库更适合做”视图即真相”的数据服务层。
6.2 Kafka Streams:偏”有状态消费者”的流处理
Kafka Streams 是”Kafka 的 consumer + 本地 RocksDB 状态”的组合:它几乎不做查询优化,只做有状态的算子链。IVM 意义下只能手写增量——可以做,但要用户自己管住一致性。适合:流式 ETL + 小状态业务;不适合:复杂 SQL 视图。
6.3 一个选型表
| 需求 | 推荐 |
|---|---|
| 低延迟 SQL 物化视图 (Postgres 兼容) | Materialize |
| 云原生弹性 + Kafka/CDC 中心 | RisingWave |
| 复杂递归/规则引擎 + DBSP 语义 | Feldera |
| 自定义算子 + 大规模通用流处理 | Flink |
| 简单有状态 Kafka 消费者 | Kafka Streams |
| 批处理 + 批式 IVM | Spark Structured Streaming(micro-batch) |
6.4 与仓库其他主题的链接
- 近实时批处理与 Parquet 的关系:Parquet 列式存储;
- CDC 源与数据湖格式:数据湖格式对比,下一篇 湖仓一致性模型 把 IVM 和 Iceberg/Delta/Hudi 的 snapshot 联起来;
- 流式系统的一致性基石:分布式共识工程实践;
- 流与表双视角:Flink Dataflow 模型 与 [Kafka 日志模型](仓库分布式系列相关文章)。
6.5 “看起来像 IVM 但不是”的常见系统
为避免在选型时走弯路,列几个常被当成 IVM 但不是的系统:
- Spark Structured Streaming (micro-batch):它是”把批任务切成小段重跑”,每个 micro-batch 内部做聚合但不维护 per-state delta。对于简单聚合它表现像 IVM,对复杂 join 实际上是重新扫。
- ClickHouse Materialized View:ClickHouse 的物化视图是写入时直接向目标表 append,没有撤回机制;适合”只累加”的指标聚合,不能做带更新/删除的 IVM。
- PostgreSQL Materialized
View:PostgreSQL 内置 MV
不会自动刷新,
REFRESH MATERIALIZED VIEW是全量重算。社区扩展pg_ivm在往增量方向演化(待核实成熟度)。 - Snowflake Dynamic Tables:语义上最接近三家流式数据库,底层实现细节未公开,但它确实把”视图自动增量化”暴露成了一等能力。
认清这些系统的边界,才能避免”选了 ClickHouse MV 后发现要撤回” 这种中期返工。
七、增量视图的工程工作流
7.1 典型部署拓扑
一个成熟的流式数据库部署,外围通常至少有三类组件:
source (Kafka / CDC / S3)
│
▼
┌───────────────────────┐
│ streaming database │ ← Materialize / RisingWave / Feldera
│ (stateful cluster) │
└──────────┬────────────┘
│ (sink / subscribe / pull)
▼
sink (Kafka / Postgres / BI / 前端)
每一段都可能是瓶颈,诊断起来经常需要把链路断点一个一个打。以下几个问题是我们在做咨询时反复遇到的:
- source 乱序:CDC 源(Debezium、Flink CDC、Canal)偶尔出现乱序。三家对乱序的容忍度不一样;Materialize 允许任意乱序但要求每条消息带 LSN/timestamp;RisingWave 的 barrier 机制要求 watermark 推进,迟到数据可能被丢;Feldera 对 CDC 格式较为严格。
- state 爆炸:一个错写的
GROUP BY会把状态从 GB 级推到 TB 级。三家都提供内存/磁盘使用的观测指标,要在上线前加告警。 - 视图重启慢:首次订阅或重启时需要重放历史,大底表能让冷启动跑几十分钟。这是所有流式数据库的通病,需要在应用层做”先读批、再切流”的冷启动协议。
7.2 状态的物理形态
三家状态管理的关键差别:
- Materialize:所有持久化走
persist层(基于 S3 + rocksdb 索引),compute 本身无本地持久化,故障时从persist重建; - RisingWave:compute 节点间 checkpoint 走对象存储(LSM + 分片),类似 Flink 的 unaligned checkpoint;
- Feldera:以内存为主,增强持久化(snapshot to disk / object store)仍在迭代(待核实)。
这个差别直接反映到“机器挂了,恢复要多久”。Materialize 和 RisingWave 可以把恢复降到分钟级;Feldera 偏”重新启动 + 重放”。
7.3 增量 DDL 与 schema evolution
真实业务里 schema 会变:加列、改类型、partition 重切。流式数据库的 DDL 语义比批式 SQL 复杂——因为视图上可能挂着 downstream 订阅者。
- Materialize 允许
ALTER MATERIALIZED VIEW,但很多情形下等同于”drop + create + 全量重算”; - RisingWave 支持部分在线 DDL(加列等轻量变更);
- Feldera 的”电路”结构对 DDL 友好度较低——通常是替换整个电路版本。
这里有一个容易忽视的隐性成本:流式数据库的”视图”一旦成为产品依赖,DDL 就不再是自由动作。要在建视图之初就考虑好 schema 演化策略,包括对齐仓库里 湖仓一致性模型 介绍的 Iceberg partition evolution 思路。
八、把 DBSP 读懂的三个小练习
8.1 练习一:手算一个
count(*) 的增量
count(*) 是非线性聚合。DBSP
把它写成:把所有行映射到一个常量 (),再对
() 求重数——重数就是 count。
- 初始
count = n; - 来了一条新行 → delta 是
+1 ×(),count 变成n+1; - 撤回一条 → delta 是
-1 ×(),count 变成n-1。
关键在于:count 本身是 Z-set 里
() 的重数这件事。只要 upstream 是
Z-set(允许负重数),count 就是一个简单投影。这也是 DBSP
“让聚合变成线性算子的投影” 的精神所在。
8.2 练习二:max
为什么难
max(x)
的增量要复杂得多:如果撤回的是当前最大值,需要扫全表才能知道新的最大值。DBSP
对这类算子提供了”通用增量维护”的保底方案——保留
per-group 的小 heap,使得撤回最大值只需 O(log
n)。
但这意味着:不是所有聚合都”便宜地增量”。min、max、top-k、percentile
都需要额外状态。选型时要有意识地评估状态开销。
8.3 练习三:窗口聚合的增量等价
Flink 常用的 tumble / hop / session window,在 DBSP 里被统一为”给每条记录打一个窗口 ID,再按窗口 ID 做 group by”。这一步让窗口聚合自动获得增量语义——只要 group by 的聚合函数本身可增量。副作用是:如果窗口到期后还有迟到数据,DBSP 天然输出”撤回 + 新发”的 delta 对,而不是 Flink 那种”allowed lateness 内合并,之外丢弃”。这对下游消费者是更简单的模型——你只需理解 Z-set,而不需要单独理解 watermark + lateness。
九、常见误解与反模式
9.1 误解:流式数据库 = Flink + SQL 语法糖
Flink SQL 和 Materialize / RisingWave / Feldera 在表面 API 相似,但语义基石完全不同:前者是”流处理引擎 + SQL 前端”,后者是”以 Z-set 为原子的增量数据库”。对撤回、乱序、恢复的处理路径各不相同。把一段 Flink SQL 直接端到 Materialize,大概率跑得通但语义会偏。
9.2 反模式:把所有业务查询都物化
物化视图不是免费的。每一条
CREATE MATERIALIZED VIEW 都对应一份常驻 state
与一条数据流。老生常谈但反复被违反:只物化 “高频读 +
低频写 + 能承担状态开销”
的查询。其余查询走按需计算。
9.3 反模式:用流式数据库做通用 OLAP
流式数据库的强项是”视图持续增量刷新”;它不是替代 DuckDB/ClickHouse/Trino 的通用 ad-hoc 查询引擎。用它做一次性大扫描会吃亏——它的 planner 没有为大规模 shuffle/扫描优化。
9.4 反模式:忽视”撤回风暴”
某些业务(如”TopN 榜单”)的一次底层数据变动会触发大量撤回 + 新发 delta。如果 sink 是一个只能 append 的下游(比如某些消息队列),这会把下游打爆。生产上需要在 sink 之前加一层 coalescing / debounce / 聚合。
十、开放问题与小结
10.1 开放问题
- DBSP 在 streaming UDF 下的语义:当用户写一个任意 Python/Rust UDF 时,它是否满足 DBSP 要求的线性/双线性?静态检查仍然是开放问题。
- 状态大小的代数化控制:DBSP 能告诉你结果,但”要维护多少状态”仍高度依赖 schema 与基数估计;是否存在一个代数化的状态代价模型?
- 与湖仓的融合:如果底表在 Iceberg / Delta 上,IVM 能否把”表 snapshot 的 diff”直接当成 Z-set delta 消费?Materialize、RisingWave 都在探索,尚未完全产品化。
- 近似 IVM:对高基数聚合能否牺牲精确性换状态下降?sketch + differential 的交叉是活跃研究方向。
- 多租户下的资源隔离:流式数据库常常把许多 view 塞进同一集群;单个 view 的状态爆炸会影响其他 view 的延迟。资源配额、state 限额、公平调度的研究远未成熟。
- 冷启动 / 重建的 ergonomics:从 0 重启一张大视图依然昂贵;如何让”平滑接续” 成为默认(而不是需要手工调 snapshot 策略)?
10.2 一段话总结
IVM 不是新问题;真正的变化是在过去十年里,它从一个”数据库引擎内部的冷僻模块”变成了一整类产品的核心。Differential Dataflow 提供了工业级 runtime,DBSP 提供了足够干净的代数,Materialize / RisingWave / Feldera 提供了三种工程取舍。对工程师而言,最重要的事不是选哪家,而是理解一件事:“流”和”增量视图”在合适的代数下是同一个东西。一旦理解了这一点,“流批一体”就从一句市场口号,变回了一个有数学骨架的工程决策。
10.3 一张决策速查
| 问题 | 快速答案 |
|---|---|
| 我要低延迟 PG 兼容视图 | Materialize |
| 我要云原生弹性 + CDC 中心 | RisingWave |
| 我要复杂递归 / 规则引擎 | Feldera |
| 我的业务要通用流处理 + UDF | Flink |
| 我的业务只是 Kafka 消费 + 状态 | Kafka Streams |
| 我的业务只有累加聚合 | ClickHouse MV(够便宜) |
10.4 推荐的跟进阅读
- 先读 DBSP VLDB 2023 论文的前 8 页,把 Z-set 和三类算子抓到手;
- 再翻一遍 differential-dataflow 仓库的
README与differential-dataflow/src/operators/目录(几百行即可读个大概); - 然后挑 Materialize 或 RisingWave 之一在本地跑一个 docker
compose,用
CREATE MATERIALIZED VIEW体验一下撤回语义; - 最后回来重读本文 §五 demo,把双线性公式在脑子里跑一遍。
整个路径大约 6–8 小时,足以让这个话题从”知道” 变成”能讨论”。
参考文献
- Budiu M., McSherry F., Ryzhyk L., Tannen V. DBSP: Automatic Incremental View Maintenance for Rich Query Languages. VLDB 2023. https://www.vldb.org/pvldb/vol16/p1601-budiu.pdf
- McSherry F., Murray D. G., Isaacs R., Isard M. Differential Dataflow. CIDR 2013.
- Murray D. G., McSherry F., Isaacs R., Isard M., Barham P., Abadi M. Naiad: A Timely Dataflow System. SOSP 2013.
- Gupta A., Mumick I. S. (eds.) Materialized Views: Techniques, Implementations, and Applications. MIT Press, 1999.
- Blakeley J. A., Larson P.-Å., Tompa F. W. Efficiently Updating Materialized Views. SIGMOD 1986.
- Gupta A., Mumick I. S., Subrahmanian V. S. Maintaining Views Incrementally. SIGMOD 1993.
- Gjengset J. et al. Noria: Dynamic, Partially-Stateful Data-Flow for High-Performance Web Applications. OSDI 2018.
- Hueske F., Kalavri V. Stream Processing with Apache Flink. O’Reilly, 2019.(Dynamic Table 语义的权威综述)
- Apache Flink 官方文档. https://nightlies.apache.org/flink/flink-docs-stable/
- Materialize 文档与工程博客. https://materialize.com/docs/
- RisingWave 文档. https://docs.risingwave.com/
- Feldera 文档与 DBSP 开源项目. https://www.feldera.com/ / https://github.com/feldera/feldera
- Differential Dataflow 开源项目. https://github.com/TimelyDataflow/differential-dataflow
- Laddad S. et al. Keep CALM and CRDT On. VLDB 2025.(DBSP 与 CRDT 关系讨论)
- Chandy K. M., Lamport L. Distributed Snapshots: Determining Global States of a Distributed System. ACM TOCS 1985.(流式 checkpoint 的理论基础)
上一篇:【数据库研究前沿】CRDT 与 CALM 定理再读:2025 的协调自由数据管理
下一篇:【数据库研究前沿】湖仓一体一致性模型:Iceberg、Delta、Hudi 的事务边界
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【数据库研究前沿】系列导论:从 System R 到 AI-Native 的 2026 研究地图
以 System R、Postgres、Bigtable、Spanner、Snowflake 等关键节点串起 50 年数据库史,勾勒 2026 年 AI-Native、向量检索、HTAP 云原生、新硬件、隐私计算、新范式、方法论七条主线,并给出 25 篇系列文章的完整阅读地图。
【数据库研究前沿】如何读数据库顶会论文:SIGMOD/VLDB/CIDR 阅读路线
从顶会定位、检索渠道、三遍读法到工业与学术论文的辨别方法,给出 2023–2025 年数据库领域可信必读二十篇,并配套 CMU 15-721、Stanford CS 245 等公开课清单。
【数据库研究前沿】学习型查询优化器:Neo、Bao、Balsa 到 LLM-CBO
系统梳理 Neo、Bao、Balsa 以及新兴 LLM-assisted 查询优化的核心思想,结合 PostgreSQL pg_hint_plan 给出一条可落地的 learned QO 工程路径
【数据库研究前沿】学习型索引再审视:RMI、ALEX、PGM
从 Kraska 2018 RMI 到 ALEX、PGM-Index、RadixSpline,系统梳理学习型索引的数学骨架、更新代价与落地边界,并给出一个最小 RMI 的 Python 实现