DDlog (Differential Datalog) — 输入只改一条,引擎只算受影响的那一小块
是什么
DDlog 是一种增量化 Datalog 引擎:你写一堆”如果 A 和 B 成立,那么 C 也成立”的规则;它把规则编译成 Rust 程序;运行时给它喂事实,它输出推理结果——关键是,每次只改一条输入,它只重算受影响的那一小块。
日常类比:像 Excel 里改一个单元格——所有依赖它的公式自动更新,但没依赖它的格子动都不动。DDlog 是把这个增量更新能力做到逻辑推理上,而且做到了几百万条规则也快。
你写:
input relation User(id: u32, country: string)output relation USUser(id: u32) :- User(id, "US").第一次跑:DDlog 扫一遍 User,把所有 country == "US" 的人挑出来。之后你说”新加一个 user (42, US)“——DDlog 不重扫整张表,只追加 USUser(42)。说”删掉 user 17(原本是 US)“——它只发”USUser(17) 删除”。
底座是 Frank McSherry 的 Differential Dataflow,VMware Research 把它做成 Datalog 前端。
为什么重要
不理解 DDlog 这套思路,下面这些事会觉得不可思议:
- 为什么 VMware NSX 改一条防火墙规则,几万台虚拟机的连接表能毫秒级更新——背后就是 DDlog
- 为什么 IDE(Rosalyn / rust-analyzer)改一行代码,全项目的类型错误标记几乎瞬时——增量分析
- 为什么 Materialize 数据库说”SQL 也能流式增量”——和 DDlog 同一作者,同一思路
- 为什么”重新跑整个 pipeline” 在大规模数据上是反模式——增量 > 重算
核心要点
DDlog 的思路可以拆成 三步:
-
规则 → 算子图:每条 Datalog 规则被编译成一串 Differential Dataflow 算子(join、map、filter、iterate)。类比:把”if A then B”翻译成一根流水线,A 进 B 出。
-
差量流(delta stream):每个算子收到的不是整张表,而是”刚才哪几行进来了 / 哪几行走了”——这是 differential dataflow 的核心数据形式。带版本戳让多版本能并存。
-
arrangement:中间结果会按 join 的键预先索引化(叫 arrangement),后续 join 直接从索引拿数据,不用每次扫全表。代价是内存。
递归(如”祖先关系”)用嵌套迭代算子求不动点——同样是增量的:祖先表里只追加新增的那部分。
三步合起来:输入差量进来 → 沿着算子图传播 → 输出差量出去。中间所有”没变的关系”一动不动。
实践案例
案例 1:传递闭包(祖先关系)
经典 Datalog 例子:
input relation Parent(child: string, parent: string)output relation Ancestor(person: string, ancestor: string)
Ancestor(c, p) :- Parent(c, p).Ancestor(c, a) :- Parent(c, p), Ancestor(p, a).第一次跑,DDlog 计算出所有 (child, ancestor) 对。然后你新增一对 Parent("Eve", "Alice"):
- 普通 Datalog:把整个 Ancestor 表清空,从头算。
- DDlog:只算”由这条新边能多推出哪些 ancestor 关系”——通常就几条。
数据集大的时候,差距是几个数量级。
案例 2:网络防火墙策略(VMware NSX 的真实用法)
input relation Connection(src: u32, dst: u32, port: u16)input relation BlockRule(dst: u32, port: u16)output relation Blocked(src: u32, dst: u32) :- Connection(src, dst, port), BlockRule(dst, port).NSX 控制器有几万条 BlockRule、几百万条 Connection。运维往 BlockRule 加一条新策略:
- 旧 Java 实现:扫全表重算 Blocked,几百毫秒到几秒
- DDlog:只算这条新规则匹配的 Connection 子集,毫秒级输出新增的 Blocked 行
VMware 的工程团队把这块从 Java 重写成 DDlog 后,规则代码量缩了一个数量级。
案例 3:增量指针分析(IDE / 静态分析的典型场景)
源码改了一行 x = y,传统 Andersen pointer analysis 要重跑整个项目(几万行代码可能几秒到几十秒)。
DDlog 写成规则后,只有”x 指向什么、谁指向 x 派生出来的链”会被重算——通常只是几十到几百条 fact 的差量。这也是 reps-ifds 同类问题的另一种解法。
踩过的坑
-
内存吃得多:每个中间 relation 都被建成 arrangement(带版本戳的索引)留在内存里。规则越多、关系越多,RAM 越大。生产里跑 NSX 控制器经常要几十 GB 内存。
-
否定和聚合必须分层(stratified):不能写”X 不在 Y 里,且 Y 依赖 X”——这种循环否定 DDlog 编译器会直接拒绝。聚合(sum / min / max)也要保证不和递归互相依赖。
-
编译到 Rust 慢:一份 100 行 DDlog 规则编出来要几十秒到几分钟才有二进制。改一行规则迭代一次开发体验很重——所以 DDlog 鼓励把”规则集”当库做,不是当脚本。
-
删除操作有时贵:min / max 这类聚合,删掉一条原始事实可能让派生表整段重算(因为新的最小值要重新选)。“通常增量便宜” 不等于”所有操作都便宜”。
适用 vs 不适用场景
适用:
- 输入流式增删、需要持续输出差量的系统(网络控制器、实时风控)
- 静态分析、IDE、增量 build(输入小幅变化、输出大)
- 规则可声明式表达(join + 递归就够),且规则集相对稳定
不适用:
- 一次性批处理(重头跑一次就完)——上 souffle-datalog 更快
- 规则要频繁动态生成 / 改写——DDlog 编译开销太重
- 内存严重受限(嵌入式 / 边缘设备)——arrangement 太占内存
- 需要非分层否定 / well-founded semantics——DDlog 不支持
历史小故事(可跳过)
- 2013 年:Frank McSherry 在 Microsoft Research 做 Naiad,提出 Differential Dataflow——一种带”逻辑时间戳”的算子框架,能做增量计算和迭代。
- 2017 年:McSherry 离开 MS 创业 Materialize(把同一思路做成 SQL 产品给数据团队用)。
- 2019 年:VMware Research 的 Leonid Ryzhyk 和 Mihai Budiu 在 Datalog 2.0 Workshop 发表 DDlog——把 Datalog 编译到 Differential Dataflow 上。
- 2020-2021 年:DDlog 在 VMware NSX 生产环境上线,重写网络控制器;论文发表,开源。
- 2024 年:项目归档(VMware 内部用法变化 + 维护者去向调整),但 Differential Dataflow 和 Materialize 仍活跃。
学到什么
- 增量 > 重算 是大规模系统的根本节奏:能算差量的别算全量
- 声明式 + 增量 的组合让运维改一行配置就毫秒生效,工程效率和性能可以同时拿
- 内存换时间 是 DDlog 最大的代价——arrangement 把”以后可能用得上的索引”全留下来
- 学术原型 → 生产引擎 的桥(Naiad → Differential Dataflow → DDlog → NSX)是过去十年增量计算最完整的一条线
延伸阅读
- DDlog GitHub(已归档) —— 原始仓库 + tutorial,README 写得清楚
- Frank McSherry blog —— Differential Dataflow 作者博客,几篇文章把核心机制讲透
- Materialize 文档 —— 同一思路的 SQL 产品化版本
- souffle-datalog —— 批处理向的 Datalog 引擎,对比理解 DDlog 的”增量 vs 全量”取舍
- self-adjusting —— Adapton / self-adjusting computation,另一种”输入小变只重算受影响部分”的思路
关联
- souffle-datalog —— Souffle 是批处理向 Datalog;DDlog 是增量向,定位互补
- self-adjusting —— Self-adjusting computation 的”只重算受影响部分”思路在 DDlog 里以差量流形式实现
- salsa-adapton —— Rust 生态的增量计算框架(rust-analyzer 用),同问题不同解法
- kildall-dataflow —— 经典数据流分析框架;DDlog 把它的 fixed-point 求解换成增量版
- reps-ifds —— IFDS 增量指针分析,DDlog 的天然应用场景
- andersen-pointer-analysis —— 指针分析经典算法,可用 DDlog 表达成几十行规则
- dataflow-model-2015 —— Google Dataflow 模型,同样是”差量 + 时间戳”的流式计算思路
反向链接
- andersen-pointer-analysis —— Andersen 指针分析 — 让编译器自己算出 p 可能指向谁
- dataflow-model-2015 —— Dataflow Model — 流处理的四问框架
- ethane-2007 —— Ethane 2007 — 把企业网安全策略集中到一台中央电脑上
- kildall-dataflow —— Kildall 数据流框架 — 用一套格论统一所有全局编译优化
- naiad-2013 —— Naiad — 一套引擎同时跑批处理、流处理和迭代计算
- reps-ifds —— Reps-Horwitz-Sagiv IFDS — 把跨过程分析变成图上找路
- rethinkdb —— RethinkDB — 让数据库自己把更新推给客户端的先驱
- salsa-adapton —— Salsa / Adapton — 让程序只重算”真的变了”的那一小块
- self-adjusting —— Self-Adjusting Computation — 输入小幅变化时只重算受影响的那部分
- souffle-datalog —— Soufflé — 把 Datalog 编译成 C++ 让程序分析跑得动