分布式百科:确定性模拟测试 已经讲清:控制网络、磁盘、时钟与随机数,执行就变成「种子 → 路径」的可重放函数。本篇不重复那套通识,只回答 FoundationDB 内核读者的问题:这些接口在 FDB 里接到哪、如何覆盖第 13 篇的恢复路径、和生产二进制差在哪。
版本锚定:FoundationDB SIGMOD 2021 §4 Simulation Testing;源码
apple/foundationdb7.3.42(2026-07 shallow clone 核对路径);7.x 模拟测试入口fdbserver -r simulation。
一、与 distributed/54 的分工
| 话题 | distributed/54 | 本篇 |
|---|---|---|
| 非确定性源清单、种子重放哲学 | 主文 | 只引用,不展开 |
| TigerBeetle / Antithesis 对照 | 有 | 不写 |
| Flow、FDB 模拟器进程模型 | 概述 | 工程落点 |
| 与 Sequencer / TLog 恢复的测试关系 | 弱 | 主线 |
读完 54 再读本篇,可以少走「又学一遍
DST」的弯路;若只关心
FDB,本篇自洽,但遇到「为什么不能直接调
clock_gettime」时应回链 54。
二、接口替换:网络、磁盘、时钟、随机数
FDB paper Figure 6 把模拟器画成:同一套数据库代码,所有非确定性与通信被抽象;生产实现是对系统调用的薄封装,模拟实现由离散事件调度器驱动。
flowchart TB
APP["FDB server logic in Flow actors"]
APP --> ABS["Abstraction: network disk time RNG"]
ABS --> PROD["Production shim to OS syscalls"]
ABS --> SIM["Simulator: deterministic event queue"]
SIM --> NET["Simulated network delay drop partition"]
SIM --> DISK["Simulated disk partial write reboot"]
SIM --> CLK["Logical clock fast-forward"]
SIM --> RNG["Seeded PRNG"]
2.1 Flow:把并发收进可调度 actor
FoundationDB 服务端主要用 Flow(C++ 上的 async/await 风格扩展)编写。Actor 由 Flow 运行时调度;刻意避免多线程抢占式并发污染确定性。FDB paper 写明:数据库代码本身确定,一个节点通常对应一个进程/核,而不是共享地址空间里的多线程竞态。
对恢复路径的含义:第 13 篇里的「停止旧 TLog → 招募新角色 → 写 Coordinator」是一串 actor 与 RPC,在模拟里变成事件队列上的确定顺序,而不是「看机器当天忙不忙」。
2.2 网络(fdbrpc
路径)
生产:真实套接字。
模拟:进程内消息传递;延迟、丢包、乱序、分区由种子驱动。恢复测试可以稳定构造「旧
LogServer 应答子集刚好过复制度门槛」或「ClusterController 与
Sequencer 之间的分区」。
2.3 磁盘
生产:文件系统与 fsync 语义。
模拟:可注入写入失败、部分写、机器重启后只保留已同步数据。这对
TLog 持久化与「DV 是否前进」直接相关——RV 计算依赖
DV/KCV,磁盘故障注入是恢复正确性的主战场之一。
2.4 时钟
生产:系统时钟。
模拟:逻辑时钟,可快进到下一事件。低 CPU
利用率段不必真的睡过去,因此「数秒级恢复 + 长空闲」类 bug
的发现效率高于墙钟集成测试(论文定性论述)。
2.5 随机数
选举超时、退避、负载均衡抖动等统一走种子化
PRNG(deterministicRandom(),见下表)。同一种子
+ 同一代码版本 ⇒ 同一事件序;CI
报出的种子就是回归用例 ID。
2.6 源码锚点(7.3.42)
下表钉住本篇提到的接口在官方仓库中的位置(apple/foundationdb
tag 7.3.42)。Flow actor 实现文件带
.actor.cpp / .actor.h 后缀,由
actor 编译器生成可链接对象。
| 符号 / 入口 | 路径 | 职责 |
|---|---|---|
ISimulator |
fdbrpc/include/fdbrpc/simulator.h |
模拟器抽象:继承 INetwork,定义
newProcess / killProcess /
onProcess、故障域与 TSS 模式 |
Sim2 |
fdbrpc/sim2.actor.cpp(class Sim2 final : public ISimulator) |
默认模拟器实现:离散事件 run
loop、逻辑时钟、Sim2Conn 网络 |
g_simulator |
fdbrpc/sim2.actor.cpp |
全局
ISimulator*;simulator_should_inject_fault()
等故障注入查此指针 |
Sim2FileSystem |
fdbrpc/include/fdbrpc/simulator.h +
fdbrpc/sim2.actor.cpp |
模拟磁盘:open / 部分写 /
deleteFile;newFileSystem() 挂到
INetwork::enFileSystem |
deterministicRandom() |
flow/include/flow/IRandom.h
声明;flow/flow.cpp 定义 |
种子化 PRNG;选举退避、Buggify 触发、故障注入概率均走此接口 |
BUGGIFY /
BUGGIFY_WITH_PROB |
flow/include/flow/flow.h |
编译期保留、模拟构建激活的白盒分支;getSBVar(__FILE__, __LINE__, …)
按源位置开关 |
enableBuggify() |
flow/flow.cpp |
按 BuggifyType 全局启停 buggification |
setupAndRun() |
fdbserver/SimulatedCluster.actor.cpp;声明于
fdbserver/include/fdbserver/SimulatedCluster.h |
读测试配置、拉起模拟集群与 workload 的主入口 |
| CLI | fdbserver/SimulatedCluster.actor.cpp(TraceEvent
记录 fdbserver -r simulation) |
本地/CI 跑 swarm 模拟的标准命令 |
生产二进制走 flow/network.cpp 等真实
INetwork 实现;模拟构建把
g_network 换成 Sim2 实例,同一套
fdbserver actor 代码两条路径复用。
三、模拟器里跑什么:整集群与可组合 workload
模拟器在单进程里拉起多个逻辑 FDB 服务器,经模拟网络互连,并挂上可组合的 workload:
- 故障注入指令(机器/机架/数据中心级停机与重启);
- 模拟业务读写;
- 数据库配置变更(与第 13 篇「配置变更也走恢复」同路径);
- 直接调用内部功能点。
flowchart LR
SEED["Seed"] --> SWARM["Swarm: cluster size config workloads faults"]
SWARM --> RUN["One simulation run"]
RUN --> ORACLE["Oracles: asserts contracts recoverability"]
ORACLE -->|fail| REPLAY["Replay same seed"]
ORACLE -->|pass| NEXT["Next seed"]
测试预言(oracle)不只看「没崩溃」:
- workload 内断言检查原子性/隔离性可维护的数据不变量;
- 代码内本地断言;
- 可恢复性:把环境推到足以破坏可用性的故障组合后,再恢复到应可恢复的状态,断言集群最终恢复(FDB paper §4)。
这直接钉住第 13 篇的产品命题:失败被设计成走恢复,因此模拟必须反复走恢复,而不是只测 steady-state 提交。
四、Buggify 与恢复路径覆盖
外部故障注入改变网络与机器;Buggify 在代码内部关键决策点引入「不违约但罕见」的行为:故意延迟、返回通常成功的错误、选怪异调参等。生产构建中编译为无开销;模拟构建由 PRNG 触发。
对恢复相关代码的工程意义:
| 手段 | 覆盖面 | 典型例子 |
|---|---|---|
| 网络/机器故障注入 | 角色消失、分区、延迟 | Sequencer 死亡、TLog 子集不可达 |
| Buggify | 内部稀有分支 | 不必要的重试、异常参数、额外延迟 |
| Swarm testing | 组合爆炸 | 随机集群规模、随机打开的 Buggify 子集 |
TEST(...) 条件覆盖宏 |
度量稀有状态是否被踩到 | 「缓冲区满时是否进过该分支」 |
论文强调:调参随机化还防止「某一组性能参数偶然变成正确性依赖」。恢复逻辑若依赖隐式超时窗口,会在模拟里被不同种子拆穿。
五、种子重放工作流(FDB 工程视角)
与 54 篇通识示例相同,但落到 FDB 仓库习惯:
- CI / 本地跑模拟,某种子触发断言或不可恢复。
- 用同一种子重跑;事件序对齐后下断点或加日志。
- 修复后再次同种子确认;种子进入回归集。
- 大版本前可并行放大种子扫描(论文描述为可突发扩容的 embarrassingly parallel 测试)。
sequenceDiagram
participant CI as CI swarm
participant DEV as Developer
participant SIM as FDB simulator
CI->>SIM: Run seed S
SIM-->>CI: Assert fail at recovery step
CI-->>DEV: Report seed S
DEV->>SIM: Replay seed S
SIM-->>DEV: Same event order
DEV->>SIM: Fix and replay seed S
SIM-->>DEV: Pass
DEV->>CI: Keep seed S in regression
本站不引用未经核对的「每天百万模拟小时」营销口径作为硬指标;FDB paper 对离散事件快进与并行种子扫描的机制描述是 A 级依据,具体产能数字以论文原文表述为准,不在此改写。
六、能测到什么、测不到什么
6.1 擅长
- 第 13 篇的 epoch 切换、旧 TLog 停止、RV/PEV 计算相关竞态;
- 复制与故障域约束下的数据丢失边界(与论文 §2.5 策略相关的场景);
- 客户端可见的严格可串行化契约在故障交叉下是否被破坏(通过 workload 不变量)。
6.2 边界(工程间隙)
| 模拟保证 | 生产仍可能不同 |
|---|---|
| 逻辑正确性、恢复可重放 | 真实网卡/存储尾延迟分布 |
| 契约级性能无关分支 | 调参下的吞吐与尾延迟绝对值 |
| 抽象层内的 I/O | 泄漏出抽象层的系统调用会破坏确定性 |
| 单进程多逻辑节点 | 内核调度、NUMA、多租户噪声 |
6.3 与 Jepsen 的对照
分布式百科:Jepsen 方法论 在 §7.4 已把 DST 与 Jepsen 并列表格。落到 FDB 工程:
| 维度 | Jepsen(Kingsbury) | FDB 确定性模拟 |
|---|---|---|
| 被测对象 | 已部署的多节点进程(容器/VM + 真实 TCP) | 单进程内多个逻辑 FDB 进程(Sim2) |
| 故障模型 | iptables 分区、进程
kill、时钟跳变(墙钟) |
种子驱动的延迟/丢包/磁盘注入 + BUGGIFY
内部变异 |
| 正确性 oracle | 线性一致性 / Elle 事务异常检测(操作历史) | workload 不变量 + 代码内断言 + 可恢复性(SIGMOD 2021 §4) |
| 可复现性 | 历史可保存,但 OS/网络噪声难比特级重放 | 同种子 + 同版本 ⇒
同事件序(deterministicRandom +
逻辑时钟) |
| 擅长 | 发现「对外 API 语义 vs 声称不一致」(含 FoundationDB 等厂商委托测试) | 锤同一套生产源码在恢复/复制路径上的深层竞态 |
| 盲区 | 磁盘部分写、复杂存储语义覆盖有限(Jepsen §8.1) | 真实 NIC/云盘尾延迟、多租户调度噪声(上表) |
Jepsen 回答「黑盒语义是否成立」;FDB DST 回答「这份 C++ 实现在抽象故障模型下是否自洽」。二者验证层级不同——FDB 在 SIGMOD 2021 论文中把 DST 作为开发主路径;Jepsen 百科 年表亦记录 2021–2023 间对 FoundationDB 等库的独立黑盒测试,与模拟器形成交叉检查。确定性模拟也不替代 TLA+ 规约证明或生产渐进发布;它与 第 17 篇 的症状树互补:模拟缩小「正确性」空间,排障处理「已部署配置与负载」空间。
6.4 与角色恢复测试的具体映射
把第 13 篇步骤映射到模拟手段,便于读源码或加 workload 时对号入座:
| 恢复步骤 | 模拟器更易注入的故障 | 期望 oracle |
|---|---|---|
| Coordinator 锁与读配置 | 分区、延迟、多数派抖动 | 至多一个 Sequencer 完成恢复 |
| 停止旧 TLog | 部分 TLog 无应答 / 迟应答 | RV/PEV 计算满足复制门槛语义 |
| 招募新角色 | 进程杀灭、启动延迟 | 新拓扑写入 Coordinator 后才开写 |
| 拷贝 \([\mathrm{PEV}+1,\mathrm{RV}]\) | 磁盘部分写、拷贝中断 | 新 TLog 仍能满足复制度 |
| 特殊恢复事务 | 提交与读交叉 | Storage 不暴露 \(>\mathrm{RV}\) 的未确认后缀 |
| 恢复后客户端重试 | 延迟与乱序 | workload 不变量保持 |
flowchart TD
W["Recovery workload"]
W --> F1["Inject TLog subset silence"]
W --> F2["Inject Sequencer kill"]
W --> F3["Inject disk partial write"]
F1 --> O["Oracle: no dual epoch commits"]
F2 --> O
F3 --> O
O --> INV["App invariant holds after retries"]
若某次改动触及 fdbserver 恢复相关
actor,仅跑「无故障提交」回归不够;至少应用能触发上表若干行的种子或
workload 组合。具体命令以仓库当前测试文档为准,本篇不伪造 CI
输出。
七、学术谱系、争论与开放问题
谱系(确定性重放 → FDB DST):Dunlap
等(ReVirt: Enabling Intrusion Analysis through
Virtual-Machine Logging and Replay,OSDI
2002)在虚拟机层记录非确定性输入并按序重放,使同一初始状态
+ 同一事件流得到可对比的执行轨迹——这是「把分布式 bug
变成可回归用例」的早期工程范式。FoundationDB 在 SIGMOD 2021
§4 把同一思想收进产品测试主路径:不录
VM,而是在源码层替换网络/磁盘/时钟/RNG(第二节表),让生产
actor 代码在 Sim2 里成为种子决定的函数。Will
Wilson(Strange Loop 2014,B 级口述)记录了 FDB 团队把 DST
推成默认 CI 的动机;机制细节以论文与 7.3.42 源码为准。
争论:TLA+ 模型检测 vs DST 跑真实代码
- 形式化一方:Newcombe 等(How Amazon Web Services Uses Formal Methods,CACM 2015)描述 AWS 用 TLA+ 在协议层穷举/模型检测,在写代码前发现 S3、DynamoDB 等服务的边界条件——优势是状态空间可声明、结论可追溯到规约;代价是模型与实现必须手工对齐,且对完整 C++ 实现做 exhaustive 检查不可行。
- DST 一方:Zhou 等(FoundationDB: A
Distributed Key-Value Store,SIGMOD 2021
§4)选择对已编译进
fdbserver的同一套逻辑做种子驱动故障注入:覆盖实现细节、Flow actor 交错与恢复路径组合,但只保证「抽象层内」的确定性——泄漏到 OS 的 syscall、真实网卡队列等不在证明范围内。
分歧不在「要不要测正确性」,而在证据形态:TLA+
给出相对规约的演绎保证(假设模型完整);DST
给出相对实现代码的回归保证(假设抽象层封闭)。FDB 论文明确
DST 用于日常开发闭环;AWS 论文明确 TLA+
用于早期设计评审——两篇均未声称对方可替代。对恢复路径(第 13
篇)而言,DST 能直接跑 SimulatedCluster
workload;TLA+ 更适合验证 Coordinator/Sequencer
状态机是否遗漏互斥条件,二者验证层级不同。
开放问题:
- 如何系统度量「恢复相关状态空间」覆盖率,而不仅是
TEST()点计数? - 模拟与云盘/多租户存储语义之间的模型缺口,如何在不破坏确定性的前提下缩小?
- 开源贡献者变更恢复路径时,最小必跑种子集如何维护才不依赖内部 swarm 规模?
八、常见误解
误解一:读过 DST 通识就等于理解 FDB
恢复已被测全。
通识说明方法;FDB 是否覆盖某条恢复分支,取决于
workload、故障分布与 Buggify
点是否踩到——要看模拟配置与覆盖宏,不是方法论标题。
误解二:模拟通过等于生产延迟达标。
模拟验证契约与恢复;延迟与容量见 第
16 篇,且需真实负载,不能把模拟快进当成性能证明。
误解三:Buggify
是外部混沌工程工具。
它是编译进代码的白盒变异;与外部故障注入叠加,而不是替换。
九、小结
三句话小结:
- FDB 用 Flow 把服务端收进可替换的网络/磁盘/时钟/RNG 抽象;生产走系统调用,模拟走种子驱动的事件队列。
- 模拟器跑整集群逻辑与可组合 workload,用断言与可恢复性 oracle 反复锤第 13 篇的 epoch 恢复,而不是只测无故障提交。
- Buggify + swarm + 种子回归构成工程闭环;DST 不给出吞吐排名,也不取消生产可观测与排障。
参考资料
论文
- J. Zhou 等, FoundationDB: A Distributed Key-Value Store, SIGMOD 2021. §4 Simulation Testing——DST 机制、swarm workload、可恢复性 oracle。
- C. Newcombe 等, How Amazon Web Services Uses Formal Methods, CACM 2015——TLA+ 在协议设计层的用法,与 DST 的证据形态对照。
- G. Dunlap 等, ReVirt: Enabling Intrusion Analysis through Virtual-Machine Logging and Replay, OSDI 2002——确定性重放谱系上游。
规范 / 官方文档
- FoundationDB 官方开发与测试文档(github.com/apple/foundationdb)——构建、
fdbserver -r simulation入口。
源码(7.3.42)
fdbrpc/include/fdbrpc/simulator.h—ISimulator、Sim2FileSystem。fdbrpc/sim2.actor.cpp—Sim2、g_simulator、Sim2Conn、模拟 run loop。flow/include/flow/flow.h—BUGGIFY/BUGGIFY_WITH_PROB。flow/include/flow/IRandom.h、flow/flow.cpp—deterministicRandom()。fdbserver/SimulatedCluster.actor.cpp、fdbserver/include/fdbserver/SimulatedCluster.h—setupAndRun()。
站内联动
- 确定性模拟测试通识
- Jepsen 方法论 §7.4 测试方法对照
- 第 13 篇故障恢复
- 第 17 篇生产排障
辅助(B 级)
- Will Wilson, Testing Distributed Systems w/ Deterministic Simulation, Strange Loop 2014——动机与口述历史;机制以论文与源码为准。
上一篇:故障恢复:角色招募与恢复
epoch
下一篇:Record
Layer:记录与二级索引
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【FoundationDB 内核】Unbundled · OCC · TLog · Redwood · Simulation
补齐 FoundationDB 选型叙事与生产内核之间的一层:拆解 Proxy、Sequencer、Resolver、TLog、Storage Server 的 Unbundled 写路径,严格可串行化、Redwood 与确定性模拟,并以 Record Layer 和 TiKV/etcd 对照收束。
【FoundationDB 内核】Unbundled 全景:事务、日志与存储为何拆开
以 SIGMOD 2021 为锚点,厘清 FoundationDB 控制面与数据面角色分工,说明一次事务穿过 Proxy、Sequencer、Resolver、TLog、Storage 的路径,并与站内 distributed/39、tikv-htap 划清边界。
【FoundationDB 内核】Client API 与事务模型:冲突范围、自动重试与 5 秒窗口
钉住 FoundationDB 客户端事务契约:读写缓冲、冲突范围、@transactional 自动重试,以及 5 秒上限如何约束应用设计——机制证明留给第 8–9 篇。
【FoundationDB 内核】Coordinator 与集群配置:Paxos 边界、招募与恢复入口
说明 Coordinators 如何用磁盘 Paxos 持久化事务系统配置、Cluster Controller 如何招募角色,并划清『配置共识』与『用户 mutation 共识』的边界。