第 6 篇 Sequencer 已经说明 read version 与 commit version 如何由逻辑单点分配;本篇把它接进一次真实提交。问题不是「Proxy 做什么」,而是:客户端何时可以相信事务已提交,以及 Storage Server 何时一定能读到这笔提交——这两件事在 FoundationDB 里刻意不是同一时刻。
SIGMOD 2021 论文把写路径拆成 Transaction System(TS)与 Log System(LS),Storage System(SS)在后台消费 redo log。官方 7.x Architecture 文档进一步把 Proxy 分成 GRV Proxy(发 read version)与 Commit Proxy(编排提交)。本篇只走写事务提交那一段:Client → Commit Proxy → Sequencer(Master)→ Resolver → Transaction Log(TLog)。
本文是「FoundationDB 内核」系列第 7 篇(共 18 篇)。上一篇:Sequencer;下一篇:Resolver 与 OCC;→ 系列目录
版本锚定:FoundationDB 7.x;架构与提交步骤以官方 Architecture / Transaction Processing 文档与 Zhou et al., SIGMOD 2021 为准。不引用未经本机复现的吞吐或延迟数字。
一、问题:提交成功 ≠ 立刻在 Storage 上可见
Unbundled 架构的核心取舍是:把「事务排序与冲突检测」放在无状态 TS,「耐久性」放在 LS,「服务读」放在 SS。SIGMOD 2021 §2.3.2 写明:TS 做内存侧事务处理,LS 存 WAL,SS 存数据并服务读。
由此导出本篇要钉死的边界:
| 时刻 | 系统状态 | 客户端语义 |
|---|---|---|
| Commit Proxy 收到客户端 commit | 写仍在 Proxy 本地批里 | 未提交 |
| Resolver 判定无冲突 | 仍可因后续步骤失败而中止 | 未提交 |
| 指定 TLog 副本全部 durable 并回复 | Proxy 可向客户端返回成功 | 已提交 |
| Storage Server 拉到并应用该版本 | 该副本可按该 version 读出 | 读路径可见(可能略滞后) |
官方 Architecture 文档对 Commit Proxy 的三步描述与上表一致:向 Master 取 commit version → Resolver 判冲突 → 在 transaction logs 上 durable。Storage 拉取明确写在 Background Work,不在 commit 临界路径上。
sequenceDiagram
participant C as Client
participant CP as CommitProxy
participant S as Sequencer
participant R as Resolver
participant L as TLog
participant SS as StorageServer
C->>CP: commit(readSet, writeSet)
CP->>S: request commit version
S-->>CP: commitVersion (+ prev LSN)
CP->>R: conflict ranges
R-->>CP: ok or conflict
alt conflict
CP-->>C: not_committed
else no conflict
CP->>L: persist mutations
L-->>CP: durable ack
CP-->>C: commit success
L-->>SS: async pull / push path
end
读路径不经过这条流水线:客户端拿 read version 后直接访问 Storage Server(第 11 篇)。本篇只回答写侧「何时 durable、何时可读」。
二、Client 侧:本地缓冲写,提交时打包冲突范围
事务开始时,客户端向 GRV Proxy 取 read version,随后对
Storage Server 做版本化读;set /
clear 只缓存在客户端,不立刻进集群。SIGMOD 2021
§2.4.1 与 Developer Guide 都强调:写缓冲到
commit(),同事务内读见自己的写(read-your-writes)由客户端合并本地缓冲与远端读结果完成。
提交时客户端发给 Commit Proxy 的不只是 mutation 列表,还包括:
- read conflict ranges:事务期间普通读触及的 key / range;
- write conflict ranges:即将提交的写触及的 key / range;
- read version:OCC 窗口的左端点。
冲突范围如何被 Resolver 使用,见 第 8 篇。本篇只需记住:流水线携带的是范围集合,不是锁表。
@fdb.transactional(Python 绑定)在
not_committed
等可重试错误上自动重跑整段函数。官方 Developer Guide
明确:客户端内存里的普通赋值不会随事务回滚;应放在重试循环之外,除非接受冲突重试时再次执行。
三、Commit Proxy:批处理与三步编排
Commit Proxy 是写路径的编排者,本身无状态(角色状态随事务系统世代恢复重建)。官方 Architecture 列出它的职责:提交事务、向 Master 汇报已提交版本、维护 key range → Storage Server 映射供客户端缓存。
3.1 取 commit version
Proxy 向 Master / Sequencer 申请 commit version。SIGMOD 2021 §2.4.1:该版本必须大于任何已有的 read version 与 commit version;Sequencer 以约每秒一百万个版本的速率推进版本号(论文原文)。同时返回 previous LSN,让 Resolver 与 TLog 能按 LSN 无间隙串行处理。
批处理(§2.6):多个客户端事务可共用一次版本分配,再整批送 Resolver。负载低时缩小批以压延迟,负载高时增大批以摊销 Sequencer 调用。这是写吞吐扩展的关键手段之一——扩展靠加 Commit Proxy / Resolver / TLog,不是靠把 Sequencer 做成多活写者。
3.2 Resolver 冲突检测
Proxy 按 key 空间切分,把各片冲突范围发给对应
Resolver。官方文档:任一 Resolver 报冲突,整事务
not_committed。已知工程副作用:某一 Resolver
已把写集记入近期历史、另一 Resolver
判冲突导致中止时,可能对后续事务产生假阳性冲突;官方认为生产冲突率通常很低,且
5 秒 MVCC 窗口会过期冲刷历史(Architecture, Transaction
Commit)。细节与 OCC 算法见第 8 篇。
3.3 写入 TLog
无冲突后,Proxy 查内存 shard map,给 mutation 打上目标 Storage Server 的 tag,再写入满足复制策略的一组 TLog。SIGMOD 2021 Figure 2:mutation 只发到 preferred LogServer 及额外副本以满足复制度,其余 LogServer 可收空包以保持版本序。日志头携带 LSN、previous LSN、以及该 Proxy 的 known committed version(KCV)。
当指定副本全部回复 durable 后:
- Proxy 更新自身 KCV;
- 向 Sequencer / Master 汇报已提交版本,使后续 read version 不低于此提交;
- 向客户端返回成功。
这是客户端可见的提交点。
flowchart TD
A["Client commit RPC"] --> B["Commit Proxy batch"]
B --> C["Sequencer: allocate commit version"]
C --> D["Resolvers: OCC check"]
D -->|conflict| E["Return not_committed"]
D -->|ok| F["TLog: durable replicas"]
F --> G["Report committed version"]
G --> H["Reply success to client"]
F -.-> I["Storage Server async apply"]
四、提交点与可读边界
4.1 耐久性在 TLog,不在 Storage Engine 的本地 WAL 语义
对客户端而言,「已提交」意味着 mutation 已按复制策略持久在 Log System 上。SIGMOD 2021 §2.4.3:Shipping redo log 到 SS 不是 commit path 的一部分。Storage Server 可以缓冲更新、延迟刷盘,因为耐久性已由 TLog 承担。
这与「每个存储节点先写本地 WAL 再 commit」的传统单机模型不同:FDB 的 WAL 是集群级、按 Storage 分片的分布式队列(论文称 LogServers 为 replicated, sharded, distributed persistent queues)。
4.2 读可见性:通常很快,但不与 commit 原子同拍
论文 §2.4.3 说明 Storage 会积极从 LogServer 拉 redo(甚至在 LS 完全 durable 之前也可拉,以降低多版本读延迟);若某副本尚不可读,客户端可等待、换副本,或收到可重试错误后重启事务。官方 Architecture 也写:在日志落盘前即可转发给负责该 mutation 的 Storage Server;Storage durable 后从 log 上 pop。
因此:
- 提交成功:客户端可依赖 ACID 中的 D(在配置的复制与故障模型下);
- 某次点读立刻读到:取决于该 key 所在 Storage 副本是否已应用该版本,不是 Commit Proxy 返回成功的充要条件。
GRV Proxy 发放的 read version 来自 Master 上记录的最大已提交版本,并校验 transaction log 集合仍满足复制策略且未被停掉(防止旧世代 Proxy 发出版本)。严格可串行化如何由此成立,见 第 9 篇。
4.3 空提交与版本推进
官方 Background Work:在无客户端事务时,Commit Proxy 仍会向 TLog 发空提交,保持 commit version 前进。这避免版本停滞影响租约与恢复逻辑,也说明「版本推进」不完全等同于「用户有写」。
五、批处理、空提交与限流如何咬合流水线
5.1 动态批
论文 §2.6:Proxy 把多个客户端事务收成一批,只向 Sequencer 要一次 commit version,再整批做冲突检测与日志写入。批大小随负载伸缩——轻载缩小以改善延迟,重载增大以保护 Sequencer。机制含义是:客户端感知的「一次 commit RPC」在 Proxy 内部可能与邻居事务共享版本分配往返,但每个事务仍有自己的冲突范围与成败结果。
5.2 空提交
无用户事务时,Commit Proxy 仍向 TLog 写入空提交以推进版本(Architecture, Background Work)。这对理解监控有帮助:TLog 上版本在动,不等于业务 QPS 非零;反过来,版本停滞更值得当作事务系统健康信号(第 17 篇会接到排障树)。
5.3 Ratekeeper 不挡在 TLog 之后
Ratekeeper 主要通过降低 GRV Proxy 发放 read version 的速率来限流(Architecture)。新事务更难「开始」,从而间接降低进入 Commit Proxy 的压力。它不是在 TLog durable 之后再拒绝已验证事务的主路径——过载保护发生在流水线更早的入口。
六、与 TiKV 写路径的机制对照(不比性能)
| 维度 | FoundationDB(本篇) | TiKV Percolator(站内对照) |
|---|---|---|
| 提交编排 | Commit Proxy 中心化三步 | 客户端驱动 Prewrite / Commit,按 Region 并发 |
| 冲突检测 | Resolver 上 OCC,提交时一次性 | Prewrite 时查 lock / write CF |
| 耐久性 | TLog 复制队列 | Region Raft 日志 |
| 读路径 | 直达 Storage Server | 通常走 Region Leader(或 follower 读配置) |
| 隔离默认 | 严格可串行化 | 快照隔离(可叠加悲观锁) |
对照目的是分清协调点放在哪:FDB 把排序与冲突收到 TS;TiKV 把锁与写状态编码进每 Region 的 MVCC。不在此比较谁更快——本系列无实测则不下性能结论。TiKV 细节见 tikv-htap 第 10 篇。
一次计数器自增在两边的形态差异,也能说明流水线位置不同。FDB
侧(Developer Guide 风格)是单事务读改写,冲突由 Resolver
在提交时判定;TiKV 侧是 Prewrite 锁 + Commit 两阶段。FDB
若改用原子 add,则连「读」都不进 read conflict
range,流水线仍走完 TLog,但 OCC 压力更小——这是 API
选择对流水线负载的影响,不是另一条提交协议。
七、学术谱系、工程间隙与开放问题
谱系:Lomet & Mokbel 等提出的 unbundled / Deuteronomy 思路(把事务管理与数据管理拆开)是论文 Related Work 中的参照系;FoundationDB SIGMOD 2021 的分叉是:MVCC 数据放在 SS 而非 TS,OCC + 全局 Sequencer 版本实现严格可串行化,并用确定性模拟兜住故障组合。Kung & Robinson 的 OCC(ACM TODS 1981)与 Bernstein 等人的 MVCC 教材模型,是 Resolver / 版本读的经典来源。
工程间隙:论文微基准称单线程 Resolver 可处理大量 TPS 量级的随机范围读写冲突检测,但那是冲突检测微基准,不是端到端集群吞吐,且依赖低冲突假设。生产上假阳性冲突、Resolver 分片边界、批大小与 Ratekeeper 限流如何耦合,论文未给出可直接搬用的运维公式——第 16、17 篇 只讨论可观察症状,不编造容量公式。
开放问题:durable 与 readable 解耦之后,Storage 落后尾延迟在何种负载下会把「严格可串行化读」逼成大量重试?论文 Figure 3 给出某生产集群上 Storage 相对 Log 的滞后分布,但那是特定部署快照,本系列不把它写成普适 SLA。可读入口仍是 SIGMOD 2021 §2.4.3 与官方 Architecture 的 Background Work 描述。
八、失败模式在流水线上的落点
| 现象 | 通常落在哪一跳 | 客户端常见错误 |
|---|---|---|
| 读集过期 | Resolver | not_committed |
| 事务超过约 5 秒 | 版本窗口 / Storage 拒旧版本 | transaction_too_old |
| TLog 或 Proxy 世代切换 | 整代 TS 恢复 | 提交失败或 commit_result_unknown |
| 问错 Storage 后缓存过期 | 读路径(非本篇主线) | 定位重试,不必然 abort 写 |
commit_result_unknown
值得单独记:Architecture 说明恢复可能发生在 Proxy 已向 TLog
发送 mutation
之后、客户端尚未收到成功之前。此时重试必须假设「第一次可能已提交」。流水线提供的是耐久性与版本协议,不自动提供业务幂等——幂等是
Layer / 应用契约(第 15、17 篇会回到这一点)。
九、常见误解
| 误解 | 事实 |
|---|---|
| 「提交成功表示所有 Storage 已写完」 | 成功只要求 TLog 侧按策略 durable;Storage 异步应用 |
| 「写也要经过 Storage 才能返回」 | 写路径是 Proxy → Resolver → TLog;读才直达 Storage |
| 「Sequencer 参与每一字节的复制」 | Sequencer 分配版本;复制与落盘在 TLog |
| 「多个 Commit Proxy 各自独立排序」 | 版本全序来自 Sequencer;Proxy 只是并行编排入口 |
| 「空提交没有意义」 | 用于推进版本并维持事务系统健康假设 |
| 「GRV Proxy 与 Commit Proxy 可互相替代」 | 7.x 文档中两者职责分离:发读版本 vs 编排提交 |
十、小结
三句话小结:
- 写事务提交由 Commit Proxy 编排:Sequencer 发 commit version → Resolver OCC → TLog 指定副本 durable 后向客户端返回成功。
- 「已提交」锚定在 Log System 的耐久性上;Storage Server 异步拉日志,读可见性可能略滞后于提交返回。
- 读写路径分离使读扩展与写扩展解耦;严格可串行化依赖版本分配与冲突检测的组合,下一篇拆 Resolver。
下一篇进入 Resolver 与 OCC:read/write conflict ranges、冲突窗口、自动重试,以及与 TiKV Percolator SI 的对照。
下一篇:Resolver 与 OCC
返回 系列目录
参考资料
论文(A 级)
- Zhou, Jingyu et al. FoundationDB: A Distributed Unbundled Transactional Key Value Store. SIGMOD 2021(§2.3–2.4 写路径、Figure 1–2、批处理与 atomic ops)。
- Kung, H. T., Robinson, John T. On Optimistic Methods for Concurrency Control. ACM TODS 1981(OCC 经典定义)。
官方文档(A 级)
- FoundationDB 7.x, Architecture — Commit
Proxies、Transaction Commit、Background
Work、
commit_result_unknown。 - FoundationDB 7.x, Developer Guide —
事务、冲突、
@transactional重试语义。
站内对读
- 第 6 篇 Sequencer、第 8 篇 Resolver、第 10 篇 TLog。
- tikv-htap 第 10 篇 Percolator。
- distributed/39 FoundationDB 段落(选型概览,不复述其性能表)。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【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 内核】Resolver 与 OCC:冲突范围、窗口与自动重试
拆解 FoundationDB Resolver 如何用 read/write conflict ranges 做乐观并发控制:冲突窗口落在 read version 与 commit version 之间,客户端自动重试;并与 TiKV Percolator 快照隔离对照机制差异,不写无实测性能结论。
【FoundationDB 内核】Transaction Log:提交点、durable version 与异步解耦
拆解 FoundationDB Transaction Log(TLog)如何作为分布式 WAL:按复制策略同步 durable 后 Commit Proxy 才返回成功;说明 durable version、known committed version 与恢复版本的关系,以及 Storage Server 异步消费如何离开提交临界路径。