土法炼钢兴趣小组的算法知识备份

【数据库研究前沿】流批一体与增量视图:Materialize、RisingWave、Feldera 的 DBSP 理论

文章导航

分类入口
database
标签入口
#ivm#dbsp#z-set#differential-dataflow#materialize#risingwave#feldera#streaming

源码下载

本文相关源码已整理,共 1 个文件。

打开下载目录 →

目录

这是【数据库研究前沿】系列的第 23 篇。“流批一体”这个词在工业界被用得极多,但多数语境下只是”同一份作业能跑实时也能跑历史回放”的工程口径。本文关注的是另一个层面的流批一体是否存在一个数学上干净、系统上可实现的模型,让”查询随输入变化而增量更新”与”查询在某一时刻的批量结果”是同一个语义。这个问题关乎一条技术路线能否长期站住脚——如果流结果与批结果不代数等价,那”流批一体”只是市场口号,不是工程事实。

这条线真正的奠基性工作,是 2023 年 VLDB 的 DBSP: Automatic Incremental View Maintenance for Rich Query Languages(Budiu、McSherry、Chajed、Tannen 等)。它的前身是 Frank McSherry 2013 年之后在 Microsoft / ETH / Materialize 这一串研究里发展出的 Timely DataflowDifferential 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 为什么”撤回”是技术难点

传统查询语义里,“插入”和”删除”看起来对称;但一到增量语义就不对称。原因是很多常见算子对”撤回”有长程依赖

这让朴素”反向执行算子”的做法行不通。IVM 的难点本质是:“删除” 要能被算子链整体一致地撤销。DBSP 通过 Z-set(允许负重数)把这个问题变成”对一个代数群做加法”——一步到位地把撤回变成正常运算。

1.3 早期工作:Counting Algorithm 与 Magic Sets

这些工作共同奠定了“状态里带重数 / 带 delta 签名”这件事的必要性。

1.3 为什么工业界直到 2010 后才真正用起来

原因其实很实在:

Flink 的 Dynamic Table / Changelog Stream(Fabian Hueske 等 2017 起)第一次把”流与表双向等价”放进工程语义。但 Flink 内部并不是 DBSP——它的一致性、撤回语义、状态管理都有自己的实现选择(见 §6)。

1.4 近十年的两条主线

2013 年之后,IVM 的研究分成两条显著不同的主线:

两条线不是并列的:DBSP 在很大程度上是”differential dataflow 的代数化、最小化版本”。理解 DBSP 基本等于理解了 differential dataflow 的核心思想,外加更干净的语言。

1.5 为什么”流批一体”这个词被滥用

“流批一体”在工业语境里常有三种完全不同的意思:

  1. API 统一:同一套 SQL/DataFrame API 既能写批作业也能写流作业(如 Flink SQL、Spark Structured Streaming);
  2. 存储统一:批和流读同一份底层数据(如 Iceberg/Delta/Hudi + Flink 的组合);
  3. 语义统一:批查询的结果和流式视图的结果在任意时间点代数等价(DBSP 的目标)。

只有第三种才是”真正的流批一体”。前两种是工程便利,不是数学等价。DBSP 相关产品(Materialize / RisingWave / Feldera)是目前在第三种意义上做到最彻底的工业实现。


二、Differential Dataflow:时间是多维的

2.1 Timely Dataflow 的 progress tracking

Timely Dataflow(Murray 等 SOSP 2013 的 Naiad)的核心抽象是:

这套 progress tracking 是后来 differential dataflow 能做递归、迭代查询增量化的基础。

2.2 Differential Dataflow 的 (data, time, diff)

Differential Dataflow 把每一条数据都换成一个三元组 (record, time, +1 或 -1 等整数)。两条关键观察:

  1. 所有 SPJ、group by、连接、递归查询都可以被写成对这些三元组集合的线性或近线性操作
  2. 在这个表示下,“新输入来了”就是增加一批正负三元组;“查结果”就是 fold 所有三元组到当前时间点。

这是一个非常直接的”把 IVM 归约到线性代数”的操作。McSherry 团队随后把它打磨成一个工业级 Rust 库(differential-dataflow),支持任意维度时间戳、任意 semiring 的 diff、并行执行。

2.3 什么时候 differential 是贵的

在两个场景下 differential dataflow 会比朴素全量更贵:

这是为什么 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 定义了两个算子:

有了这三个算子,所有线性算子 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_oldb_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

典型陷阱:对 CHANGE DATA CAPTURE 源的依赖要求上游格式稳定;高基数 group by 的内存开销显著。

4.2 RisingWave 2.1

典型陷阱:对象存储的延迟让 checkpoint 间隔不能太短;故障恢复期间会有 staleness。

4.3 Feldera 0.30

典型陷阱:生态最年轻,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”的地方

4.6 性能形态:何时哪家更快

三家都是 Rust 实现,单机吞吐通常在同一个数量级。真正让它们拉开差距的是特定查询结构:

4.7 一致性与可调试性

三家一个共同的工程亮点:确定性重放。同一份输入、同一份查询、同一个版本,重放必然得到相同结果。这让 debug 变得远比 Flink 容易——Flink 因为 watermark + timer + 非确定性算子,在复现 bug 时经常要跟”当时的 event time 刻度”搏斗。

对应到事故复盘:


五、一个最小 demo:Python Z-set 上的增量 join

完整代码见 23-incremental/demo/,目录里包含:

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_oldb_old,当任意一边来了新增(delta = +1)或撤回(delta = -1)时,incremental_join 算出的 ΔV 叠加到之前的 V,与直接对 a_old + delta_ab_old + delta_b 整体做 join 的结果严格一致

5.3 在 demo 里观察的几件事

demo 最后附一个 sanity check:state + incr_delta == full_recompute(new_a, new_b),随机输入下跑 10,000 轮断言成立。这就是把 DBSP 论文里那张线性等式的核心直觉压到 200 行之内的验证。

5.4 如何把 demo 扩展成更真实的例子

demo 故意做到 200 行之内,但它的结构允许几个自然延伸,读者可以自己动手:

这些小练习完成之后,再去读 DBSP 原文或 differential-dataflow 代码,会有”啊原来是这样”的顺滑感。


6.1 Flink:通用流处理,但不是天生 IVM

Flink 能做 IVM(通过 Dynamic Table + Changelog Stream),但Flink 的核心抽象不是 Z-set。它把状态表示为 keyed state + operator state + timer,所有增量语义是在 SQL planner 层 rewrite 出来的。这意味着:

对比下来,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 与仓库其他主题的链接

6.5 “看起来像 IVM 但不是”的常见系统

为避免在选型时走弯路,列几个常被当成 IVM 但不是的系统:

认清这些系统的边界,才能避免”选了 ClickHouse MV 后发现要撤回” 这种中期返工。


七、增量视图的工程工作流

7.1 典型部署拓扑

一个成熟的流式数据库部署,外围通常至少有三类组件:

  source (Kafka / CDC / S3)
           │
           ▼
  ┌───────────────────────┐
  │   streaming database  │  ← Materialize / RisingWave / Feldera
  │   (stateful cluster)  │
  └──────────┬────────────┘
             │ (sink / subscribe / pull)
             ▼
  sink (Kafka / Postgres / BI / 前端)

每一段都可能是瓶颈,诊断起来经常需要把链路断点一个一个打。以下几个问题是我们在做咨询时反复遇到的:

7.2 状态的物理形态

三家状态管理的关键差别:

这个差别直接反映到“机器挂了,恢复要多久”。Materialize 和 RisingWave 可以把恢复降到分钟级;Feldera 偏”重新启动 + 重放”。

7.3 增量 DDL 与 schema evolution

真实业务里 schema 会变:加列、改类型、partition 重切。流式数据库的 DDL 语义比批式 SQL 复杂——因为视图上可能挂着 downstream 订阅者。

这里有一个容易忽视的隐性成本:流式数据库的”视图”一旦成为产品依赖,DDL 就不再是自由动作。要在建视图之初就考虑好 schema 演化策略,包括对齐仓库里 湖仓一致性模型 介绍的 Iceberg partition evolution 思路。


八、把 DBSP 读懂的三个小练习

8.1 练习一:手算一个 count(*) 的增量

count(*) 是非线性聚合。DBSP 把它写成:把所有行映射到一个常量 (),再对 () 求重数——重数就是 count。

关键在于:count 本身是 Z-set 里 () 的重数这件事。只要 upstream 是 Z-set(允许负重数),count 就是一个简单投影。这也是 DBSP “让聚合变成线性算子的投影” 的精神所在。

8.2 练习二:max 为什么难

max(x) 的增量要复杂得多:如果撤回的是当前最大值,需要扫全表才能知道新的最大值。DBSP 对这类算子提供了”通用增量维护”的保底方案——保留 per-group 的小 heap,使得撤回最大值只需 O(log n)。

但这意味着:不是所有聚合都”便宜地增量”minmaxtop-kpercentile 都需要额外状态。选型时要有意识地评估状态开销。

8.3 练习三:窗口聚合的增量等价

Flink 常用的 tumble / hop / session window,在 DBSP 里被统一为”给每条记录打一个窗口 ID,再按窗口 ID 做 group by”。这一步让窗口聚合自动获得增量语义——只要 group by 的聚合函数本身可增量。副作用是:如果窗口到期后还有迟到数据,DBSP 天然输出”撤回 + 新发”的 delta 对,而不是 Flink 那种”allowed lateness 内合并,之外丢弃”。这对下游消费者是更简单的模型——你只需理解 Z-set,而不需要单独理解 watermark + lateness。


九、常见误解与反模式

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 开放问题

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 推荐的跟进阅读

整个路径大约 6–8 小时,足以让这个话题从”知道” 变成”能讨论”。


参考文献

  1. 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
  2. McSherry F., Murray D. G., Isaacs R., Isard M. Differential Dataflow. CIDR 2013.
  3. Murray D. G., McSherry F., Isaacs R., Isard M., Barham P., Abadi M. Naiad: A Timely Dataflow System. SOSP 2013.
  4. Gupta A., Mumick I. S. (eds.) Materialized Views: Techniques, Implementations, and Applications. MIT Press, 1999.
  5. Blakeley J. A., Larson P.-Å., Tompa F. W. Efficiently Updating Materialized Views. SIGMOD 1986.
  6. Gupta A., Mumick I. S., Subrahmanian V. S. Maintaining Views Incrementally. SIGMOD 1993.
  7. Gjengset J. et al. Noria: Dynamic, Partially-Stateful Data-Flow for High-Performance Web Applications. OSDI 2018.
  8. Hueske F., Kalavri V. Stream Processing with Apache Flink. O’Reilly, 2019.(Dynamic Table 语义的权威综述)
  9. Apache Flink 官方文档. https://nightlies.apache.org/flink/flink-docs-stable/
  10. Materialize 文档与工程博客. https://materialize.com/docs/
  11. RisingWave 文档. https://docs.risingwave.com/
  12. Feldera 文档与 DBSP 开源项目. https://www.feldera.com/ / https://github.com/feldera/feldera
  13. Differential Dataflow 开源项目. https://github.com/TimelyDataflow/differential-dataflow
  14. Laddad S. et al. Keep CALM and CRDT On. VLDB 2025.(DBSP 与 CRDT 关系讨论)
  15. Chandy K. M., Lamport L. Distributed Snapshots: Determining Global States of a Distributed System. ACM TOCS 1985.(流式 checkpoint 的理论基础)

上一篇【数据库研究前沿】CRDT 与 CALM 定理再读:2025 的协调自由数据管理

下一篇【数据库研究前沿】湖仓一体一致性模型:Iceberg、Delta、Hudi 的事务边界

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .