Spanner:用时钟换外部一致性
Spanner: Buying External Consistency with Clocks
2012 年 OSDI 上,Google 的 James C. Corbett 一队人描述了一套全球数据库:数据按目录切开放进 Paxos 组,事务跨组用两阶段提交,而时间戳不来自逻辑时钟,来自一个叫 TrueTime 的 API。TrueTime 不返回“现在是几点”,它返回一个闭区间 [earliest, latest],意思是:真实时间一定落在这区间里,但系统拒绝假装自己知道得更精确。
Spanner 要买的性质叫外部一致性:若事务 T1 的提交在真实时间上早于 T2 的开始,则 T1 的提交时间戳小于 T2 的。传统看法是,没有同步时钟就做不到这件事,只能退到因果一致或某个会话保证。Spanner 的赌注是:把时钟误差变成显式的区间,再在提交时等待区间右端落到过去。等待换来的不是感觉上的“时间准了”,而是一条可以拿来做快照读的全序。
破除误解
误解
**误解一:TrueTime 是一台超级精确的全球时钟。实情**
它是对不确定性的诚实接口。TT.now() 给出区间,TT.after(t) 只在 t 已经确定过去时为真,TT.before(t) 只在 t 确定尚未到来时为真。区间宽度的一半记作 ε。2012 年论文里的典型 ε 大约是几毫秒量级,依赖 GPS 接收机和原子钟,也依赖机房之间的网络与天线视野。ε 变大时,系统不会撒谎说自己更准,它会让提交等待变长。精确性是用等待买的,不是用一句 API 声明买的。
误解二:有了 GPS,就可以不要 Paxos。
时钟解决的是时间戳怎么打,不是日志怎么复制。每一组 tablet 仍然用 Paxos 把写入做成多数派持久化。TrueTime 不能让一台落后的副本突然变成真相。它只让 leader 在提交时选出一个不会和已经提交的事务颠倒的时间戳,并等到这个时间戳确定进入过去。没有 Paxos,TrueTime 只是一排会漂的钟;没有 TrueTime,Paxos 组仍然能在组内达成一致,但跨组事务的外部一致性要另找办法。
误解三:Spanner 证明了“CAP 不必取舍”。
Spanner 在广域网上提供线性一致的读写,靠的是同步时钟假设加上多数派。网络分区时,少数派那边不能完成需要多数派的提交,可用性仍然按 CAP 的字面意思下降。论文没有推翻不可能结果,它展示的是:若你愿意在机房里装原子钟、在提交路径上等待 ε,并且接受少数派在分区时卡住,那么全球范围内的外部一致性是工程上可买的。买到的是一种特定产品,不是一条新的定理。
TrueTime 在提交路径上做什么
一次读写事务的粗路径是:先在涉及的 Paxos 组上拿锁、做两阶段提交,leader 向 TrueTime 要一个时间戳 s,然后提交等待——一直等到 TT.after(s) 为真,才把提交成功返回给客户端。等待的长度大约是 ε 减去已经过去的时间。ε 若是 4 毫秒,等待就是毫秒级;ε 若因 GPS 失锁涨到几十毫秒,提交延迟会一起涨。系统把时钟故障变成了延迟,而不是变成错误的顺序。
为什么必须等?因为 s 是从区间里选出来的。在等待结束之前,另一台机器上的 TT.now() 仍可能给出一个与 s 重叠的区间。若此时就对外宣布提交成功,后开始的事务可能拿到更小的时间戳,外部一致性当场破裂。等待的语义是:当客户端收到成功时,s 已经确定是过去,后续事务的区间左端一定大于 s。
快照读则沿着另一条路走。只读事务可以选一个过去的时间戳,在各 Paxos 组上读该时间戳之前的数据,不必再拿写锁。外部一致性保证这个快照对应真实时间上的一个切面:凡是在那个时刻已经提交成功的事务,都在切面里;凡是之后才提交的,都不在。这比“读从库、碰运气”要硬,也比在每个只读路径上再跑一遍两阶段提交要便宜。
TrueTime 的物理实现是分层的。每个数据中心有一组 time master,带 GPS 接收机和原子钟;普通机器与 master 同步,并把自己的 ε 设得足够保守,以覆盖本地振荡器漂移和同步消息延迟。论文强调保守:宁可把 ε 报大、让提交多等,也不能把区间报瘦、让顺序说错。这和 NTP 那种“尽量准、错了再漂”的文化相反。Spanner 要的不是平均误差小,而是误差上界可信。
数据怎么切:目录、tablet、Paxos 组
Spanner 把一张表的行按键范围切成 tablet,tablet 再被目录(directory)这种配置单位捆在一起做迁移。复制因子、放置位置、甚至是否跨洲,都挂在目录上,而不是挂在每一行上。一个 Paxos 组负责一组 tablet 的日志。跨组事务走两阶段提交,协调者本身也落在某个 Paxos 组里,避免协调者成为不可恢复的单点。
这种切分让“全球”和“本地”可以同时存在。广告投放这类数据可以放进跨洲复制的目录,低延迟的用户状态可以放进单区域目录。应用看见的是同一套 SQL 风格的模式和事务接口,看不见每一次提交经过了几个机房。看不见不等于没付钱:跨洲两阶段提交的延迟以一次洲际往返为下界,TrueTime 的等待只是其中一项,通常不是最大的那一项。
模式本身经历过一次公开的演化。2012 年论文里的 Spanner 更像带时间戳的半关系存储;2017 年 SIGMOD 上 David F. Bacon 等人写了 Spanner: Becoming a SQL System,描述查询编译、DML 和更完整的 SQL 表面。F1(Google 广告后端)是这条演化的重要用户:它需要关系模型和跨洲事务,也愿意为提交延迟付钱。后来的 Cloud Spanner 把同一套思路做成了托管产品,TrueTime 的硬件假设随之变成了云厂商的机房标准,而不是客户自己去装原子钟。
和 Dynamo 对照,能看清同一家公司两条产品线的哲学分裂。Dynamo 默认永远可写,冲突事后合并;Spanner 默认提交时就排好全序,冲突在锁和等待里变成延迟或中止。没有哪一条是“更正确”的数据库。购物车要的是写入不失败,账本要的是提交顺序可对外解释。TrueTime 只对后一种目标有用。
ε 的政治:时钟误差是一等故障
把不确定性写成区间,等于把一种通常藏在运维手册里的故障抬进了 API。天线被雪盖住、机房改了网线、原子钟进入保持模式,这些事件的第一症状不是“数据错了”,而是 ε 变宽、提交变慢。监控必须盯区间宽度,而不是只盯是否还能读到时间。这是一种很少见的诚实:系统把“我不太确定现在几点”当成正常返回值,而不是当成内部尴尬。
它也划出了别人难以复制的边界。你可以实现 Paxos 和两阶段提交,但若没有机房级的 GPS 与原子钟部署,ε 会大到让提交等待变得难看,外部一致性的产品承诺就会缩回单区域。后续有工作用混合逻辑时钟或在软件里跟踪因果,试图在没有专用时钟硬件时逼近类似保证,通常要以更复杂的依赖跟踪或更窄的部署范围作为交换。Spanner 选择把复杂性放进机房物理层,让数据库 API 保持相对简单。这是一次基础设施特权,不是一条可以随手抄进开源项目的算法。
外部一致性还有一个容易被忽略的客户端含义。应用若在提交成功之后立刻打开一个新事务去读,它应当看见刚才那次写——因为等待已经把时间戳推入过去。应用若绕过数据库、用自己的墙钟去和 Spanner 的时间戳比较,就踏出了定理的范围。TrueTime 的保证是给事务接口的,不是给机房里每一台服务器的 clock_gettime 的。
时间戳、锁与模式演化
读写事务在涉及的行上拿锁,时间戳在提交时才最终选定。这个顺序很重要:先锁后戳,避免两个并发事务各自选好戳再发现锁冲突、只好中止。只读快照则可以不拿写锁,只要所选时间戳已经安全——对应的 Paxos 组已经把该戳之前的日志提交。leader 若不确定自己仍是 leader,不能凭内存里的数据回答快照读,这和 etcd 一类系统必须先确认领导权是同一类谨慎。TrueTime 没有取消共识,它只是给已经提交的日志贴上一张可全局比较的标签。
模式变更在全球部署里特别疼。加一列、加一个二级索引,不能假装成一次本地 DDL。Bacon 等人 2017 年的论文把 Spanner 描述成真正的 SQL 系统:查询计划、DML、模式版本必须和事务时间戳生活在同一套全序里。一次模式变更本身往往是一串提交,后到的事务要看见新模式,先到的事务必须仍能在旧模式上完成。没有外部一致的时间轴,这种“模式也是数据”的演化会退化成每个副本各解释各的 CREATE TABLE。F1 愿意为广告账本付跨洲延迟,部分原因就是它需要模式和数值更新落在同一条可解释的历史上。
把 Spanner 缩回单区域,TrueTime 的等待几乎小到可以忽略,Paxos 和两阶段提交仍然在。许多人因此以为“时钟是主菜”。更准确的说法是:时钟是把多区域的多份 Paxos 日志粘成一条外部可见全序的胶水。单区域线性一致可以用租约和逻辑时钟近似;跨洲还要对外宣称“先提交的一定先发生”,才轮到 GPS 和原子钟上场。胶水有价格,价格写在 ε 和洲际往返上。不需要这条胶水的应用,不必为它付机房税。
提交等待如何变成用户能感觉到的延迟
一次读写事务的可见延迟,通常叠了三截:锁等待、Paxos 多数派往返、以及 commit wait。前两截任何共识数据库都有。第三截是 TrueTime 的税,长度跟 ε 走。论文说 Paxos 的网络往返常常能盖住一部分等待,意思是:领导者在复制日志的同时,时间区间也可能已经闭合。短事务、同区域副本时,用户不一定感觉到“额外睡了一次 ε”;跨洋、ε 被天线事故抬高时,这一截会单独冒出来。
只读快照可以躲开锁,但仍要选一个已经安全的时间戳。选得太新,可能看见尚未过完等待的提交;选得太旧,读到的是过期快照。租约让领导者在一段时间内不必每次问多数派,于是只读可以更快,前提是领导者没有在租约期里偷偷死掉。时钟与租约绑在一起:TrueTime 不仅给事务编号,也给“我仍是领导”这句话提供时间上界。
F1 把广告业务的模式、二级索引和 SQL 放在这台机器上。交错表把子行贴在父行旁边,为的是少跨 Paxos 组。schema 设计于是重新变成物理设计:你怎么声明层次和位置,决定你付多少次跨组两阶段提交。TrueTime 不能把新泽西和新加坡变成同一机房,它只能保证两地提交的先后可以被写成时间戳的先后。
两阶段提交仍然是可用性裂缝。钟再准,协调者挂了照样要走恢复协议。跨组事务把多个 Paxos 组绑成一次原子提交,任何一组的多数派不可用,这次提交就不能前进。外部一致性不负责让跨洋多行事务变得便宜,它只负责:一旦提交成功并对客户端可见,其时间戳在真实时间里排得住。位置约束因此先于时钟:数据放哪一洲,决定光速下界;时钟只决定你还要额外等多少不确定度。把所有热点目录放进同一领导者,外部一致性会退化成一台机器上的串行锁,只是编号看起来像全球时间。
客户端应如何理解一次成功提交
对应用来说,提交成功意味着两件事同时成立:多数派已经 durably 记下这次事务,并且协调者已经等到时间戳落入过去。因此紧接着的一次新事务读,应当看见刚才的写。若应用把提交成功理解成“我本地的墙钟已经超过那个时间戳”,就踏出了接口。机器之间的墙钟没有 TrueTime 的区间语义,拿去和事务时间戳比大小,定理不负责。
备份、离线分析和模式迁移,都在消费同一套时间戳秩序。选一个已经闭合的时间戳做快照,扫描可以无锁地穿过各分片。这是外部一致性的附赠:全球一份可命名的“那一刻”。附赠不是免费的。选得太新,快照可能还在等待;选得太旧,分析看到的是过期世界。仓库作业必须把 ε 的当前分布写进作业参数,而不能假设一个常数。
把时钟误差当成一等故障,还改变了值班手册的写法。过去数据库值班盯的是副本是否落后、领导者是否切换、磁盘是否写满。现在多了一条:区间宽度有没有在几个采样周期里单调变宽。变宽不一定立刻停写,但提交等待会先把尾延迟抬起来,产品会先于正确性报警。把天线维护写进数据库手册,听起来越界,却是这套设计的诚实之处:物理层进入了事务路径,值班就不能再假装那是别人的钟。
跨域连接
- 测度论:TrueTime 把“现在”从一点改成一个可测的区间,提交等待相当于等到该区间的测度全部落在过去。外部一致性于是不是对某个未知真值点的猜测,而是对区间覆盖关系的推理:若两个区间已经分离,顺序就可以确定;若仍相交,系统拒绝宣布顺序。ε 是这个测度的半径。把它报小,并不是提高了测量精度,而是缩小了你愿意承认的不确定集,一旦真实误差落在集合外面,定理的前提就碎了。
- 证明:Corbett 等人给出的外部一致性论证,前提全部写在 TrueTime 的区间语义和提交等待上。机器不会替你检查机房里的原子钟是否真的满足那个上界,正如 seL4 不会替你检查规格有没有漏写 DMA。证明在这里的形态是“相对假设的推导”:假设成立,时间戳全序与真实时间一致;假设被 GPS 失锁打穿,推导不再对现实负责。把论文里的引理读成无条件的产品保证,就是把假设从句子里删掉了。
- 概率论:ε 看起来像一个常数,其实是对振荡器噪声、网络延迟和卫星几何的保守分位数。报得太紧,覆盖真实误差的概率下降,偶发的顺序颠倒会以小概率出现;报得太松,每次提交都为极小概率事件付等待税。Spanner 选择偏向后者。这是假设检验里控制第一类错误的老问题:宁可多等,也不让“顺序说错”这种不可接受的错误溜过阈值。
- 理性预期:提交等待是一种让所有后继参与者形成一致预期的装置。当客户端收到成功,任何新事务都应当预期自己会排在这次提交之后,不必再去打听隐秘的因果边。 Dynamo 那种最终一致强迫应用预期“读可能看见旧值”;Spanner 强迫基础设施预期“时钟误差有上界”,从而让应用可以预期“成功意味着已经进入全序”。两种系统都在管理预期,只是把理性负担放在了不同的角色身上。
参考文献
- Corbett, J. C. et al. Spanner: Google's Globally-Distributed Database. USENIX OSDI, 2012.(亦见 ACM Transactions on Computer Systems 31(3), 2013)
- Bacon, D. F. et al. Spanner: Becoming a SQL System. ACM SIGMOD, 2017.
- Shute, J. et al. F1: A Distributed SQL Database That Scales. PVLDB 6(11), 2013.
- Lamport, L. The Part-Time Parliament. ACM Transactions on Computer Systems 16(2), 1998.(Paxos)
- Gray, J. Notes on Data Base Operating Systems. In Operating Systems: An Advanced Course, Springer, 1978.(两阶段提交)
延伸阅读
- Google Cloud. TrueTime and External Consistency 技术说明(Cloud Spanner 文档,持续更新).
- Lloyd, W. et al. Don't Settle for Eventual: Scalable Causal Consistency for Wide-Area Storage with COPS. ACM SOSP, 2011.(不用全球时钟时,因果一致能买到什么)
- Demirbas, M. 等关于混合逻辑时钟的后续讨论,用来对照“没有原子钟时还能保留多少顺序”。