“会话内四项保证不自动给一批分开的读一个共同快照。先读旧权限,再读依赖新权限的内容,即便服务器每次都正确保留会话知识,两个返回值组合仍可能不一致。两轮因果多键读取补的是这一个调用内部的结果合同…”
形式陈述
单键读都正确,组合仍可能错
客户端先读到公开权限,随后内容更新成只允许朋友访问的私密版本。如果它接着单独读到这份内容,就把两个不相容的版本拼在一起。因果一致性可以保证公开私密内容前,集群已具备新权限;它不会自动撤回客户端更早那次旧权限读取。
本页实现COPS-GT的两轮get transaction核心:一次调用指定一组不同键K,最终一起返回一份满足依赖的多键结果。客户端串行使用其上下文,不在调用未完成时由别的线程改写它。单次读取失败时不返回半份成功结果。本文选择单集群、固定键归属、诚实节点和每键单写者连续版本的教学模型;原系统支持更丰富的冲突处理,这里不把它偷偷当作已经完成的实现。[1,§4.6与Figure7]
每个键k有初始版本0,之后为1、2、…。新版本包含完整值和传递依赖dep:dep[j]=v表示它依赖键j至少到v版。每键写者也依赖自己的前一版本,因此同键版本形成链。dep要包括前驱的前驱,按键取所需版本的最大值;未列出的分量视为0。
集群仅在全部依赖已经公开后,才公开新版本。当前版本只前进。一次单键latest返回本次原子读取处的当前版本及其完整dep;get-exact(k,v)返回恰好v的内容和dep。本页使用保存旧版本并按指定版本读取的接口,不能只存一个最新值再声称支持精确次轮。
调用开始时,所选集群已满足客户端既有上下文;跨集群迁移若尚未达到这一条件,应先等待或拒绝。下面解决本次读取自身的跨键一致性,不替迁移层补出丢失的会话历史。消息失败、取消或历史不再可取时,返回RETRY或CANCELLED,而不是默默退回latest。
首轮最大要求,次轮精确版本
设k=|K|。首轮并行向每个请求键发latest,收到f[x],其中v(f[x])为其版本。对每个x∈K计算
如果t[x]=v(f[x]),保留首轮结果。如果更大,就在第二轮请求get-exact(x,t[x]),替换该键结果。第二轮所有请求可并行发出;不重新请求那些已经满足要求的键。所有必需版本齐备后才统一返回,并把结果及其依赖加入客户端上下文。
返回结果g必须满足:对任意请求键y,它的每一个也在K中的依赖键x,都有v(g[x])≥dep(g[y])[x]。K之外的依赖不必作为额外返回项;集群“依赖先公开”的前置保证它们已存在。本页不把有限读取集合写成整库同步或全局最新快照。
历史保留也是接口条件
首轮latest与登记该(tx,key)的保留下界b,必须原子完成,其中b就是此次返回版本。该pin存续时保留键的全部版本v≥b,包括之后产生的版本及其依赖元数据。第二轮只会向前补到t[x]≥b,因此其精确版本不能被正常回收删掉。
事务完成或显式取消后释放所有已取得的pin。取消同时撤销句柄;迟到的精确读不能凭旧句柄重新生效。示例把这些看成单集群存储API的原子状态转换,没有实现跨节点故障恢复或超时租约。参考器业务值为不可变字符串,初始版本0的值为None。如果客户端永久失联且没有额外撤销协议,pin可以永久阻止回收;不能从安全保留推出有界空间。
直觉
把首轮看成一张待补齐的购物清单
首轮可能在不同瞬间拿到不同键,不能直接把它们装订成答案。但每份新内容随附完整依赖,告诉客户端“如果选择我,其他已请求的键最少要到哪一版”。把这些要求合并,就得到第二轮的固定目标。
精确版本是这条清单能一次收尾的原因。第二轮若顺便升级到更晚版本,就可能又带来首轮从未见过的新要求。新的要求再触发更多读取,有限两轮的证明便失效。
例子与边界
权限与内容的三代版本
独立初始化两个键:x1=public,y1=old。请求集K={x,y},执行如下:
| 事件 | 客户端拿到或集群公开的内容 |
|---|---|
| 首轮读取x | 返回x1,登记x的pin下界1 |
| 写权限 | 公开x2=private,依赖x1 |
| 写内容 | 公开y2=personal,依赖x2和y1 |
| 首轮读取y | 返回y2,登记y的pin下界2 |
| 计算目标 | t[x]=2,t[y]=2 |
首轮(x1,y2)遗漏y2需要的x2,因此不能返回。第二轮只要取精确x2,即可返回(private,personal)。版本目标是2和2,不是两个操作发生于同一墙钟刻度。
现在让第二轮发出之前再发生两写:y3=sanitized依赖x2/y2,然后x3=public依赖y3/x2。正确读取仍取x2,保留y2,结果不受这些新写干扰。若把次轮改成latest(x),就得到x3;它要求y≥3,手中的y2已不够。即使x3和y2各自都是某次合法单键读,它们的组合也不满足本调用的合同。
还可以在下一次补y之前写y4并让它依赖x4,如此交替延长追赶。这说明“反复取最新直到一致”没有本页的两轮完成界;不是声称任何这样的重试算法都永远无法结束。
只有最近依赖为何不够
取三个键,依次产生x2→z2→y2,其中y2直接依赖z2,z2依赖x2。首轮在交错时间拿到x1、z1、y2。
如果y2只返回最近依赖z2,客户端目标会是x1/z2/y2;第二轮取回z2时才发现还缺x2,已超过两轮。完整传递dep(y2)同时列出x2和z2,首轮就把目标算成x2/z2/y2,两个精确请求可在第二轮一起完成。[1,§4.3区分nearest与all dependencies]
最近依赖可以在服务器传播时节省检查:z2公开已经证明它的前驱x2公开。但这不保证客户端更早拿到的x1会自己变新。服务器可见性与客户端本次收集结果是两层状态,优化不能直接跨层套用。
一次错误回收就会破坏精确读取
在两键例里,x首轮返回1之后,第二轮需要2。即便当前x已经3,也不能因“2不再最新”就删除2。下界1的活跃pin保护1、2、3;它保护未来可被依赖选中的版本,而不只保护首轮那一份值。
若先读出x1、稍后才登记pin,GC可以在中间看不到保留责任而删除x2;事后补pin无法恢复字节。因此登记与latest原子同出,是安全接口的前提。显式取消后可以回收;届时旧事务只能失败重试,不能用x3顶替缺失x2继续返回。
只有pin也不能弥补版本或依赖被篡改。每个(key,version)必须唯一对应内容和完整依赖;请求键重复、句柄不匹配、未完成首轮便计算目标等不合法调用应拒绝。参考器用显式检查展示这些出口,没有把Python对象共享当作分布式授权机制。
推论与应用
两轮为什么足够
先固定全部首轮结果F。对某个键x,若结果不换,它的依赖显然已经进入目标计算。若换成第二轮版本t[x],这个版本必是某个首轮结果f[y]在x上明确列出的最大依赖;有限集合的最大值由其中一项达到,不是插值得到一个不存在的版本。
因为dep(f[y])包含完整传递依赖,版本(x,t[x])自己的每个前驱要求也已包含在dep(f[y])里。因此次轮取得该版本后不会发现超出既定目标的新要求。对所有请求键同时成立,就有dep(g[y])[x]≤t[x]=v(g[x])。这给出结果内的依赖闭包,而不是只靠实测两轮恰好结束。[1,§4.6]
精确目标在首轮某结果公开之前就已公开于集群;保留pin又保证它仍可读取。在无故障、请求终会被处理的合同下,第二轮不用等待未来写产生。两轮是请求波次数,不是固定毫秒界,也不排除队列、传输或故障造成的等待。
可以把返回版本连同它们全部前驱组成一个对依赖向下闭合的集合,这里只看写事件。每键版本是链,对请求键不可能从闭包引入高于返回版的写,否则刚证得的逐键不等式会被破坏。因此结果可由这一合法因果视图解释。该视图不必等于某个现实瞬间的所有键最新状态,也没有给跨键写添加原子提交。
pin如何与正常回收兼容
对某键,把所有活跃pin下界与当前最新版号一起取最小值f。回收仅删除v<f的已保存版本,保留v≥f。若该键没有pin,f就是当前版本,可只留下当前值。任一事务的目标不小于自己的首轮下界,所以不会被这个规则删掉。
示例的GC直接扫描该键保存的版本和全部活跃pin;若分别有V份版本、P份pin,单次花O(1+V+P)工作,不声称已经实现高效堆或持久回收协议。取消后再次用原句柄读必须先检查句柄有效性,即使恰巧还剩那个历史值也不能恢复已取消会话。
请求数与元数据成本
对k个不同键,首轮k次请求,第二轮至多k次,共不超过2k;空集合直接返回空结果,不创建pin。设首轮完整依赖总条数为D,用键集合与目标散列表,计算要求花期望O(1+k+D)。设全部最终结果的依赖合计D′条、已有上下文U项,结果检查与上下文复制/合并另需期望O(1+k+D′+U),值的传输按字节收费,不能把传递闭包当作零长标签。参考器关闭事务时扫描当前全部P份pin清除本事务条目,另花O(1+P)工作;这个简单存储接口没有声称每次释放只需常数。
精确历史查找用(key,version)字典,定位期望常数,但内容复制、网络和pin登记仍有成本。完整依赖生成需要读取前驱的完整依赖;若新写合并a个前驱(包括同键前一版本)、其依赖合计B条,使用散列表需期望O(1+a+B),另加规范化排序或编码的实际费用。随着因果历史变长,完整依赖和保留版本都可能增长。
本页严格区分一次多键结果、迁移中的会话历史以及更新之间的原子性。分别核两键、三键、取消和保留的终结任务可以验证这些界面,不能把某次一致只读结果当作所有写事务都可串行化。
参考资料
- [1] Wyatt Lloyd、Michael J. Freedman、Michael Kaminsky、David G. Andersen,Don’t Settle for Eventual: Scalable Causal Consistency for Wide-Area Storage with COPS,SOSP2011,pp.401–416;§4.3(PDF6页)完整与最近依赖,§4.6/Figure7(PDF7–8页)两轮最大目标和精确次轮证明,§5.1(PDF8–9页)旧版本与依赖回收。本文使用显式无时限pin替代原实现有时限保留方案,单键单写者和本地原子API是明示教学限制