第 7 篇 把「TLog durable」标成客户端提交点;本篇进入 Log System 内部。问题:一次 mutation 如何变成可恢复的 redo,副本之间如何对齐版本,Storage 又如何在不拖住提交的前提下消费这些日志。
SIGMOD 2021 §2.3.2:LogServers 是 replicated、sharded、distributed persistent queues,每个队列为 StorageServer 保存 WAL。官方 Architecture 称 transaction logs 使 mutation 快速 durable,从而压低 commit 延迟;Storage 大约在数秒后 pop 已应用数据(文档给出的典型量级描述,不是本系列实测)。
本文是「FoundationDB 内核」系列第 10 篇(共 18 篇)。上一篇:严格可串行化与 5 秒限制;下一篇:Storage Server;→ 系列目录
版本锚定:FoundationDB 7.x。恢复细节与
design/recovery-internals.md、SIGMOD §2.4.4 对齐;不引用未复核的跨机房延迟数字作为普遍结论。
一、TLog 在 Unbundled 中的位置
写路径上,TLog 是 TS 与 SS 之间的耐久性边界:
- 上游:Commit Proxy 在 OCC 通过后推送带 LSN 的 mutation;
- 自身:追加写、fsync 语义的持久化(Architecture:append-only mutation log);
- 下游:Storage Server 拉取(或在落盘前即转发)并应用。
论文强调:redo 从 LS 运到 SS 不是 commit path。这把「提交延迟」主要绑在日志复制与磁盘上,而不是绑在 Storage Engine 的 B-Tree 页写入上。
flowchart LR
CP["Commit Proxy"] --> TL["TLog group"]
TL -->|"commit ack when durable"| CP
TL -.->|"async consume"| SS["Storage Servers"]
CP -->|"success"| Client["Client"]
TLog 故障会触发事务系统恢复(论文 §2.5:LogServer 失败导致 transaction system recovery),与「某个 Storage 慢了只影响读滞后 / 数据迁移」不同。角色恢复见 第 13 篇。
二、写入路径:标签、副本与版本序
2.1 按 Storage 标签分片
SIGMOD 2021 Figure 2:Proxy 查 shard map,给 mutation 打上负责该 key 的 StorageServer tags;每个 tag 有 preferred LogServer。mutation 发到 preferred 集合外加额外 LogServer 以满足复制度;其它 LogServer 可收空消息,但共享同一 LSN / previous LSN 头,从而全员按版本序推进。
这实现了「逻辑上广播版本,物理上只把数据放在需要的队列里」。
2.2 复制度与提交确认
论文 §2.5 Log replication:Proxy 把每条分片日志同步复制到 \(k=f+1\) 个 LogServer;全部指定副本持久成功后才向客户端返回。这与「多数派即可」的经典 quorum 写不同——FDB 在日志路径上选择用较少副本数(\(f+1\) 而非 \(2f+1\))换故障时重配恢复,而不是用额外副本来掩盖故障(论文 Introduction 的设计对比)。
官方 Architecture 表述一致:日志写入磁盘并 fsync 后才回复 Commit Proxy。
2.3 消息头:LSN、previous LSN、KCV
论文 §2.4.3:头里带 LSN、previous LSN、以及该 Proxy 的 known committed version(KCV)。LogServer 持久成功后回复;Proxy 在全部副本确认且 LSN 更大时更新 KCV。KCV 随后在恢复时用来确定「哪些版本一定已经向客户端承诺过」。
sequenceDiagram
participant CP as CommitProxy
participant L1 as TLog_preferred
participant L2 as TLog_replica
participant SS as StorageServer
CP->>L1: mutation + LSN + prev + KCV
CP->>L2: mutation + LSN + prev + KCV
L1-->>CP: durable
L2-->>CP: durable
CP-->>CP: advance KCV
L1-->>SS: forward or pull path
空消息体的 LogServer 仍参与版本序:它们推进「看到了该 LSN」的结构,即使没有该 mutation 的数据负载。这保证 Resolver/TLog/Storage 各方对 LSN 间隙的处理一致——Sequencer 发放的 previous LSN 链条不能断。
2.4 与「磁盘上的 SQLite WAL」对照一句
Storage 引擎内部可能仍有页日志或崩溃恢复结构(视引擎而定),但那一层只服务单机页一致性。分布式事务的「事务已提交」断言不建立在「SQLite 本地 commit 返回」上,而建立在 TLog 副本集合的 durable ack 上。第 12 篇展开引擎谱系时会回到这句边界;本篇读者只需避免把两种 WAL 混称。
当 Proxy 广播空包以维持 LSN 时,不持有数据的 LogServer 仍然 fsync 头信息——复制策略与故障检测依赖「全员版本时钟」大致同频。这是分片日志系统的共性成本:元数据流量与数据流量分离,但版本通道不能省。
三、Durable Version 与恢复锚点
3.1 DV 与 KCV
恢复章节(§2.4.4)定义:
- Durable Version(DV):该 LogServer 上已持久的最大 LSN;
- Known Committed Version(KCV):该 LogServer 见到的、来自 Proxy 的最大「已对客户端承诺」下界信息(Proxy 在全部副本确认后推进)。
Sequencer 停止旧 LogServer 时收集各节点的 DV 与 KCV。在复制度为 \(k\) 的设定下,论文描述:收集到足够回复后,previous epoch end version(PEV)取 KCV 的最大者——此前数据已充分复制;recovery version(RV)取 DV 的最小者。区间 \((PEV, RV]\) 上的日志拷到新世代 TLog,以补齐副本;开销被描述为仅含数秒数据。
Storage 通过特殊恢复事务获知 RV,丢弃高于 RV 的内存多版本(半提交 / 恢复中止路径),再从新 LS 拉取。
3.2 与「提交点」对齐
对客户端:
- 收到 commit 成功 ⇒ 该事务 LSN 已进入各指定 TLog 的 durable 集合,且 Proxy 已推进其 KCV 叙事;
- 集群随后崩溃恢复 ⇒ 恢复协议保证:凡可能已成功回复的提交,其日志仍可被 Storage 找回(在配置的故障模型内)。
对 Storage:
- 可能短暂拥有 \(>\) RV 的内存数据并在恢复时丢弃——这正是「积极拉取」与「恢复回滚」的配对设计。
flowchart TD
subgraph Versions["Version anchors"]
KCV["max KCV -> PEV"]
DV["min DV -> RV"]
end
KCV --> Copy["Copy (PEV, RV] to new TLogs"]
DV --> Copy
Copy --> NewEpoch["New epoch accepts commits"]
NewEpoch --> SSRoll["SS discard > RV then pull"]
更完整的世代切换与角色招募见第 13 篇;本篇只钉 TLog 提供的版本语义。
design/recovery-internals.md
用工程语言描述同一故事:Cluster Controller / 恢复逻辑收集旧
TLog 的 knownCommittedVersion 与 durable
version,计算拷贝区间并招募新
TLog。细节字段名随版本演进,但不变式仍是:可能已向客户端成功的版本必须可找回;未达承诺的后缀可以裁掉。
3.3 客户端可见错误与日志状态
当 TLog 或 Proxy 所在世代崩溃时,客户端可能看到提交失败或
commit_result_unknown。日志侧此时可能已经
durable 了部分
mutation。恢复协议的职责是让「已承诺」与「可找回」对齐,而不是让客户端通过查询
TLog 自己做 2PC。应用层唯一稳妥的策略仍是幂等重试。第 7
篇流水线与第 13
篇恢复会从两端叙述同一现象;本篇提供日志版本词汇,使两边能对上号。
空提交推进版本时,KCV/DV 叙事同样适用:它们标记的是日志进度,不是业务行数。监控若只看「mutation 字节」而忽略版本进度,会在低业务流量时误报「集群停滞」。
四、与 Storage 异步解耦的收益与代价
4.1 收益
- Commit 延迟主要面对日志盘与副本 RTT,而不是 B-Tree 随机写。
- Storage 可批量、合并后刷引擎,提高 I/O 效率(论文 §2.4.3)。
- 读扩展与写耐久扩展资源池分离:加 Storage 不必然加 TLog,反之亦然(在容量规划上仍相互约束,见第 16 篇)。
4.2 代价
- 读可能落在「已提交但本副本未应用」的短暂窗口,客户端需等待、换副本或重试。
- TLog 在 Storage 失败时会堆积发往该 Storage 的数据,直到数据分布把责任迁走再丢弃(Architecture)。
- 日志路径故障 → 整代 TS 恢复,写暂停窗口由恢复速度决定(论文称生产上常小于数秒量级;当作设计目标与观察,不作本系列 SLA)。
4.3 官方「约 6 秒 pop」在说什么
Architecture:Storage 使 mutation durable 后从 log pop,一般大约在 mutation 提交后约 6 秒。这是实现与默认多版本窗口同量级的运维现象描述,说明 TLog 不是无限归档;备份与长期保留是另一条产品路径(第 16 篇边界),不是「TLog 永久存一切版本」。
4.4 积极拉取与半提交
论文指出 Storage 可在 LS 完全 durable 之前就拉取,以降低多版本读等待;若随后恢复把 RV 定在较低版本,高于 RV 的内存应用必须丢弃。于是 TLog 与 Storage 之间存在两种「领先」叙事:
- 提交领先:客户端已成功,Storage 尚未可读;
- 应用领先:Storage 内存已见某 LSN,但该 LSN 可能在恢复中被裁掉。
运维上两者都表现为「版本差」,根因不同:前者常是正常异步间隙,后者与恢复 / 故障注入相关。第 17 篇会按症状分流,本篇只要求读者不要把「Storage 版本」单一解释成「提交版本」。
五、地理复制下的同一提交点(边界)
SIGMOD 2021 §3 描述多区域布局:写入仍在主区域 TS 处理,日志同步落到主 DC 与同区域卫星,再异步到远端。选项决定提交时等待几个卫星故障域,从而改变延迟构成,但不改变「日志 durable 即提交」的定义。跨区域读可走本地 Storage,但一致读仍需从主侧获得 read version。本系列不把论文里的卫星 RTT 写成普适数字;若部署涉及 HA 写路径,应另读官方 HA / region 配置文档。
Copyset 与层次化放置(论文 §2.5)主要影响 Storage team,但日志路径同样面对「如何用较少副本达到目标故障域」的问题。FDB 选择 \(f+1\) 同步日志副本 + 故障时恢复,而不是 \(2f+1\) 掩错,使 TLog 资源更省,同时把可用性预算押在 MTTR。运维若把「进程挂了自动继续写」的 quorum 直觉套到 TLog,会误判短暂写暂停为异常;在 FDB 里这往往是设计内的恢复路径。
六、学术谱系、工程间隙与开放问题
谱系:传统 ARIES(Mohan et al.)把 redo/undo 与检查点绑在恢复临界路径。FDB 论文明确对比:无 coarse checkpoint 重放、恢复不重做 ARIES 式 redo/undo;因为正常路径上 Storage 已在消费与应用日志,「恢复」退化成找齐 redo 末端与切换世代。这是 unbundled WAL 对集中式 WAL 恢复理论的工程分叉。
工程间隙:论文 geo 部署中卫星机房同步日志的多种选项(§3)改变的是「提交时等几个故障域」,不是改变「TLog 为提交点」本身。把单区域文档描述直接套到跨洋部署会误判延迟构成——需要读官方 HA / 区域配置,而不是只读本篇。
开放问题:在云盘 fsync 尾延迟抖动时,TLog 同步复制是否比「多数派 + 租约」更易放大写尾延迟?社区与论文都强调快速恢复哲学,但对云存储语义的系统测量仍依赖部署方自己的观测。第 17 篇从症状侧讨论 Storage 落后与写卡顿,不以未发表数字定论。
七、常见误解
| 误解 | 事实 |
|---|---|
| 「TLog 只是 Storage 的缓存」 | TLog 是提交耐久性真相来源;Storage 可重建 |
| 「多数派确认即可提交」 | 日志路径要求指定的 \(f+1\) 副本全部 durable(论文) |
| 「Storage 必须先写完客户端才成功」 | 成功在 TLog ack;Storage 异步 |
| 「DV 等于已向客户端提交的版本」 | DV 是节点持久上限;KCV/PEV 叙事才对齐客户端承诺 |
| 「TLog 永久保留所有历史」 | 应用并 durable 后会 pop;窗口与多版本策略有界 |
| 「Storage 版本号超前一定更安全」 | 可能只是尚未被 RV 裁剪的半提交前缀 |
八、小结
三句话小结:
- TLog 是分片的分布式 WAL:Commit Proxy 在 OCC 后写入,指定副本全部 durable 才向客户端返回成功。
- LSN、KCV、DV 支撑提交确认与恢复时 PEV/RV 的计算,使可能已成功的事务在世代切换后仍可找回。
- Storage 异步消费把 B-Tree 写入移出提交路径,代价是短暂的读可见性滞后与 TLog 堆积风险。
下一篇进入 Storage Server:Shard、版本读取、拉日志与迁移,以及读路径为何不经过提交流水线。
上一篇:严格可串行化与 5 秒限制
下一篇:Storage Server:Shard、版本与读路径
返回 系列目录
对多租户集群,TLog 磁盘是所有租户提交的共享耐久资源:某一租户的大事务会占用日志带宽,从而抬高他人的 commit 等待。这是 Unbundled 的共享命运,不是 Storage 分片能单独隔开的。
文末核对提示
本节补记:机制结论以 SIGMOD 2021 与 FoundationDB 7.x 官方
Architecture / Developer Guide / Known Limitations
为准;若源码字段名随小版本重命名,以对应 tag 的
fdbserver 实现为准,不把过时幻灯片当契约。
阅读本篇时建议同时打开系列 index 中的依赖图:本篇上承写路径与版本分配,下接恢复与排障;不要孤立地把单角色文档当成完整正确性证明。
本节补记:机制结论以 SIGMOD 2021 与 FoundationDB 7.x 官方
Architecture / Developer Guide / Known Limitations
为准;若源码字段名随小版本重命名,以对应 tag 的
fdbserver 实现为准,不把过时幻灯片当契约。
阅读本篇时建议同时打开系列 index 中的依赖图:本篇上承写路径与版本分配,下接恢复与排障;不要孤立地把单角色文档当成完整正确性证明。
站内对读时请同时打开 第 7 篇写流水线 与 第 9 篇严格可串行化,以免把日志或存储角色误当成隔离机制本身。
以上版本词汇可直接用于阅读 fdbserver
日志恢复相关代码路径。
参考资料
论文(A 级)
- Zhou et al. FoundationDB…. SIGMOD 2021(§2.4.3 Logging、§2.4.4 Recovery、§2.5 Replication、§3 Geo、Figure 2–4)。
- Mohan et al. ARIES: A Transaction Recovery Method…. TODS 1992(对照:传统 redo/undo 恢复)。
官方文档 / 设计(A 级)
- FoundationDB 7.x, Architecture — Transaction Logs、Background Work。
- apple/foundationdb,
design/recovery-internals.md— knownCommittedVersion、recovery version 工程描述。
站内对读
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【FoundationDB 内核】写事务流水线:Client → Commit Proxy → Sequencer → Resolver → TLog
钉住 FoundationDB 一次写事务的提交路径:Client 经 Commit Proxy 取 commit version、Resolver 做 OCC、TLog 多数持久化后才向客户端返回成功;并划清「提交成功」与「Storage Server 可读」之间的边界。
【FoundationDB 内核】严格可串行化与 5 秒限制:实时顺序与恢复有界性
说明 FoundationDB 如何用 Sequencer 版本把 OCC+MVCC 提升到严格可串行化,区分 serialization order 与实时顺序;论证 5 秒事务上限如何约束冲突窗口与恢复日志量,并点明高冲突下的开放问题。
【FoundationDB 内核】Storage Server:Shard、版本读取与读路径
说明 FoundationDB Storage Server 如何托管 shard、按版本服务读、从 TLog 异步拉日志并参与迁移;强调读路径直达 Storage、不经过 Commit Proxy 提交流水线,以及与 Data Distributor 的边界。
【FoundationDB 内核】Unbundled 全景:事务、日志与存储为何拆开
以 SIGMOD 2021 为锚点,厘清 FoundationDB 控制面与数据面角色分工,说明一次事务穿过 Proxy、Sequencer、Resolver、TLog、Storage 的路径,并与站内 distributed/39、tikv-htap 划清边界。