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

【FoundationDB 内核】故障恢复:角色招募与恢复 epoch

文章导航

分类入口
databasestorage
标签入口
#foundationdb#recovery#epoch#sequencer#log-server#coordinator#fault-tolerance

目录

前几篇把写路径拆到 TLog 提交点Storage Server 异步应用。生产里真正决定可用性窗口的,往往不是「某个角色挂了怎么修」,而是 FoundationDB 如何把几乎所有事务系统故障收敛到同一条恢复路径。本文按 Zhou et al., SIGMOD 2021(下称 FDB paper)拆:角色招募、恢复 epoch、Recovery Version(RV)/ Previous Epoch end Version(PEV)、写暂停与读边界、旧角色隔离。

版本锚定:机制以 FoundationDB: A Distributed Unbundled Transactional Key Value Store(SIGMOD 2021)与 FoundationDB 7.x 官方 Architecture / Administration 文档为准。论文给出的生产恢复耗时是作者部署观测,不是本站实测。


一、设计前提:把失败做成常态路径

FDB paper 把事务系统的故障策略写成两句原则:

  1. Make failure a common case:检测到故障时,事务管理系统主动关闭,而不是为每一种故障写独立修复分支。
  2. Fail fast and recover fast:可用性目标落在缩短 Mean-Time-To-Recovery(MTTR)——检测、主动停写、恢复——而不是无限拉长局部自愈。

因此,Proxy、Resolver、Log Server(TLog)乃至 Sequencer 自身的许多失败,最终都落到:ClusterController 招募新 Sequencer → 新 Sequencer 走同一套 bootstrapping / recovery 步骤 → 开启新的 epoch。配置变更也走同一路径:改配置会终止当前 Sequencer,再由新 Sequencer 重建事务系统。

flowchart TD
  FAULT["Fault or config change"]
  FAULT --> DET["ClusterController detects Sequencer / role failure"]
  DET --> KILL["Current Sequencer terminates"]
  KILL --> RECRUIT["Recruit new Sequencer"]
  RECRUIT --> EPOCH["New epoch: lock Coordinators, stop old TLogs, recruit roles"]
  EPOCH --> ACCEPT["Accept commits in new epoch"]

这条路径和 Coordinator 配置篇 的职责边界一致:Coordinator 持久化的是事务系统拓扑与配置,不是用户数据;用户数据仍在 Storage / Log 一侧。


二、Epoch:一代事务系统的边界

每个 epoch 对应一代事务管理系统,由唯一的 Sequencer 进程标识。同一时刻只应有一个 Sequencer 持有 Coordinator 上的锁并推进恢复;否则会出现两套 Proxy/TLog 同时收写。

概念 含义 本系列位置
Epoch 一代 TS/LS(Sequencer + Proxy + Resolver + TLog) 本篇
Commit version / LSN Sequencer 分配的提交序 第 6 篇第 7 篇
KCV Proxy 已知已提交的最大 LSN 本篇 §四
DV LogServer 已持久化的最大 LSN 本篇 §四
PEV 上一 epoch 已确认提交的上界(max KCV) 本篇 §四
RV 恢复版本(min DV);高于 RV 的未充分确认数据可丢弃 本篇 §四

Proxy 与 Resolver 无持久用户状态:招募新实例即可,恢复步骤几乎没有额外工作。难的是 LogServer:必须保证「客户端已经收到 commit 成功」的日志,在配置的复制度下仍可被 Storage Server 取回。


三、恢复步骤:从锁定到接受新提交

FDB paper §2.4.4 给出 Sequencer 在每个 epoch 上的恢复顺序。下图按论文步骤组织(图内英文标签):

sequenceDiagram
  participant CC as ClusterController
  participant SEQ as New Sequencer
  participant COORD as Coordinators
  participant OLD as Old LogServers
  participant NEW as New Proxies Resolvers TLogs
  participant SS as StorageServers

  CC->>SEQ: Recruit Sequencer
  SEQ->>COORD: Read prior TS state and lock
  SEQ->>OLD: Stop accepting new transactions
  OLD-->>SEQ: Reply with DV and KCV
  SEQ->>NEW: Recruit Proxies Resolvers LogServers
  SEQ->>COORD: Write new TS state unlock
  SEQ->>SS: Special recovery txn carries RV
  SEQ->>SEQ: Accept new commits from PEV+1

逐步对照:

  1. 读并锁定 Coordinator 状态
    Sequencer 读取上一世代的事务系统配置(含旧 LogServer 集合),并对协调状态加锁,防止另一 Sequencer 并行恢复。

  2. 停止旧 LogServer 收写
    旧 TLog 不再接受 Proxy 的新日志;响应中带回各自的 DV 与 KCV,供计算 RV / PEV。

  3. 招募新 Proxy、Resolver、LogServer
    由新 Sequencer 完成(论文写明 Sequencer「for historical reasons」也负责招募这些角色)。Proxy / Resolver 无状态;新 TLog 需要承接日志职责。

  4. 写回新拓扑并解锁
    当前 epoch 的角色集合持久化到 Coordinator 后,恢复路径才算完成「配置世代」切换。

  5. 接受新提交
    第一条特殊恢复事务把 RV 告知 StorageServer,使其丢弃内存中高于 RV 的多版本数据;随后正常写路径从新 epoch 的版本区间继续。

论文强调:StorageServer 始终在后台从 LogServer 拉日志并应用——这与正常前向路径相同。因此恢复不需要 ARIES 式从 checkpoint 重放 redo/undo;恢复只需要找到 redo 日志的终点(RV),重放仍由 Storage 异步完成。新事务系统甚至可以在旧 LogServer 上全部数据尚未被消化完之前就开始接受新事务。


四、RV 与 PEV:提交有界与旧日志隔离

Proxy 向 LogServer 写日志时捎带自身的 Known Committed Version(KCV)。每个 LogServer 维护:

恢复时,设日志复制度为 \(k\)。Sequencer 在收到足够多(论文写「多于 \(k-1\)」;另可用 Copyset 把门槛降到 \(k/2\))的旧 LogServer 应答后:

\[ \mathrm{PEV} = \max_i \mathrm{KCV}_i,\qquad \mathrm{RV} = \min_i \mathrm{DV}_i \]

版本 语义
PEV 上一 epoch 已向客户端确认提交的上界;此前数据已按复制策略落稳
新 epoch 起始 \(\mathrm{PEV}+1\)
RV 恢复点;区间 \([\mathrm{PEV}+1,\mathrm{RV}]\) 上的日志需从旧 TLog 拷到新 TLog,以补齐复制度
\(> \mathrm{RV}\) 可能未充分确认;Storage 侧丢弃内存 MVCC 中高于 RV 的部分

拷贝区间通常只覆盖「数秒」量级的日志后缀(论文定性描述,非本站计时)。这与 第 9 篇 的 5 秒事务窗口同一数量级:冲突历史与未落稳日志都不允许无限变长。

flowchart LR
  subgraph versions ["Version axis"]
    PEV["PEV = max KCV"]
    RV["RV = min DV"]
    NEXT["New epoch starts at PEV+1"]
  end
  PEV --> NEXT
  NEXT --> COPY["Copy PEV+1..RV to new TLogs"]
  RV --> DROP["Discard SS in-memory data above RV"]

旧角色隔离由此落地:旧 LogServer 在停止收写并交出 DV/KCV 之后,不再参与新 epoch 的提交;若进程仍存活,其「继续写」路径已被显式关闭。旧 Proxy / Resolver 随世代切换被新实例替代,其内存冲突历史也不跨 epoch 复用——跨 epoch 的读写事务本来就不能存活(第 9 篇 的时间上限与世代切换一致)。

4.1 为何可以「先开写、后追日志」

传统 ARIES 路径里,恢复完成前通常要先把 redo 推到数据页一致点。FDB 把「找到 redo 终点」与「把 redo 应用到 Storage」拆开:

步骤 是否在恢复临界路径 承担者
锁定 Coordinator、停旧 TLog、算 RV/PEV 新 Sequencer
招募新 Proxy / Resolver / TLog 新 Sequencer
特殊恢复事务广播 RV 是(短) 新世代写路径首笔
Storage 拉齐 \([\mathrm{PEV}+1,\ldots]\) 日志 否(后台) StorageServer
旧 TLog 数据最终可退役 追平后的运维/GC 语义

因此 MTTR 主要由控制面与日志终点发现决定,而不是由数据体积上的全量重放决定。这是 Unbundled 在恢复维度上的直接收益,也是第 14 篇确定性模拟必须反复覆盖的路径:临界路径短,但分支多(应答子集、复制门槛、拷贝后缀失败等)。

4.2 复制策略与「谁失败会触发恢复」

FDB paper §2.5 区分三类复制:

运维含义:TLog 故障与 Storage 故障的用户可见面不同——前者常表现为写暂停窗口,后者常表现为迁移流量与局部读压力。混为一谈会找错排障入口(见 第 17 篇)。


五、写暂停、读边界与可用性窗口

5.1 写路径

恢复进行中,旧 TLog 已停写,新 Sequencer 尚未 Accept commits 时,提交路径不可用。客户端表现为提交超时、连接重置或需要重试的错误;正确的客户端应走官方文档中的自动重试语义(第 2 篇),而不是假定「部分提交已成功却无响应」。

论文的生产观测(FDB paper §5.3,作者部署数据):端到端恢复耗时通常 小于约 5 秒。这是论文数字,部署拓扑与负载不同时不可外推成本站结论。

5.2 读路径

读请求直接打到分片后的 StorageServer(第 11 篇)。恢复设计刻意让 redo 应用与恢复解耦,因此:

工程上应把「写暂停」与「读是否完全可用」分开描述:恢复窗口里常见的是 提交中断、读降级或读版本获取失败,而不是整库进程全部停机。跨区域复制与卫星站点上的 failover 另见论文 §3;本篇只钉单集群事务系统恢复。

5.3 与 Storage 落后的关系

Storage 落后于 TLog 是常态异步间隙(论文 Figure 3 给出某一生产集群上的 lag 分布)。恢复不要求先把 Storage 追平;追平仍是后台拉日志。排障时若「恢复刚结束但读到旧数据」,应区分:RV 回滚apply 延迟——前者由恢复事务界定,后者是 第 17 篇 的 Storage 落后类问题。

5.4 客户端在窗口内应遵守的契约

结合 第 2 篇 的 API 契约,恢复窗口内的安全默认是:

  1. 把提交失败当未提交,除非客户端已收到明确的 commit 成功;
  2. 依赖官方重试循环,而不是在应用层对同一业务键做「补偿双写」;
  3. 区分 transaction_too_old(窗口/时长)与恢复导致的临时不可用——后者应在世代稳定后收敛,前者要改事务切分。

确定性模拟(第 14 篇)把「恢复中插入提交与重试」当成一等 workload,正是因为生产客户端几乎总会在窗口内重试。


六、学术谱系、工程间隙与开放问题

6.1 谱系

Work 会议·年份 与本篇关系
Mohan et al., ARIES TODS 1992 传统 WAL + checkpoint 恢复;FDB 明确对照并拒绝在恢复时同步重放 redo
Helland et al. / Deuteronomy 等 unbundled 思路 CIDR 等 事务管理与存储解耦;FDB 把 MVCC 放在 Storage 侧而非 TS
Zhou et al., FoundationDB SIGMOD 2021 本篇主锚:epoch、RV/PEV、fail-fast recovery
Ongaro & Ousterhout, Raft USENIX ATC 2014 Coordinator 用 disk Paxos;本系列不把 Raft 选举细节搬进 FDB 恢复

6.2 工程间隙

论文实验与叙述假设:恢复路径被确定性模拟充分覆盖(见 第 14 篇),且生产 MTTR 以作者集群为样本。公开资料没有给出「任意硬件 + 任意复制配置」下可外推的恢复 SLA 公式。运维侧能核对的是:fdbcli status 是否显示恢复中、角色是否重新招募、客户端重试是否收敛——而不是对照一张通用秒表。

6.3 开放问题

  1. 恢复窗口内的读服务等级如何对外契约化? 论文描述机制边界,但各厂商托管产品对「RPO/RTO / 读是否只读旧版本」的对外表述并不统一。
  2. Copyset 与分层故障域降低同时故障丢数据概率,但与「简单 \(k\) 副本」在运维复杂度上的长期权衡,缺少独立受控对比研究。
  3. 多区域卫星配置下自动/手动 failover 的一致性—延迟谱(论文 §3)仍是部署选择题,不是单一最优解。

七、常见误解

误解一:Sequencer 是单点,挂了就永久不可用。
Sequencer 是逻辑单例角色,失败会触发 ClusterController 招募新实例并开新 epoch;高可用来自快速恢复与 Coordinator 仲裁,而不是「同一进程永不挂」。

误解二:恢复要像传统 RDBMS 一样从 checkpoint 重放全部 redo。
FDB 恢复只定位 RV;Storage 的拉日志与日常前向路径相同,不在恢复临界路径上同步重放整段历史。

误解三:恢复期间整个集群完全不可读。
写路径会停;读是否可用取决于是否还要拿新 read version,以及 Storage 上已有版本。把「提交不可用」等同于「所有读失败」会误判故障面。


八、小结

三句话小结:

  1. FoundationDB 把事务系统故障收敛到「停当前世代 → 新 Sequencer 锁定 Coordinator → 停旧 TLog → 招募新角色 → 算 RV/PEV → 开新 epoch」同一条路径。
  2. Proxy/Resolver 无状态易替;LogServer 恢复的核心是用 KCV/DV 界定已确认提交与可丢弃后缀,并用特殊恢复事务隔离 Storage 内存中高于 RV 的数据。
  3. 写暂停是恢复窗口的硬边界;读边界取决于版本获取与 Storage 可见性,MTTR 数字只引用论文部署观测,不外推为通用 SLA。

参考资料

核心论文

  1. Zhou J., et al. FoundationDB: A Distributed Unbundled Transactional Key Value Store. SIGMOD 2021. §2.4.4 Recovery;§2.5 Replication;§5.3 恢复耗时观测。
  2. Mohan C., et al. ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging. ACM TODS 1992.(对照谱系)

官方文档 / 源码

  1. FoundationDB 7.x ArchitectureAdministration(Coordinator、角色与状态)。
  2. FoundationDB 源码树 fdbserver/(恢复与角色招募相关 actor;版本以所用 tag 为准)。

多区域卫星与 failover 细节以论文 §3 与当前版本文档为准,本篇为控制篇幅只给单集群事务系统恢复主线。

站内联动

  1. 第 3 篇 Coordinator第 6 篇 Sequencer第 10 篇 TLog第 14 篇确定性模拟

上一篇SSD Engine 与 Redwood
下一篇确定性模拟:FDB 的工程落点

同主题继续阅读

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


By .