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

【FoundationDB 内核】Proxy:Grv 与 Commit、批处理与 read version

文章导航

分类入口
databasestorage
标签入口
#foundationdb#grv-proxy#commit-proxy#read-version#batching#ratekeeper

目录

客户端几乎从不直连 Sequencer。拿读版本找 Grv Proxy,提交找 Commit Proxy——在 SIGMOD 2021 论文里二者还常合称 Proxies;FoundationDB 7.x 官方架构文档已拆成两个可独立扩展的角色。把它们当成「无状态 RPC 网关」不算错,但会漏掉两件关键工作:read version 的安全含义,以及 提交三步编排(要版本 → Resolver → TLog)

本文是系列第 4 篇,只钉 Proxy 层:分工、发版本、提交入口、批处理与 Ratekeeper 的交互。Sequencer 如何选号见第 6 篇;Resolver / TLog 细节见第 8、10 篇;端到端写流水线见第 7 篇。

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

篇目 核心内容
第 3 篇 · Coordinator 配置与招募
第 4 篇 · Proxy Grv / Commit、read version、批处理
第 5 篇 · 角色拓扑 读写总图

版本锚定:FoundationDB 7.x Architecture(GRV Proxies、Commit Proxies、Determine Read Version、Transaction Commit);Zhou et al., SIGMOD 2021 §2.3.2、§2.4.1(Proxies 职责与提交流程)。命名:正文用 Grv Proxy / Commit Proxy;引用论文时保留 Proxies 统称。


一、为什么拆成两个 Proxy

官方 Architecture 把读版本发放与提交编排拆开,缩放轴不同:

角色 客户端主要用途 与 Sequencer 的关系 状态
Grv Proxy getReadVersion 询问当前最大已提交版本等 无持久状态;挂在一代事务系统上
Commit Proxy commit 申请 commit version;汇报已提交版本 无持久用户状态;持有内存中的 shard 映射等

论文时代的描述是「Proxies offer MVCC read versions … and orchestrate transaction commits」(§2.3.2)。7.x 拆分后,读多写少的负载可以多布 Grv Proxy,而不把提交路径的 CPU 与连接数绑在同一进程画像上。

Cluster Controller 通过 Coordinators 路径让客户端刷新 Proxy 列表(第 3 篇)。Proxy 随事务系统世代替换:旧 Proxy 在恢复后不应再安全地发放「新鲜」读版本——下一节的日志存活检查就是为此。

flowchart LR
  client["Client"]
  grv["Grv Proxy"]
  commit["Commit Proxy"]
  seq["Sequencer"]
  res["Resolver"]
  tlog["Log Servers"]
  ss["Storage Servers"]
  client -->|"get read version"| grv
  grv --> seq
  client -->|"reads"| ss
  client -->|"commit"| commit
  commit --> seq
  commit --> res
  commit --> tlog

二、Grv Proxy:read version 不是随便取的整数

官方 Determine Read Version 一节给出 Grv Proxy 发版本时的要点:

  1. 向 Master/Sequencer 询问当前最大已提交版本(Master 汇总各 Commit Proxy 的 committed version)。
  2. 同时检查满足复制策略的一组 Transaction Log 仍存活(未被停止)。
  3. 将得到的版本作为 read version 返回客户端。
  4. Ratekeeper 可通过压低发版本速率做过载保护。

第二条容易被忽略。Grv Proxy 是无状态、按世代招募的。若恢复已发生、旧 Grv Proxy 仍存活,它可能继续发版本,导致只读事务看到陈旧世界;带写的事务稍后多半会 abort,但只读路径更危险。通过确认「本世代日志集仍有效」,Proxy 把「我是否已被新世代取代」绑到日志系统生命期上。

官方还解释了为何客户端不能人人直连 Sequencer 要版本:Sequencer/Master 难以水平扩展,直连会把它变成连接与批处理热点;Grv Proxy 负责汇聚、批处理与执行 Ratekeeper 预算。

sequenceDiagram
  participant Client
  participant Grv as Grv Proxy
  participant Seq as Sequencer
  participant TLog as Log Servers
  Client->>Grv: request read version
  par Version and liveness
    Grv->>Seq: latest committed version
    Seq-->>Grv: version V
    Grv->>TLog: check logs still live
    TLog-->>Grv: ok / failed
  end
  Note over Grv: Ratekeeper may delay / throttle
  Grv-->>Client: read version V

2.1 客户端拿到版本之后

客户端用该版本向 Storage Server 读(第 11 篇)。写入仍留在客户端缓冲(第 2 篇)。Grv Proxy 代理普通用户读流量——这是 Unbundled 读扩展的关键:读路径不经过提交管线(论文 §2.3.3)。


三、Commit Proxy:提交三步

官方 Transaction Commit 与论文 §2.4.1 一致,Commit Proxy 对一笔事务:

  1. 向 Sequencer 申请 commit version(严格大于已知读/提交版本;论文称 Sequencer 以约每秒一百万版本的速率推进——这是版本空间设计,不是本站实测吞吐)。
  2. 把读/写冲突范围送给 Resolver(们);按 commit version 排序做 OCC。任一 Resolver 冲突则整笔失败,向客户端返回未提交错误。
  3. 无冲突则把 mutation 写入 Log Servers;待复制策略要求的日志 durable 后,向客户端返回成功,并向 Sequencer/Master 汇报 committed version,以便后续 read version 赶上。
sequenceDiagram
  participant Client
  participant CP as Commit Proxy
  participant Seq as Sequencer
  participant Res as Resolver
  participant TLog as Log Server
  Client->>CP: commit(read/write sets)
  CP->>Seq: allocate commit version
  Seq-->>CP: commit version + prev
  CP->>Res: conflict check
  alt conflict
    Res-->>CP: abort
    CP-->>Client: not_committed
  else no conflict
    Res-->>CP: admit
    CP->>TLog: persist mutations
    TLog-->>CP: durable
    CP->>Seq: report committed version
    CP-->>Client: success
  end

3.1 系统键与路由缓存

\xff 开头的系统元数据变更会经 Resolver 分发给各 Commit Proxy(官方 Architecture)。其中包含 key range → Storage Server 映射。客户端向 Commit Proxy 按需拉取并缓存;若打到错误的 Storage Server,清空缓存再问 Proxy。Put 路径上 Proxy 也用内存 shard map 决定 mutation 打到哪些 Log 标签(论文 Figure 2,细节第 7、10 篇)。

3.2 空提交与批处理

官方 Background Work:Commit Proxy 会周期性向 Transaction Log 发送空提交,以在客户端安静时仍推进 commit version。生产实现里,对 Sequencer 的版本请求与对 Resolver / TLog 的调用都会批处理——论文明确指出先定 commit version 再冲突检测,便于批量分配版本与批量检测(§2.4.2)。

工程含义:Proxy 是延迟与吞吐的汇聚点。多 Proxy 分摊客户端连接;但每一笔仍依赖 Sequencer 的版本权威(第 6 篇)和 Resolver 的窗口状态(第 8 篇)。


四、Ratekeeper:踩刹车踩在发版本上

Ratekeeper 由 Cluster Controller 招募(第 3 篇),从 Grv / Commit Proxy、Log、Storage 收集负载,计算目标事务速率(官方 Architecture;论文 §2.3.1)。

过载时主要杠杆是:降低 Grv Proxy 发放 read version 的速率。没有新的 read version,新事务进不了读/写循环,比在存储层随机拒绝更可控。这是系统级反压,不是应用层 HTTP 429 的简单翻译。

flowchart TB
  rk["Ratekeeper"]
  grv["Grv Proxies"]
  cp["Commit Proxies"]
  tlog["Log Servers"]
  ss["Storage Servers"]
  grv -->|"stats"| rk
  cp -->|"stats"| rk
  tlog -->|"stats"| rk
  ss -->|"stats"| rk
  rk -->|"throttle read version rate"| grv

常见误解

  1. 「Proxy 就是 TiDB / SQL 层」。Proxy 不懂表与 SQL;Record Layer 等在客户端进程里,经同一 KV 事务 API 访问集群(第 15 篇)。
  2. 「读也必须过 Commit Proxy」。普通读过 Storage;Commit Proxy 参与提交与元数据/路由,不承担全量读带宽。
  3. 「Grv Proxy 返回的版本保证 Storage 上立刻有该版本的全部数据」。版本保证的是序列与可见性契约;Storage 异步 apply 可能有短暂滞后,客户端可读其他副本或重试(论文 §2.4.3)。机制见第 10–11 篇。

工程间隙

论文微基准提到单线程 Resolver 的冲突检测能力(§2.4.2);那是 Resolver 侧数字,且依赖特定假事务形态。Proxy 侧真实延迟取决于批大小、日志 fsync、跨 Resolver 分片与恢复频率。本站无实测则不给出「Proxy P99」类结论;排障时把 Proxy 与 TLog、Storage 落后分开看(第 17 篇)。


五、批处理:为什么先定版本再检测冲突

论文 §2.4.2 对比一类「先冲突检测再分配时间戳」的写法(如某些 write-snapshot isolation 变体):FDB 向 Sequencer 要 commit version,做 Resolver 检测。好处是版本分配与冲突检测都可以按批切割;代价是已分配版本的事务若最终 abort,仍可能在部分 Resolver 上留下写历史,造成短窗口内的假阳性冲突(论文亦承认,并指出生产低冲突与 5 秒过期会限制影响)。

对 Proxy 实现者/排障者:

  1. 批太大:单批尾延迟上升,客户端 P99 变差。
  2. 批太小:Sequencer / Resolver RPC 次数上升,CPU 与连接开销变差。
  3. 跨多个 Resolver 的事务:任一否决即整笔失败;假阳性放大在「写集跨越多个 Resolver 分片」时更明显。
flowchart LR
  batch["Commit Proxy batch"]
  ver["Allocate commit versions<br/>from Sequencer"]
  occ["Parallel Resolver checks"]
  log["Persist winners to TLog"]
  batch --> ver --> occ --> log

这不是让你在业务里「调 Proxy 批大小」——多数部署用默认;而是理解:提交路径的延迟结构是批处理队列,而不是逐笔锁等待。

5.1 客户端该打哪个 Proxy

客户端从 Cluster Controller 拿到 Proxy 列表后,通常在列表上做选择/负载分担(具体策略随 binding 版本演进,以官方客户端行为为准)。要点是:

5.2 一篇里能记住的延迟分解

把一笔写提交的客户端可见延迟粗拆为(不含 Storage apply):

组件 主导成本
A Grv Proxy ↔︎ Sequencer(若本笔需要新读版本) 批处理排队、Ratekeeper
B Commit Proxy ↔︎ Sequencer 版本批分配
C Commit Proxy ↔︎ Resolver(s) OCC CPU、跨 Resolver 协调
D Commit Proxy ↔︎ Log Server 复制 + fsync

业务「commit 慢」要先分清卡在 B/C/D 哪一段:C 高往往是冲突或 Resolver 过载;D 高是日志盘或复制;B 高才更像 Sequencer/批处理问题。第 7、10、17 篇会回到这张表。

flowchart LR
  A["A: read version"] --> B["B: commit version"]
  B --> C["C: resolve"]
  C --> D["D: log durable"]
  D --> E["Client success"]

六、小结

三句话小结

  1. 7.x 将 Proxy 拆成 Grv(发 read version + 日志存活检查 + Ratekeeper 节流)与 Commit(要 commit version、跑 Resolver、写 TLog)。
  2. read version 必须赶上已知提交,且发放者须仍属于当前日志世代,否则只读可能陈旧。
  3. Commit Proxy 是写管线编排器而非存储引擎;批处理与空提交服务于版本推进与吞吐,细节流水线见第 7 篇。

下一篇把角色收成一张读写总图:角色拓扑与数据流


参考资料

核心论文

  1. Zhou et al., SIGMOD 2021, §2.3.2–2.3.3、§2.4.1–2.4.2(Proxies、读写分离扩展、提交三步、批处理动机)。

规范 / 文档

  1. FoundationDB Architecture — GRV Proxies、Commit Proxies、Determine Read Version、Transaction Commit、Ratekeeper(7.x)。

站内

  1. 第 3 篇 Coordinator
  2. 本系列 index

上一篇Coordinator 与集群配置

下一篇角色拓扑与数据流

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

同主题继续阅读

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


By .