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

【TiKV / HTAP 内核】CockroachDB 对照:Range + Raft 的另一种落地

文章导航

分类入口
databasestorage
标签入口
#cockroachdb#tikv#raft#leaseholder#parallel-commits#range#distributed-sql

目录

前 15 篇把 TiKV 的 Region、Multi-Raft、PD/TSO、Percolator 事务和 TiFlash Learner 拆完了。读者这时候很容易冒出一个问题:CockroachDB 不也是”分片 + Raft + KV”吗,是不是换了个名字的 TiKV? 答案是否定的——两者共享同一套分布式共识底座(Raft),但在角色划分事务提交协议部署形态上做出了不同的工程选择。本文只对照这三点,不重讲 Raft 协议本身(见 raft-explained),也不对 CockroachDB 的 SQL 优化器做展开。

版本锚定:CockroachDB 文档引用其官方 Architecture 系列文档(stable 版本,A 级)与 Taft et al., CockroachDB: The Resilient Geo-Distributed SQL Database, SIGMOD 2020(A 级论文)。TiKV 侧对照见本系列第 3–11 篇。


一、Range 与 Region:命名不同,切分逻辑相近

CockroachDB 把全局有序 key 空间切成连续的 Range,每个 Range 对应一个独立的 Raft 组,副本默认 3 份——这与 TiKV 的 Region 是同一类设计:都是”按 key range 分片 + 每片一个 Raft 组”的 Multi-Raft 思路(第 3、4 篇已经拆过 TiKV 侧的 range/epoch/peer 模型)。两者的分片大小、Split/Merge 触发条件在实现细节上有差异,但这属于参数级别的不同,不改变架构范式。

真正值得对照的是副本角色的划分方式。CockroachDB 官方文档明确区分了两个概念:

CockroachDB 通过 Leader leases 机制让这两个角色绝大多数时刻绑定在同一个副本上(官方文档:“The range lease is always colocated with Raft leadership via the Leader leases mechanism, except briefly during lease transfers”),但在 lease 转移的短暂窗口内二者可能短暂不一致。

TiKV 没有把这两个角色显式拆成两个可能分离的概念——Region 的 Raft Leader 直接承担对外读写的职责,不存在一个独立命名、可以临时与 Raft leader 分离的”租约角色”。这不是说 TiKV 完全没有”安全本地读”的优化(Follower Read 依赖 ReadIndex/lease read 类机制),而是 CockroachDB 把这个概念做成了用户可观察、文档明确定义的架构角色,TiKV 把等价的优化留在了 raftstore 内部实现里,不作为独立对外概念暴露。这是”文档化的显式分层”与”内部化的隐式实现”之间的工程选择差异,不代表哪一方缺少对应的机制。

flowchart TB
  subgraph CRDB["CockroachDB Range"]
    direction LR
    L1["Raft leader"] -.->|"usually same node<br/>via Leader leases"| LH["Leaseholder<br/>serves reads/writes"]
  end
  subgraph TiKVR["TiKV Region"]
    direction LR
    RL["Region Leader<br/>(Raft leader)"] --> SERVE["Serves reads/writes directly<br/>no separate lease role exposed"]
  end

二、事务协议:Percolator 2PC vs Parallel Commits

TiKV 的分布式事务落地 Percolator 模型(第 10 篇):Prewrite 阶段给每个 key 写 Lock(记录 Primary 位置),Commit 阶段先决 Primary、再异步决 Secondary,事务的提交状态由 Primary 的 Write 记录唯一决定。

CockroachDB 的事务模型走了不同路径。它的核心结构是写意图(Write Intent)——本质上是带事务记录指针的 MVCC 版本,兼具”临时值”和”排他锁”两种语义,思路上与 Percolator 的 Lock 类似;但状态判定不是靠”Primary key 是否已经从 Lock 变成 Write”,而是靠一个独立的事务记录(Transaction Record),记录状态在 PENDING/STAGING/COMMITTED/ABORTED 之间转移。

CockroachDB 在 2018 年之后引入的 Parallel Commits(SIGMOD 2020 论文、CockroachDB RFC 20180324,A/B 级)把提交延迟从两轮共识压缩到一轮:客户端发起 COMMIT 时,协调者把事务记录直接置为 STAGING,同时列出所有正在写入的 key;只要能证明这些 key 的写入都已经达成共识,事务就被判定为”隐式已提交”(implicitly committed),协调者可以立即向客户端返回成功,再异步把状态落实成 COMMITTED 并清理写意图。

维度 TiKV Percolator CockroachDB Parallel Commits
学术锚点 Peng & Wei, OSDI 2010(A 级,奠基论文) Taft et al., SIGMOD 2020(A 级,工程论文,非同一篇奠基工作的直接延伸)
提交判定 Primary key 的 Lock→Write 转换 事务记录进入 STAGING + 所有写意图达成共识可证明
时间戳来源 commit_ts 由 PD TSO 分配 时间戳由 CockroachDB 内部 HLC(Hybrid Logical Clock)生成,不依赖外部单点授时服务
提交延迟目标 两轮 Raft 复制(Prewrite + Commit) 通过 STAGING 与写意图并行,把提交路径压缩到接近一轮

需要谨慎的是:“Parallel Commits 比 Percolator 快一个量级”这类结论本站不下——两套协议面向不同的时钟模型(PD 集中式 TSO vs HLC)与不同的部署形态,没有同一口径、同一硬件的实测对比,公开资料也没有给出这样的直接数字。能确认的只是两者解决的问题相同(降低跨 key 提交的共识轮数),走的路径不同。

sequenceDiagram
    participant C1 as TiKV Client
    participant PD
    participant RA as Region A (Primary)
    participant RB as Region B (Secondary)

    C1->>PD: get start_ts
    C1->>RA: Prewrite(Primary)
    C1->>RB: Prewrite(Secondary)
    C1->>PD: get commit_ts
    C1->>RA: Commit(Primary) — 决议点
    C1-->>RB: async Commit(Secondary)

    Note over C1,RB: CockroachDB 对照(示意)

    participant C2 as CRDB Coordinator
    participant TR as Txn Record Range
    participant KA as Key A
    participant KB as Key B
    C2->>KA: write intent (pipelined)
    C2->>KB: write intent (pipelined)
    C2->>TR: write STAGING + list in-flight keys
    Note over TR,KB: 一旦所有 intent 达成共识<br/>即视为 implicitly committed
    TR-->>C2: return success early

三、部署形态:分离式组件 vs 单进程一体化

TiKV 生态是分离式的:TiDB(SQL 层,无状态)、TiKV(存储与事务)、PD(元数据/调度/TSO)是三个独立进程,各自独立扩缩容——[第 1 篇]会讲到的四件套分工在这里再次出现。

CockroachDB 是单进程一体化:SQL 层、分布式层(DistSender/range 路由)、复制层(Raft)、存储层全部编译进同一个 cockroach 二进制,每个节点既能是 SQL 网关,也能是某些 Range 的副本所在地。CockroachDB 官方架构文档把这个分层描述为 SQL → Transactional → Distribution → Replication → Storage 五层,全部跑在同一个进程空间里,节点之间通过 DistSender/BatchRequest 通信。

这个差异带来的实际后果:


四、时钟模型:集中式 TSO vs 混合逻辑时钟

事务提交顺序需要一个全局可比较的时间戳,这一点两个系统都认,但拿时间戳的方式不同:

这个差异是”集中式授时换取严格顺序、但引入单点往返”与”去中心化换取更低延迟、但需要限定并持续校验时钟偏移”之间的经典权衡,与 Spanner 用 TrueTime 限定时钟不确定性区间的思路同源,但 CockroachDB 的 HLC 不依赖专用硬件(如 GPS/原子钟),精度保证也相应弱一些——这一点 CockroachDB 官方文档本身也承认,其严格可串行化保证在极端的时钟漂移场景下依赖运维层面保证 max-offset 不被打破,而不是像 TrueTime 那样有硬件级别的不确定性区间保障。


五、隔离级别:Snapshot Isolation 与 Serializable 的真实代价

前四节讲的是复制、提交协议、部署形态、时钟模型的差异,但有一个更容易被忽略、后果却更直接的差异:两个系统默认给用户的隔离级别不一样

TiKV/TiDB 的 Percolator 事务模型本质上提供的是 Snapshot Isolation(SI)——TiDB 开发指南明确写的是”TiKV 是支持基于 MVCC 的 Snapshot Isolation 的多版本存储引擎”。SI 保证每个事务看到的是一个自身开始时刻的一致性快照,写写冲突会被检测(第 10 篇的 Lock 机制),但 SI 不保证可串行化——两个并发事务各自读一部分数据、各自更新另一部分数据,只要它们没有直接的写写冲突,SI 允许这两个事务的合并效果是任何单独串行执行都不会产生的结果。这类异常有个经典名字:写偏斜(Write Skew)

CockroachDB 默认给用户的是 SERIALIZABLE——官方文档明确写”默认情况下,CockroachDB 保证事务隔离级别是 SERIALIZABLE,不允许任何并发异常”。它能做到这一点,靠的是 Serializable Snapshot Isolation(SSI):Cahill, M., Röhm, U. & Fekete, A. 在 Serializable Isolation for Snapshot Databases(SIGMOD 2008,A 级)里给出的算法——在标准 SI 的基础上,运行时动态检测”读写依赖环”(rw-antidependency cycle),一旦检测到可能导致不可串行化结果的依赖模式,就主动中止其中一个事务,而不需要像悲观锁那样阻塞读写。CockroachDB 的设计文档直接引用了 Cahill 的论文(docs/design.md,A 级)作为其 SSI 实现的依据。

一个标准的写偏斜例子能说明两者的实际差异:

初始状态:账户 A 余额 100,账户 B 余额 100,业务规则「A+B 不能同时低于阈值 50 才能各自扣款」
事务 T1(在快照下检查 A+B=200 >= 100,扣 A 的 80)
事务 T2(在快照下检查 A+B=200 >= 100,扣 B 的 80)
T1、T2 并发执行,彼此看不到对方的更新(各自读到的是 A=100, B=100 的快照)
两者都提交成功:最终 A=20, B=20,A+B=40 < 100 —— 业务规则被违反,但纯 SI 系统里两个事务各自的写没有冲突
维度 TiKV / TiDB(Percolator,SI) CockroachDB(默认 SSI)
学术锚点 Percolator 论文本身只要求 SI(OSDI 2010),未承诺可串行化 Cahill et al., SIGMOD 2008(SSI 算法)
写偏斜 可能发生,需要应用层显式加锁(如 SELECT ... FOR UPDATE第 11 篇)规避 默认阻止,代价是可能触发事务重试(SQLSTATE 40001
用户可选项 只有 SI(TiDB 内部称为 Repeatable Read,语义等价 SI,而非 ANSI 标准的 RR) SERIALIZABLE(默认)与 READ COMMITTED 两档可选
性能权衡 无冲突检测开销,但需要开发者理解 SI 的异常边界 低竞争下开销接近 SI;高竞争下中止率上升,需要客户端做重试处理

这不是”哪个更先进”的问题,是两条不同的默认安全边界:选择 TiKV/TiDB 的团队,需要清楚自己的业务逻辑是否隐含”读一部分、判断后写另一部分”的模式(典型如余额检查、库存扣减),这类模式在 SI 下必须显式加锁;选择 CockroachDB 的团队默认不必操心这一点,但要设计好事务重试逻辑去应对 SSI 中止。这也是本系列第 11 篇强调”TiKV 无锁”是误解的原因之一:SI 的正确使用要求开发者在写偏斜风险场景主动加锁,锁不是可选项,而是弥补 SI 语义边界的必要手段。


六、陈旧读的两种实现:safe-ts vs Closed Timestamps

第 15 篇定义过 TiFlash 的 safe-ts:一个 Region 级别的时间戳下界,查询时间戳不超过它就能保证读到快照一致的结果;TiDB 的 Stale Read 靠 Resolved TS 让查询主动放宽新鲜度要求换取更低延迟。CockroachDB 有一套目标几乎完全对应的机制,叫 Closed Timestamps:Leaseholder 持续向所有副本(不止是分析副本)广播一个”承诺”——任何时间戳不超过这个值的写入都不会再被接受,所以任何副本只要 apply 到了对应的 Raft 日志位置,就能安全地服务这个时间戳以下的读请求(CockroachDB Docs, Transaction Layer §Closed Timestamps;CockroachDB Blog, An Epic Read on Follower Reads,A/B 级)。

两套机制的目标相同(让非 Leaseholder/非 Leader 的副本也能安全提供一致性读),但覆盖范围和实现细节有明显差异:

维度 TiFlash safe-ts CockroachDB Closed Timestamps
适用副本类型 仅 Learner(TiFlash),面向 HTAP 分析读 所有 Voter 副本,用于任意 Follower Read 场景(不限于分析负载)
推进依据 apply 进度 + 未决锁 resolve 结果(第 15 篇 §一 Leaseholder 承诺的时间戳下界,随 Raft 日志一起下发(piggyback 在 Raft 命令上)
触发读取的语法 AS OF TIMESTAMP / TIDB_BOUNDED_STALENESS(TiDB SQL 层) AS OF SYSTEM TIME(CockroachDB SQL 层)
与租约的关系 与 TiKV 的 Region Leader 无强绑定语义(Learner 独立判断 apply 进度) 显式与 Leaseholder 的 lease 生命周期绑定:新 Leaseholder 不会推翻旧 Leaseholder 已经关闭的时间戳承诺
语义 快照一致(Snapshot Isolation 意义上的一致),非可串行化 在 SERIALIZABLE 默认隔离级别下,历史读同样遵循串行化保证

关键差异在”覆盖范围”:TiFlash 的 safe-ts 只解决”分析副本能不能安全读”,因为 TiKV 的标准 Follower 走的是第 5 篇提到的 ReadIndex/Lease Read 机制,不需要 safe-ts 这套时间戳下界;CockroachDB 则把”任意副本能否安全提供历史读”做成了统一机制,不区分副本是不是列存/分析角色——这与第三节讲过的”单进程一体化,没有独立的分析副本类型”是同一个架构选择的延伸:CockroachDB 没有 TiFlash 这类专门角色,所以它必须让 Closed Timestamps 覆盖所有副本,才能达到”从任意副本读到不太旧的一致数据”的效果。


七、OceanBase 边界(一句话)

OceanBase 走的是第三条路:它用 shared-nothing + Multi-Paxos 做副本复制,但把 SQL 引擎、存储引擎、事务引擎全部集成在同一个 OBServer 进程里,并且默认让 OLTP 与轻量 OLAP 都直接由主副本的 LSM-tree 存储引擎服务(基线数据 + 增量数据两层),只有在需要更强分析能力时才引入行列混合存储或列存备份副本——这与 TiKV+TiFlash”行列两套独立副本”、CockroachDB”单一行存引擎”都不同,本系列不做展开,仅作边界标注。


八、与 distributed/39 的分工

《分布式 KV 存储对比:etcd、TiKV 与 FoundationDB》已经把 TiKV、etcd、FoundationDB 三者的共识协议、存储引擎、事务模型、性能特征做过系统对照,本篇不重复那张对照表。本篇新增的是 distributed/39 没覆盖的 CockroachDB,重点讲清楚”表面都是 range/Region + Raft,但角色划分与提交协议差在哪”;完整的多系统选型决策留给第 18 篇统一收束。


九、常见误解

误解一:CockroachDB 和 TiKV 是同一套设计换了个名字。 共识层都基于 Raft、分片模型都是”range + 每片一个 Raft 组”,但 CockroachDB 显式定义了 Leaseholder 这个独立角色、用 Parallel Commits 重新设计了提交路径、把 SQL 引擎直接编译进存储节点——这些都是不同的工程决策,不是同一套实现的改名版本。

误解二:Parallel Commits 让 CockroachDB 的事务比 Percolator 快一个数量级。 没有可信的、同口径的公开实测支撑这个结论。能确认的只是两者用不同机制压缩了跨 key 提交的共识轮数,具体延迟差异依赖硬件、网络拓扑与负载特征,本站不下无证据的性能排名。


十、开放问题

  1. 单进程一体化 vs 分离式部署,长期运维成本谁更低:这是工程哲学层面的争论,缺乏公开的、控制变量的对比研究,通常只能看到各自厂商基于自身案例的说法,本站不采信单方营销结论。
  2. Leaseholder 显式分层是否该被 TiKV 借鉴:CockroachDB 把”谁能读写”和”谁是 Raft leader”做成可能短暂分离的两个概念,这在处理跨地域部署、lease 转移时提供了额外的灵活度;TiKV 是否需要引入等价的显式分层,还是维持当前的隐式实现,取决于生产场景对灵活度与实现复杂度的权衡,目前没有公开的路线图结论。
  3. 默认隔离级别的选择是否该由存储层还是 SQL 层决定:TiKV 把 SI 作为存储层的固定语义,可串行化的保证(如果需要)交给应用层加锁;CockroachDB 把 SSI 做成 SQL 层默认策略,可按需降级到 SI 或 READ COMMITTED。哪种分层更适合大多数业务,缺乏跨系统、控制变量的量化研究,本站不下结论,只标注两者选择了隔离语义栈里不同的层。

十一、小结

三句话小结:

  1. CockroachDB 和 TiKV 都基于 Raft 做 range/Region 级 Multi-Raft 分片,但 CockroachDB 显式定义了可能短暂偏离 Raft leader 的 Leaseholder 角色,TiKV 把等价优化隐藏在 raftstore 内部。
  2. 两者事务模型都源自”给每个 key 打临时标记 + 集中记录最终状态”的思路,但 TiKV 走 Percolator 的 Primary/Secondary 两阶段提交,CockroachDB 走 Parallel Commits 把提交路径压缩到接近一轮共识;默认隔离级别也不同——TiKV 是 Snapshot Isolation,CockroachDB 默认 Serializable(基于 Cahill et al. 2008 的 SSI 算法)。
  3. TiKV 生态是 TiDB/TiKV/PD 三组件分离部署,CockroachDB 是单进程一体化;OceanBase 走的是第三条路,本系列只做边界标注不做深拆。

参考资料

  1. CockroachDB Docs, Architecture Overview / Replication Layer / Transaction Layer(cockroachlabs.com/docs/stable/architecture,A 级)。
  2. Taft R., et al. CockroachDB: The Resilient Geo-Distributed SQL Database. SIGMOD 2020(A 级)。https://doi.org/10.1145/3318464.3386134
  3. CockroachDB Blog / RFC, Parallel Commits(cockroachlabs.com/blog、cockroachdb/cockroach RFC 20180324,A/B 级,机制设计文档)。
  4. Peng D., Dabek F. Large-scale Incremental Processing Using Distributed Transactions and Notifications. OSDI 2010(Percolator,A 级)。
  5. Cahill, M. J., Röhm, U. & Fekete, A. Serializable Isolation for Snapshot Databases. SIGMOD 2008(A 级,SSI 算法,rw-antidependency 环检测)。https://doi.org/10.1145/1376616.1376690
  6. CockroachDB, docs/design.md(cockroachdb/cockroach,A 级,SI/SSI 语义与 Cahill 论文引用出处);CockroachDB Docs, Transactions(A 级,默认 SERIALIZABLE、READ COMMITTED 可选级别)。
  7. TiDB Development Guide, MVCC garbage collection(A 级,TiKV 基于 MVCC 的 Snapshot Isolation 定义)。
  8. CockroachDB Docs, Transaction Layer §Closed Timestamps;CockroachDB Blog, An Epic Read on Follower Reads(A/B 级,Closed Timestamps 与 Follower Reads 机制)。
  9. OceanBase Developer Guide, Architecture(oceanbase.github.io,A 级,一句话边界依据)。
  10. distributed/39 分布式 KV 存储对比(etcd/TiKV/FoundationDB 完整对照,不在本篇重复)。
  11. 本系列 第 3–4 篇 Region 模型与 Multi-Raft第 10–11 篇 Percolator 乐观事务与 ResolveLock第 15 篇 新鲜度与一致性

上一篇新鲜度与一致性

下一篇生产排障:热点、锁、TSO、apply

返回 系列目录

同主题继续阅读

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

2026-07-16 · database / storage

【TiKV / HTAP 内核】PD 元数据与调度:心跳、算子与调度器

拆解 PD 如何靠 store/region 心跳建立集群视图,用 balance-leader/balance-region/hot-region 等调度器算分,生成 transfer-leader、move-peer 等 Operator,再通过下一次心跳的响应把算子'建议'给 Region Leader;对照 Spanner placement driver 与 CockroachDB 去中心化再平衡的架构分歧。


By .