前 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 官方文档明确区分了两个概念:
- Raft leader:按 Raft 协议选出、负责日志复制的角色;
- Leaseholder(租约持有者):持有该 Range 的”range lease”,是唯一能对外提供一致性读、并把写请求转成 Raft 命令的角色。
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 通信。
这个差异带来的实际后果:
- 运维单元不同:TiDB 集群需要分别规划 TiDB/TiKV/PD 三类节点的数量与资源;CockroachDB 只需要一种节点类型,扩容即加节点;
- 计算与存储的耦合度不同:TiDB 的 SQL 计算与 TiKV 存储物理分离,SQL 层可以独立水平扩展,不占用存储节点资源;CockroachDB 的 SQL 执行发生在接收请求的节点上,与该节点是否持有相关 Range 副本无关,但计算资源和存储资源仍共享同一批进程;
- HTAP 路径不同:TiDB 靠 TiFlash 这个独立列存 Learner 副本承接分析负载(第 14 篇);CockroachDB 的主线定位是分布式 OLTP,没有与 TiFlash 对等的官方列存副本方案,分析型负载通常依赖外部 ETL 或第三方列存系统。
四、时钟模型:集中式 TSO vs 混合逻辑时钟
事务提交顺序需要一个全局可比较的时间戳,这一点两个系统都认,但拿时间戳的方式不同:
- TiKV:所有
start_ts/commit_ts都由 PD 的 TSO 服务集中分配(第 9 篇),保证任意两个时间戳可以直接比较大小,代价是每次事务开始和提交都要一次到 PD 的网络往返; - CockroachDB:用 HLC(Hybrid
Logical
Clock)——每个节点的本地物理时钟加一个逻辑计数器,节点间通过请求/响应携带的时间戳互相”对表”、只在必要时把本地时钟推进到不落后于对方——不需要一个中心化的服务来分配时间戳,但代价是
HLC
的时钟偏移量必须被限制在一个已知的最大误差(
max-offset,默认通常在数百毫秒量级)之内,否则跨节点的时间戳比较会失去意义。
这个差异是”集中式授时换取严格顺序、但引入单点往返”与”去中心化换取更低延迟、但需要限定并持续校验时钟偏移”之间的经典权衡,与
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 提交的共识轮数,具体延迟差异依赖硬件、网络拓扑与负载特征,本站不下无证据的性能排名。
十、开放问题
- 单进程一体化 vs 分离式部署,长期运维成本谁更低:这是工程哲学层面的争论,缺乏公开的、控制变量的对比研究,通常只能看到各自厂商基于自身案例的说法,本站不采信单方营销结论。
- Leaseholder 显式分层是否该被 TiKV 借鉴:CockroachDB 把”谁能读写”和”谁是 Raft leader”做成可能短暂分离的两个概念,这在处理跨地域部署、lease 转移时提供了额外的灵活度;TiKV 是否需要引入等价的显式分层,还是维持当前的隐式实现,取决于生产场景对灵活度与实现复杂度的权衡,目前没有公开的路线图结论。
- 默认隔离级别的选择是否该由存储层还是 SQL 层决定:TiKV 把 SI 作为存储层的固定语义,可串行化的保证(如果需要)交给应用层加锁;CockroachDB 把 SSI 做成 SQL 层默认策略,可按需降级到 SI 或 READ COMMITTED。哪种分层更适合大多数业务,缺乏跨系统、控制变量的量化研究,本站不下结论,只标注两者选择了隔离语义栈里不同的层。
十一、小结
三句话小结:
- CockroachDB 和 TiKV 都基于 Raft 做 range/Region 级 Multi-Raft 分片,但 CockroachDB 显式定义了可能短暂偏离 Raft leader 的 Leaseholder 角色,TiKV 把等价优化隐藏在 raftstore 内部。
- 两者事务模型都源自”给每个 key 打临时标记 + 集中记录最终状态”的思路,但 TiKV 走 Percolator 的 Primary/Secondary 两阶段提交,CockroachDB 走 Parallel Commits 把提交路径压缩到接近一轮共识;默认隔离级别也不同——TiKV 是 Snapshot Isolation,CockroachDB 默认 Serializable(基于 Cahill et al. 2008 的 SSI 算法)。
- TiKV 生态是 TiDB/TiKV/PD 三组件分离部署,CockroachDB 是单进程一体化;OceanBase 走的是第三条路,本系列只做边界标注不做深拆。
参考资料
- CockroachDB Docs, Architecture Overview / Replication Layer / Transaction Layer(cockroachlabs.com/docs/stable/architecture,A 级)。
- Taft R., et al. CockroachDB: The Resilient Geo-Distributed SQL Database. SIGMOD 2020(A 级)。https://doi.org/10.1145/3318464.3386134
- CockroachDB Blog / RFC, Parallel Commits(cockroachlabs.com/blog、cockroachdb/cockroach RFC 20180324,A/B 级,机制设计文档)。
- Peng D., Dabek F. Large-scale Incremental Processing Using Distributed Transactions and Notifications. OSDI 2010(Percolator,A 级)。
- 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
- CockroachDB, docs/design.md(cockroachdb/cockroach,A 级,SI/SSI 语义与 Cahill 论文引用出处);CockroachDB Docs, Transactions(A 级,默认 SERIALIZABLE、READ COMMITTED 可选级别)。
- TiDB Development Guide, MVCC garbage collection(A 级,TiKV 基于 MVCC 的 Snapshot Isolation 定义)。
- CockroachDB Docs, Transaction Layer §Closed Timestamps;CockroachDB Blog, An Epic Read on Follower Reads(A/B 级,Closed Timestamps 与 Follower Reads 机制)。
- OceanBase Developer Guide, Architecture(oceanbase.github.io,A 级,一句话边界依据)。
- distributed/39 分布式 KV 存储对比(etcd/TiKV/FoundationDB 完整对照,不在本篇重复)。
- 本系列 第 3–4 篇 Region 模型与 Multi-Raft、第 10–11 篇 Percolator 乐观事务与 ResolveLock、第 15 篇 新鲜度与一致性。
上一篇:新鲜度与一致性
返回 系列目录
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【TiKV / HTAP 内核】Multi-Raft:一 Region 一 Raft 组
拆解 TiKV 的 Multi-Raft 架构:为什么每个 Region 是独立的 Raft 组,与 etcd 单 Raft 组对照差在哪;跨 Region 事务代价作为开放问题收束,不重写 Raft 协议本身。
【TiKV / HTAP 内核】PD 元数据与调度:心跳、算子与调度器
拆解 PD 如何靠 store/region 心跳建立集群视图,用 balance-leader/balance-region/hot-region 等调度器算分,生成 transfer-leader、move-peer 等 Operator,再通过下一次心跳的响应把算子'建议'给 Region Leader;对照 Spanner placement driver 与 CockroachDB 去中心化再平衡的架构分歧。
【TiKV / HTAP 内核】HTAP / 分布式 KV 全景:TiDB–TiKV–PD–TiFlash 四件套
定位 TiKV 7.x/8.x 在 RocksDB 单机 LSM、Raft 协议与 HTAP 范式之间补的那一层:TiDB、TiKV、PD、TiFlash 四组件各自的抽象边界,以及本系列 18 篇的读法。
【TiKV / HTAP 内核】Key 编码与 MVCC:三 CF 与时间戳
拆解 TiKV 的 user key + start_ts/commit_ts 编码规则与 default/lock/write 三 CF 分工,并说明为什么这套 MVCC 与 RocksDB Snapshot 的 sequence 号是两层完全不同的机制。