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

【FoundationDB 内核】Sequencer:read/commit version、逻辑单点与恢复

文章导航

分类入口
databasestorage
标签入口
#foundationdb#sequencer#master#read-version#commit-version#recovery#strict-serializability

目录

严格可串行化需要一个全局顺序。FoundationDB 把这个顺序钉在 版本号 上:事务开始时拿到 read version,提交时拿到更大的 commit version;commit version 既是序列化顺序,也是日志序号(LSN)语义的来源(Zhou et al., SIGMOD 2021 §2.4.2)。发放与推进这些版本的权威,就是 Sequencer(7.x 文档常称 Master)。

常见误解是:「有单点 Sequencer = 没有高可用」。更准确的说法是:每一世代只有一个活跃 Sequencer,失败则整代事务系统恢复并由新 Sequencer 接管——可用性靠短 MTTR,而不是靠多活同时发号。

本文是系列第 6 篇(第一部分收官):版本分配规则、与 Proxy 的协作、逻辑单点含义、恢复时版本与客户端如何衔接。写流水线细节见第 7 篇;严格可串行化证明见第 9 篇;完整恢复状态机见第 13 篇。

本文是「FoundationDB 内核」系列第 6 篇(共 18 篇)。→ 系列目录

篇目 核心内容
第 5 篇 · 角色拓扑 读写总图
第 6 篇 · Sequencer 版本分配、单点、恢复
第 7 篇 · 写事务流水线 Client → TLog 端到端

版本锚定:Zhou et al., SIGMOD 2021 §2.3.2、§2.3.5、§2.4.1–2.4.4;FoundationDB 7.x Architecture(Master、Determine Read Version、Transaction Commit、Transaction System Recovery)。下文统一称 Sequencer,括注文档名 Master。


一、两个版本:读快照与提交序号

论文 §2.4.1–2.4.2:

版本 谁申请 不变量(论文表述) 用途
read version 客户端经 Proxy(现 Grv Proxy)向 Sequencer 不低于启动时已知的已提交版本 MVCC 读快照;只读事务的序列点
commit version Commit Proxy 向 Sequencer 大于任何现有 read / commit version 序列化顺序 / LSN;Resolver 与 Log 按此排序

Sequencer 选择 commit version 时以约 每秒一百万 的速率推进版本空间(论文 §2.4.1)。这是版本计数设计,便于在物理时钟抖动下仍有足够序号粒度;不是「集群每秒恰好一百万笔事务」的吞吐断言。

为避免 LSN 空洞,Sequencer 在返回 commit version 时附带 previous commit version;Proxy 把二者传给 Resolver 与 Log Server,使之按序处理(论文 §2.4.2)。

flowchart LR
  rv["read version R"]
  cv["commit version C"]
  rv -->|"R <= committed at start"| snap["MVCC snapshot"]
  cv -->|"C > all prior R and C"| order["Serialization order / LSN"]
  snap --> txn["Transaction body"]
  txn --> order

1.1 严格可串行化的直觉(预告)

因为事务在 \(R\) 上读到「当时已提交」的结果,又在更大的 \(C\) 上提交,且冲突检测按 \(C\) 排序拒绝读写冲突,论文称由此得到 strict serializability(§2.4.2)。完整论证、与实时顺序、5 秒窗口的关系见第 9 篇;本篇只固定版本权威在 Sequencer。


二、与 Proxy 的协作:Sequencer 不面向海量客户端

2.1 读版本路径

7.x 文档:Grv Proxy 向 Master 取最大已提交版本,并检查日志存活,再返回给客户端(第 4 篇)。Master 作为「各 Commit Proxy 已提交版本的汇聚点」,避免客户端直连单点造成连接爆炸。

2.2 提交版本路径

Commit Proxy 为每批事务向 Sequencer 申请 commit version;成功 durable 后把 committed version 汇报回去,推动后续 read version 前进。空提交用于客户端安静时仍推进版本(第 4 篇)。

sequenceDiagram
  participant Grv as Grv Proxy
  participant CP as Commit Proxy
  participant Seq as Sequencer
  participant Client
  Client->>Grv: get read version
  Grv->>Seq: latest committed
  Seq-->>Grv: R
  Grv-->>Client: R
  Client->>CP: commit
  CP->>Seq: next commit version
  Seq-->>CP: C, prev
  Note over CP: Resolver + TLog ...
  CP->>Seq: report committed C

2.3 历史职责:招募写子系统

论文 §2.3.2:因历史原因,Sequencer 还招募 Proxies、Resolvers、LogServers。官方文档中 Master 协调写子系统换代。对排障的含义是:Sequencer 进程退出往往不是「只丢一个发号器」,而是一代写管线作废(第 3、13 篇)。


三、逻辑单点 ≠ 无高可用

说法 更准确的机制
「Sequencer 单点故障集群永久不可用」 失败后 Cluster Controller 招募新 Sequencer,走 reconfiguration;论文目标是短 MTTR
「多 Sequencer 同时发号做 HA」 同一世代只允许一个权威;双活发号会破坏版本不变量
「Sequencer 存着全部事务状态」 版本与世代协调为主;Proxy/Resolver 无用户持久状态,耐久在 Log

论文设计原则 Make failure a common case(§2.1):与其在写路径上处理每一种局部故障,不如关掉当前世代、走统一恢复。官方 Architecture:Master 监控 Grv/Commit Proxy、Resolver、Transaction Log;任一失败则 Master 退出,Controller 招募新 Master,开启新 epoch。

stateDiagram-v2
  [*] --> EpochN: Sequencer N active
  EpochN --> Detect: failure in write subsystem
  Detect --> Suicide: Sequencer exits
  Suicide --> Recruit: Cluster Controller recruits
  Recruit --> Recover: new Sequencer recovery
  Recover --> EpochN1: Sequencer N+1 accepts commits

3.1 恢复与版本、客户端

论文 §2.4.4 与官方恢复节要点(细节第 13 篇):

  1. 新 Sequencer 从 Coordinators 读出并锁定上一代配置,防止并发恢复。
  2. 停止旧 Log 接受新事务,招募新 Proxy / Resolver / Log。
  3. 确定 Recovery Version(RV),丢弃 RV 之后的半提交状态;Storage 后台继续拉日志,恢复不依赖 ARIES 式整库 redo。
  4. 官方补充:恢复可 快进约 90 秒,令进行中事务 transaction_too_old,重试时附着新世代。
  5. 提交中遇到恢复的客户端可能看到 commit 结果未知,必须幂等重试(第 2 篇)。

版本租约 / 窗口与 5 秒 MVCC 的关系:Storage 与 Resolver 只保留短窗口历史;恢复快进进一步把旧读版本作废。应用侧表现仍是「短事务 + 可重试」,而不是跨分钟的快照会话。


四、具体故事:两个提交如何排成全序

事务 \(T_1\)\(T_2\) 并发:

  1. 两者经 Grv Proxy 取得 read version \(R_1\)\(R_2\)(可能相同或相近)。
  2. \(T_1\) 先进入 Commit Proxy,获得 commit version \(C_1\),Resolver 通过,TLog durable,Proxy 向 Sequencer 汇报。
  3. \(T_2\) 稍后获 \(C_2 > C_1\)。若 \(T_2\) 的读集与 \(T_1\) 的写集在 \((R_2, C_2)\) 上冲突,Resolver 拒绝 \(T_2\)
  4. 若无冲突,\(T_2\)\(C_2\) 排在序列化历史中 \(T_1\) 之后。

Sequencer 不检查 key 冲突——它只保证版本单调与全序编号;冲突是 Resolver 的职责(第 8 篇)。把 Sequencer 理解成「全局锁管理器」会误导优化方向:降低冲突要靠数据模型与减少读冲突范围,而不是「加更多 Sequencer」。

flowchart TB
  subgraph seqDuty ["Sequencer duty"]
    mono["Monotonic versions"]
    prev["Prev LSN chaining"]
    epoch["Epoch recruitment"]
  end
  subgraph notDuty ["Not Sequencer duty"]
    keys["Per-key locking"]
    sql["SQL planning"]
    store["Serving user reads"]
  end

常见误解

  1. 「逻辑单点就是单机玩具」。生产系统用快速检测 + 重配换取 \(f+1\) 复制等资源优势(论文 Abstract);是否适合你的故障域,是部署问题。
  2. 「read version 由每台 Storage 本地分配」。版本权威在 Sequencer;Storage 只提供给定版本上的数据。
  3. 「commit version 等于墙上时钟」。版本按策略推进,与实时顺序的关系在严格可串行化定义下讨论(第 9 篇),不要当成 NTP 时间戳。

工程间隙

论文评价恢复在生产上通常较短(§5.3)。真实尾部取决于失败检测灵敏度、日志封印、RV 计算与客户端重试风暴。多租户高冲突时,恢复后的重试可能叠加成负载尖峰——Ratekeeper 与应用退避需要一起设计(第 16–17 篇)。形式化验证与确定性模拟如何覆盖「发号 + 恢复」交错,见第 14 篇与 distributed/54。


五、版本、租约与 5 秒窗口如何咬合

「版本租约」在运维口语里有时被说成 Sequencer 对时间的控制;机制上更精确的拆法是三条互相约束的规则:

  1. 序号规则:commit version 单调,read version 不低于已知提交(Sequencer + Proxy 汇报)。
  2. 窗口规则:Storage / Resolver 只保留约 5 秒的多版本与冲突历史(官方 Architecture;Known Limitations)。
  3. 世代规则:恢复快进使旧 read version 迅速过期,客户端必须重挂新世代。

因此应用看到的「事务不能太长」,不是任意超时参数,而是 MVCC 状态有界 的直接推论——有界状态使恢复与内存占用可预期,却禁止长事务(论文与 Anti-Features 立场一致)。第 9 篇会把这三条收进严格可串行化论述。

flowchart TB
  seq["Sequencer versions"]
  win["5s MVCC / conflict window"]
  epoch["Recovery epoch fast-forward"]
  seq --> client["Client transactions"]
  win --> client
  epoch --> client
  client --> retry["Short, idempotent retries"]

5.1 与 PD TSO 的一句对照

TiKV 生态里 PD TSO 也是全局时间权威(见 tikv-htap 第 9 篇规划)。相似点:都避免「每个存储节点自己发时间戳」。差异点:FDB 的 Sequencer 与写子系统世代、日志封印绑定;TSO 与 Multi-Raft 提交路径的耦合方式不同。不要假设调 PD 的经验能直接翻译成调 Sequencer。

5.2 第一部分收束:01–06 各自钉住什么

读者应带走的一句
01 Unbundled 把 TS/LS/SS 拆开,事务穿多跳角色
02 客户端契约是冲突范围 + 重试 + 5 秒窗
03 Coordinators 共识的是配置与世代,不是每个 mutation
04 Grv 发安全 read version;Commit 编排提交三步
05 读扩 Storage,写扩 Proxy/Resolver/Log;单例靠恢复
06 Sequencer 发全序版本;逻辑单点通过换代保可用

下一篇开始第二部分,把 Commit Proxy 之后直到 TLog 的路径写成可逐步对照源码/文档的流水线。

flowchart LR
  p1["01-06 roles"] --> p2["07-09 txn SSI"]
  p2 --> p3["10-12 log storage"]
  p3 --> p4["13-18 ops layer"]

5.3 开放问题(版本权威)

全局单点发号简化了严格可串行化论证,却把「发号器世代切换」变成集群可用性的敏感点。是否存在可水平扩展、又保持同一严格可串行化契约的版本服务,仍是分布式事务系统里反复出现的设计张力——FDB 的回答是 Proxy 批处理 + 快速恢复,而不是多活 Sequencer。第 9、18 篇会在一致性与选型语境下再碰这个问题。

对应用开发者,开放问题落地成一条纪律就够:不要把跨秒级的业务工作流塞进一个 FDB 事务;把长流程拆成多个短事务,并用业务幂等键消化 commit_unknown_result。核心用有界窗口换可恢复性,Layer 与应用补齐长流程。


六、小结

三句话小结

  1. Sequencer 为事务分配 read version 与严格更大的 commit version(附 previous LSN),定义序列化顺序;冲突检测不在 Sequencer 上做。
  2. 每一世代一个活跃 Sequencer 是逻辑单点;高可用靠失败后整代恢复与短 MTTR,而不是多活同时发号。
  3. 客户端经 Proxy 间接使用 Sequencer;恢复会导致旧版本作废或提交结果未知,应用必须短事务且幂等。

第一部分(01–06)到此收束。下一篇把提交路径一次性串完:写事务流水线


参考资料

核心论文

  1. Zhou et al., SIGMOD 2021, §2.1、§2.3.2、§2.3.5、§2.4.1–2.4.4(原则、Sequencer、版本、恢复)。

规范 / 文档

  1. FoundationDB Architecture — Master、Determine Read Version、Transaction Commit、Transaction System Recovery(7.x)。

站内

  1. 第 4 篇 Proxy第 5 篇角色拓扑
  2. distributed/54 确定性模拟(第 14 篇前置)。
  3. 本系列 index

上一篇角色拓扑与数据流

下一篇写事务流水线

返回 系列目录 · 数据库索引

同主题继续阅读

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


By .