Spanner:Globally-Distributed Database(论文笔记)
发布于:
论文:Spanner: Google’s Globally-Distributed Database(Corbett et al., OSDI 2012)
Spanner 想解决一个“看起来不可能”的组合:
- 跨地域(全球)部署
- 可扩展(按 key-range 分片)
- 高可用(复制)
- 强一致语义(论文强调 external consistency,接近线性一致的事务提交顺序)
本文论文的最大价值是把“时间、不确定性、一致性、延迟”之间的关系讲清楚:Spanner 用 TrueTime 把物理时钟的不确定性显式化,配合 commit-wait 把全局提交顺序工程化落地。
1. Spanner 要的语义:External Consistency
1.1 什么是 external consistency
简化理解:
- 如果事务 T1 在真实世界里“先提交完成”,那么任何客户端都不应该看到 T2 的效果早于 T1。
这比单纯的 serializability 更“贴近真实时间”——对跨地域业务很重要(比如金融、跨区配置变更等)。
1.2 为什么这很难
跨地域意味着:
- 网络 RTT 大
- 复制协议本身要多数派
- 时钟无法完美同步
如果没有“全球可比较的时间基准”,事务提交顺序就只能依赖消息顺序与协议状态,语义会变弱或成本更高。
2. 核心武器:TrueTime
2.1 TT.now() 返回的是区间
TrueTime 把时间建模成区间而不是点:
TT.now() -> [earliest, latest]
不确定性 ε = latest - earliest 由时钟同步体系(GPS + 原子钟 + 软件校准)控制。
flowchart TD
TT[TrueTime] --> N[TT.now()]
N --> E[earliest]
N --> L[latest]
E --> EPS["epsilon equals latest minus earliest"]
EPS --> C1[clock sync quality]
EPS --> C2[hardware + software]
EPS --> C3[datacenter topology]
2.2 commit-wait:把不确定性“等过去”
外部一致性需要:提交时间戳 s 必须在真实时间上“站得住”。Spanner 的关键动作是 commit-wait:
- 事务决定提交时间戳
s - 等待直到
TT.after(s)为真(也就是TT.now().earliest > s)
这样可以保证:当事务返回“提交成功”时,真实时间已经超过 s,从而建立全局可比较的顺序。
sequenceDiagram
participant Txn as Transaction
participant TT as TrueTime
Txn->>Txn: choose commit timestamp s
loop commit-wait
Txn->>TT: TT.now()
TT-->>Txn: [earliest, latest]
end
Note over Txn: wait until earliest > s
Txn-->>Txn: return commit success
3. 数据组织:key-range 分片 + 复制组
Spanner 的数据单位通常可理解为:
- directory / key-range(按 key 前缀/区间组织)
- 每个 range 由一个复制组(Paxos group)管理
flowchart TD
K[Key space] --> R1[Range 1]
K --> R2[Range 2]
K --> R3[Range 3]
R1 --> G1[Paxos group #1]
R2 --> G2[Paxos group #2]
R3 --> G3[Paxos group #3]
G1 --> DC1[replicas across DCs]
G2 --> DC1
G3 --> DC1
4. 事务:跨 range 的 2PC + 每个 range 内 Paxos
4.1 两层协议的组合
- range 内:用 Paxos(或等价多数派复制)决定写入顺序与 durability
- 跨 range:用 2PC 协调多个参与者(participant)
sequenceDiagram
participant C as Client
participant CO as Coordinator
participant P1 as Participant (range A)
participant P2 as Participant (range B)
C->>CO: Commit txn
CO->>P1: Prepare
CO->>P2: Prepare
P1-->>CO: Prepared (timestamp)
P2-->>CO: Prepared (timestamp)
CO->>CO: choose commit ts s (with TT)
CO->>P1: Commit(s)
CO->>P2: Commit(s)
P1-->>CO: Ack
P2-->>CO: Ack
CO-->>C: Success (after commit-wait)
4.2 TrueTime 在事务里扮演什么角色
它让系统能给事务一个“全球可比较”的提交时间戳,从而:
- 提供 external consistency
- 让只读事务在某些模式下可以“按时间戳读”而避免锁冲突(论文里描述了多种读模式)
5. 读:强一致读 vs 时间戳读
Spanner 支持按不同一致性需求选择读策略:
- 强一致读:读最新 committed(成本更高)
- 时间戳读:读某个时间戳
t的快照(可用来降低锁竞争/提高可用性)
工程要点:
- 越接近“读最新”,越需要更强的同步/等待
- 越愿意读“稍旧的快照”,就越能换到更低延迟、更高吞吐
6. 延迟成本:外部一致性不是免费的
6.1 成本来源
- 多地域复制:Paxos 多数派 RTT
- 跨 range:2PC 两轮
- TrueTime:commit-wait 引入额外等待(与 ε 相关)
flowchart TD
L[Commit latency] --> P[Paxos quorum RTT]
L --> T[2PC overhead]
L --> W["commit-wait proportional to epsilon"]
W --> EPS[TrueTime uncertainty]
6.2 什么时候值得
- 对“全球顺序”和“业务可解释性”有硬需求
- 对跨地域一致的配置/元数据/金融类业务
如果业务可以接受最终一致或弱一些的读写语义,可能更适合 Dynamo-style 或多主异步复制。
7. 读完后的 takeaways
- Spanner 的关键不是“它有分布式事务”,而是 TrueTime + commit-wait 把外部一致性工程化。
- 强一致的成本主要体现在:多地域复制 + 2PC + 时间不确定性等待。
- 本文论文最值得学的是:如何把“理论语义”转成“可运维的系统机制”(显式建模不确定性)。
8. 更细看 TrueTime:epsilon 从哪来,怎么影响延迟
8.1 epsilon 的来源
ε 不是一个“理论常数”,它来自一整套工程体系:
- 时钟源(GPS/原子钟)
- 机房内分发与校准
- 软件层的误差估计与上报
Spanner 不要求“时钟完美同步”,但要求系统能给出一个可信的误差上界。这就是 TrueTime 的关键:
- 不知道现在是几点(精确点)
- 但你知道“现在一定在这个区间里”
8.2 commit-wait 的延迟上界
commit-wait 至少要等待到 TT.now().earliest > s。
要点:
ε越大,commit-wait 的额外等待越大- 在跨地域写入本就很慢的情况下,commit-wait 可能不是主导项;但在同城/低 RTT 环境下,它可能更显著
flowchart TD
A[commit latency] --> B[paxos RTT]
A --> C[2PC rounds]
A --> D[commit-wait]
D --> E[epsilon]
E --> F[clock sync quality]
9. Paxos group(复制组)在系统里的位置
Spanner 的复制通常可以理解为:每个 range 是一个 Paxos group。
- group leader 负责排序与提交
- 多数派 ack 后写入生效
因此即便不考虑事务,单 range 的写入也至少需要“多数派 RTT”。
10. 事务提交:2PC + Paxos 的组合成本
10.1 为什么需要 2PC
跨 range 的事务需要协调多个 participant,否则会出现:
- A range 写成功、B range 写失败
- 系统进入不一致状态
2PC 用 coordinator 做全局决策:
- prepare 阶段让所有 participant 进入可提交状态
- commit/abort 阶段统一做决定
10.2 与复制组的叠加
每个 participant 的 prepare/commit 本身又要在其 Paxos group 内达成一致。
所以“一个跨 range 写事务”的关键路径通常是:
- 多个 group 的 prepare(多数派)
- coordinator 选择时间戳 + commit-wait
- 多个 group 的 commit(多数派)
11. 只读事务:为什么时间戳读很强大
只读事务如果能在一个时间戳 t 上读取一致快照,就可以:
- 避免加锁干扰写事务
- 获得更稳定的读延迟
TrueTime 帮你把“时间戳 t”变成跨地域可比较的概念,从而让快照读语义更自然。
12. 目录(directory)与数据放置:让 locality 可控
论文里强调了 directory 的概念(可理解为 key-space 的层级组织),目的是:
- 把“经常一起访问的数据”放到更近的位置
- 让数据放置策略(placement)可表达
这是一种工程现实:强一致全局数据库不只是协议,还要面对 locality 与成本。
13. 外部一致性:再用一个“因果视角”总结
commit-wait 的本质是:
- 当系统告诉你“事务提交成功”时,它确保真实时间已经跨过该事务的提交时间戳
这样用户观察到的提交顺序就能和真实世界的时间顺序一致(在误差上界 ε 的定义下)。
14. 读完后的 takeaways
- Spanner 的工程核心是:显式建模时间不确定性,并把它纳入协议(commit-wait)。
- 真正的成本来自:复制 + 2PC + commit-wait 的叠加;理解延迟拆分有助于判断是否值得。
- directory/placement 体现了“全球数据库最终还是要服从 locality”:系统不可能违背物理。
15. 并发控制:锁、两阶段锁与时间戳的关系
Spanner 论文的主角是 TrueTime,但“事务能跑起来”还需要并发控制。
一个工程要点:
- 写事务通常需要锁(至少是对写集合的保护)
- 只读事务可以在某些模式下用“时间戳快照读”减少锁冲突
flowchart TD
T[Transaction] --> W[Read-write txn]
T --> R[Read-only txn]
W --> L[Locks]
W --> C[2PC + Paxos]
R --> TS[Timestamp read]
TS --> TT[TrueTime]
15.1 为什么只靠时间戳不够
如果没有锁,两个写事务可能并发写同一 key-range:
- 即使你能分配时间戳,也需要在冲突时决定谁赢谁输
因此写事务仍需要某种互斥/验证机制(锁或等价的 OCC/验证)。
15.2 两阶段锁(2PL)的直觉位置
2PL 常见要点:
- 阶段 1:获取锁
- 阶段 2:释放锁(提交/回滚时释放)
在分布式环境里,锁本身也是协调开销,所以 Spanner 更希望让只读事务减少锁参与。
16. 读模式:强一致读 vs stale read vs bounded staleness
Spanner 提供多种读模式,本质是在做“语义 vs 延迟/吞吐”的交换:
- 强一致读:读最新 committed(成本更高)
- 时间戳读:读某个
t的快照(可以更快、更可并行) - bounded staleness:允许读稍旧的数据,换低延迟
flowchart TD
R[Read] --> S[Strong read]
R --> TS[Timestamp read]
R --> BS[Bounded staleness]
S --> L1[Higher coordination]
TS --> L2[Lower contention]
BS --> L3[Lower tail latency]
16.1 为什么 bounded staleness 重要
在全球部署下,很多读并不需要“最新”,而需要:
- 延迟稳定
- 语义可解释(最多旧 N 秒或 N 个版本)
这种模式能显著降低跨地域的同步等待。
17. directory/placement:全球系统最终还是要服从 locality
17.1 directory 的工程意义
Spanner 的 directory 可以理解为:
- key-space 的一个“可移动单元”(通常是一段范围或前缀)
- 系统可以把 directory 作为放置与迁移的对象
动机:
- 把经常一起访问的数据放近(减少跨地域事务)
- 把热数据拆分/迁移(负载均衡)
flowchart TD
D[Directory] --> K[Key range]
D --> P[Placement policy]
D --> M[Move/rebalance]
P --> DC[Choose replica locations]
M --> L[Improve locality]
17.2 为什么“全球一致”仍需要 placement
协议解决的是“如何一致”,placement 决定的是“付出多少延迟”。
- 如果跨洲事务很多,2PC + Paxos RTT 会直接把延迟拉爆
- 把相关数据 co-locate 是降低成本的第一手段
18. 故障与恢复:复制组层面的可用性
18.1 Paxos group 的故障边界
对于单个 range(一个复制组):
- 少数副本故障:多数派仍可写
- leader 故障:选举新 leader,继续服务
flowchart TD
G[Paxos group] --> Q[Quorum majority]
Q --> OK[Still available]
F[Leader failure] --> E[Elect new leader]
E --> OK
18.2 跨 group 事务的恢复更复杂
跨 group 的事务还牵涉 2PC:
- coordinator 失败怎么办
- participant prepared 但没收到 commit 怎么办
2PC 的经典问题是:coordinator 失败可能导致阻塞,需要额外机制(日志、超时、恢复协议)把状态推进。
19. 端到端延迟拆分:一个工程视角的“可解释模型”
把一次跨地域写事务的延迟粗分为:
L_paxos: 每个 participant 在其 Paxos group 内达成多数派的 RTTL_2pc: 2PC 的往返次数(prepare/commit)L_wait: commit-wait(与 ε 相关)
flowchart TD
L[Total commit latency] --> P[L_paxos]
L --> T[L_2pc]
L --> W[L_wait]
W --> EPS[epsilon]
P --> RTT[network RTT]
19.1 怎么用这个模型判断“值不值得”
- 如果你的业务对 external consistency 没硬需求,
L_wait与跨地域L_paxos/L_2pc的成本可能不划算 - 如果你的业务需要“全球顺序可解释”,Spanner 的这套机制能显著降低应用层复杂度
20. 读完后的最终 takeaways
- Spanner 的核心贡献是:把“时间不确定性”显式建模,并将其纳入一致性协议(commit-wait)。
- 强一致的实际成本来自复制与 2PC,placement/locality 决定了你付出的 RTT。
- 读模式(timestamp / bounded staleness)是工程落地的关键:它让系统在语义与性能之间有可控的旋钮。