date: 2019-10-18
tags: OS “distributed” 6.824
今天来看operational transformation, google doc背后的技术。
这篇文章不是提出operatinal transformation的那篇,不过通讯是提出论文的一作,插一句别的话,这个通讯Clarence (Skip) Ellis是美国第一个非裔博士,真是厉害了。因为这篇文章是比较泛泛的综述,所以我还引用到了其中一些方法具体实现的论文内容。
Group editor的一个难点就是如何在有限的反应时间下维持consistency。operational transformation就是为了达成这一效果的一个技术,其由GROVE (GRoup Outline Viewing Editor)系统在1989年首先提出。之后不同的研究组织都独立的对operational transformation进行了延展。本文会对operationial transformation的进化进行一个整合性的综述,以识别这其中的主要问题,算法,成就与剩余的挑战。除此之外,本文还提出了一个新的generic operational transformation control algorithm。
这部分做一些基本的定义。
定义: casual ordering relation ""
Given and at site and , then
, iff one of the following:
定义: dependent and independent operations
Given and
举个例子:
上图的黑点是generation的时刻。这里假设operation在local site被立即执行,之后传播到remote sites并在抵达时执行。上图的箭头就是传播路径。比如在site 1, 4个operation的执行顺序是, , , 。
根据定义1,有,,。同时有,,.
为了得到好的responsiveness并防止single-point of failure,GROVE提出了一个replicated architecture:
共享的文档在每个site的本地有一份备份。一个operation会在generation之后马上在本地备份上执行,之后传播到remote sites以执行。
假设remote operations以他们的original form被执行,那么会发生2种inconsistency,divergence和causality-violation。
例如上图中,因为不同operation在不同site的到达时间不同,而这些operator是不能相互交换的,所以就会在不同site得到不同的结果,这就称为divergence。比如说上图中site 0和site 1的和顺序不同。
因为上述的genertates and broadcasts的流程不存在synchronization,operation可能会以和他们的natural causal order不同的顺序到达。如上图中, 但是在site 2中先到达,而其可能会使用还没定义的,或者出现先果后因。这种情况称之为causality-violation。如果需要用户间的synchronized interaction,就需要避免这一问题。
通过上述问题,GROVE提出了以下的2点性质:
GROVE根据上述的性质提出了distributed Operational Transformation (dOPT)算法。GROVE的解决方案包含两个部分:
为了保证convergence,dOPT算法要求transformation funciton 满足:
For any and , for which , suppose and , it must be that
除了上述条件,GROVE还发现,在一些条件下,transformation function需要满足non-serializable. 比如,和是两个相互独立的delete operations,其删除的是同一位置,那么必须满足不管这两个operation是什么执行顺序,只能删掉1个字符。不过这个non-serializable的特性并不包含在上述的性质中。
这里我们举引用文献[3] (Concurrency Control in Groupware Systems)中的transformation function作为一个例子:
// o_i = insert[X_i; P_i] (在P_i位置插入X_i)
// o_j = insert[X_j; P_j]
T11(o_i, o_j, p_i, p_j) { // p_i, p_j分别是两个操作的优先级
if (P_i < P_j) // 先做插入后面的,不用变
return insert[X_i; P_i]
else if 9P_i > P_j)
return insert[X_i; P_i+1]
else {
if (X_i = X_j) // 注意,覆盖了
return null
else {
if (p_i < p_j) // 这里和原文相反,修改是为了和下文的例子相应
return insert[X_i; P_i+1]
else
return insert[X_i, P_i]
}
}
}
// delete[P] (删除P位置的字符)
T22(delete[P_i], delete[P_j], p_i, p_j) {
if (P_i < P_j)
return delete[P_i]
else if (P_i > P_j)
return delete[P_i-1]
else
return null
}
T12(insert[X_i, P_i], delete[P_j]) {
if (P_i < P_j)
return insert[X_i, P_i]
else
return insert[X_i, P_i-1]
}
T21(delete[P_j], insert[X_i, P_i]) {
if (P_i < P_j)
return insert[X_i, P_i]
else
return insert[X_i, P_i+1]
}
对于不同应用,transformation operation是不同的。dOPT是generic的。其主要负责选择需要transform的operation,并决定transformation order。dOPT的基本结构很简单:给一个causally ready operation , dOPT扫描Log来transform O against any operation in the Log which is independent of O;之后被转化的,被称为(execution form of )会被执行并记录在Log中。
dOPT(O, log) {
EO = O;
for (i = 1; i <= n; i++) {
if (Log[i] || O) // here || means independent
then EO = T(EO, Log[i]);
}
Execute EO;
Appennd EO at the end of the Log;
}
在一个情况下,dOPT可能不会收敛。如下图:
假设GROVE transformation function使用如下的priority rule:
当两个插入操作有相同的position parameter,the position of the operation with a lower priority (i.e. smaller site identifier) will be shifited。
根据generic dOPT算法和上面提到的transformation function,对于上图我们有:
首先,,所以,,对于3个site,其最终结果如下
insert[y, 2]
,第二次变为insert[y, 3]
,所以最终是xzyinsert[z, 1]
,变为zy,之后x转化,为insert[x, 2]
,得到zxy这样就出现了问题。在引用文献[3]中提到了这个问题不能通过颠倒优先级顺序来解决,因为虽然颠倒优先级可以解决上面这个例子,但是会出现其余相似的问题。在解决问题的过程中,认为问题出在现在的优先级设定太粗暴了(用single site identifier),所以在[3]中提出了一个负载的priority scheme。
Priority Calculation: The priority of an operation Oi is calculated at the originating site at the time of execution, and is included when the operation is sent to all other sites. If there are operations in the local log whose original position (before transformation) is equal to the operation being initiated, then the highest of these priorities is chosen and the local site ID is appended to it to form the priority of Oi. If there are no such operations in the local log, then the priority is simply the local site ID.
Priority Comparison: Two priorities are compared element by element, from beginning of the list to the first element in which they differ. Which ever is larger at the element where the lists differ has the higher priority. If one list is a sublist of the other, then one operation is a predecessor of the other.
--[3]中提出的priority scheme,没看懂。。。
但是没有给出这个方法是否正确的证明。实际上不同的研究机构都发现在一些情况下dOPT不work。之后的各个小节,我们将介绍他们的修正方法。
REDUCE和GROVE一样采用了distributed and replicated system architecture。其使用了一个线性的History Buffer (HB)(和GROVE中的Log相同),用来追踪所有被执行过的operation。此外,REDUCE还加入了垃圾回收以回收HB中无用的operation。
除去最开始提到的divergence和causality-violation,REDUCE还提出了一种特殊的inconsistency problem——intention-violation。
例如,假设文档的初始值是ABCDE
。假设为insert["12", 2]
,为delete[2, 3]
(删除位置3开始的2个字符)。的本意是删除CD
。执行完两个operation之后,intention-preserved的结果应该是A12BE
。然而实际上在site-0,会得到A1CDE
。需要注意intention-violation和divergence的本质是不同的,divergence总可以通过串行化解决,而intention-violation不行。
由于intentio-violation问题,REDUCE相correctness criteria更改为:
定义:A consistency model
一个合作编辑系统是consistent的前提为其总是满足如下性质:
Intention-preservation:对于任何operation ,执行的效果在任何site上都是与的intention相同的,且执行的效果不会影响其他independent operations的效果。
定义:Intention of an operation
operation 的intention就是对在generate 时的文档做出的改变
为了支持这一consistency model,REDUCE继承了GROVE中的state-vector timestamping scheme以达到Causality-preservation。
定义:State Vector (SV) timestamping scheme
假设有个sites,编号为。每个site都维护一个长为的state vector。在site k的满足
对于在site k生成的,
对于remote site i生成的,其需要等到以下的两点才可以执行(这里应该是生成时的):
这两个条件可以让在site d所有casually precede 的都被执行之后再执行,从而保证了causality-preservation。
为了实现convergence,REDUCE提出了如下定义:
定义:Total ordering relation
Given two operations and generated at site and and timestamped by and , then iff:
where . 我们称 totally precedes 或者 totally follows 。We can see that if , then .
有了这个定义之后进行了如下的scheme。(HB就是记录所有执行过的operations的log)
定义:The undo/do/redo scheme
如果incoming 已经通过了remote operation execution conditions test(上面的两条),那么:
注意这里假设了所有的operation可逆。
这样就可以做到convergence了。有论文证明了,通过基于的顺序,所有的editing effect会是一样的。
因为REDUCE中transformation function不是用来保证convergence的,所以也不需要满足GROVE中提到的要求。
为了表明operation之间的关系,我们需要引入context这样一个术语,其意思为文档从初始状态到当前状态之间执行的operation序列。
定义:definition context and execution context
The definition context, denoted as , is the context of the document state on which is defined.
The execution context, denoted as , is the context of the the document state on which is to be executed.
当的时候,操作就保留了intention。
REDUCE引入了两种transformation function—— (inclusion transformation)和 (exlusion transformation)——以达成。为了确定变换前后的条件,我们还做了如下两个定义:
定义:Context equivalent relation ""
, iff
定义:Context preceding relation ""
, iff . ( means list concatenation)
根据以上的条件,我们有:
Specification:,把两个同时进行的operation变为先后执行
Specification:,把两个先后执行的指令变为同时执行
定义 Reversibility Requirement
为了方便,[17]中还定义了两个genertic的辅助函数:
// precondition
// 1. OL[1] context context equivalent with O
// 2. OL[i] context preceding OL[i+1]
LIT(O, OL) {
if OL = []
return O
else
return LIT(IT(O, OL[1]), Tail(OL)) // 这里1表示第一个元素
}
// precondition
// 1. OL[1] context preceding O
// 2. OL[i+1] context preceding OL[i]
LET(O, OL) {
if OL = []
return O
else
return LET(ET(O, OL[1]), Tail(OL)) // 这里1表示第一个元素
}
假设是一个causally ready operation (causaly precede的都执行完了)。然后假设其context为,,且有
我们的目标是确定的转化状态满足:
GOT(O_new, HB) {
k = -1
for (i=1; i<=m; i++) {
if HB[k] || O_new {
k = i
break
}
}
if k == -1 // 所有EO都casualy precede O_new, O_new不需要做变换
return O_new
EOL = [] // list of EO in HB[k+1, m] that are causally preceding O_new
for (i=k+1; i<=m; i++) {
if HB[i] causally precede O_new
EOL.append(HB[i]);
}
if EOL == [] // 如果EOL为空,HB[k,m]都不在DC(O_new)中,集体转化
return LIT(O_new, HB[k, m]);
r = len(EOL)
// c_1 is the origin index of EOL[1]
EOL[1] = LET(EOL[1], revese(HB[k, c_1-1]));
for (i=2; i<=r; i++) {
TO = LET(EOL[i], revese(HB[k, c_i-1]));
EOL[i] = LIT(TO, EOL[1, i])
}
O_new = LET(O_new, reverse(EOL));
return LIT(O_new, HB[k, m]);
}
把transformation和上面的undo/do/redo scheme结合记起来就有了如下的scheme
定义:The undo/transform-do/transform-redo scheme
如果incoming 已经通过了remote operation execution conditions test,那么
Transform-redo,所有未执行的在中的operation 都进行如下变换:
下面是的例子,来自[17] (Achieving Convergence, Causality Preservation, and Intention Preservation in Real-Time Cooperative Editing Systems)
// insertion[S, P]
// delete[N, P]
// P(O) is the position parameter of O
// L(O) is the length of operation O
// S(O) for insert O is the string to insert
// save_LI is to remember the lost of information for future reverse
IT_II(O_a, O_b) { // insert, insert
if P(O_a) < P(O_b) // 在前面插入,不需要变
return O_a
else
return insert[S(O_a), P(O_a)+L(O_b)]
}
IT_ID(O_a, O_b) { // insert, delete
if P(O_a) <= P(O_b)
return O_a
else if P(O_a) > P_(O_b) + L(O_b) // 在删掉的后面插入
return insert(S(O_a), P(O_a)-L(O_b))
else // 插入的位置被删除了,就要在删除的初始位置插入
save_LI()
return insert(S(O_a), P(O_b))
}
IT_DI(O_a, O_b) { // delete, insert
if P(O_a) + L(O_a) <= P(O_b) // 插入与删除无关
return O_a
else if P(O_a) >= P(O_b)
return delete(L(O_a), P(O_a)+L(O_b))
else // 如果插入在删除中间,要把插入跳过
return delete[P(O_b)-P(O_a), P(O_a)] + // 这里的"+"是要两个同时进行的
delete[L(O_a)-(P(O_b)-P(O_a)), P(O_b)+L(O_b)
}
IT_DD(O_a, O_b) { // delete, delete
if P(O_a) + L(O_a) <= P(O_b) // O_b与O_a无关
return O_a
else if P(O_a) >= P(O_b) + L(O_b) // O_b完全在O_a前面
return delete[L(o_a), P(O_a)-L(O_b)]
else {
save_LI()
if P(O_b) <= P(O_a) and P(O_a) + L(O_a) <= P(O_b) + L(O_b)
return delete[0, P(O_a)] // O_a被O_b包住了
else if P(O_b) <= P(O_a) and P(O_a) + L(O_a) > P(O_b) + L(O_b)
// 把o_b没删掉的删掉
return delete[P(O_a) + L(O_a) > P(O_b) - (L(O_b), P(O_b)), P(O_b)]
else if P(O_a) < P(O_b) and P(O_a) + L(O_a) <= P(O_b) + L(O_b)
return delete[P(O_b)-P(O_a), P(O_a)]
else
return delete[L(O_a)-L(O_b), P(O_a)]
}
}
然后是的例子就是用于恢复的,在[17]中也有记录。
REDUCE的方案解决了上文提出的dOPT puzzle,证明我没看...有兴趣的同学可以去自己看一下。
jupyter和GROVE的不同在于,jupyter有一个central server。一个client site本地生成operation之后,现在本地副本上执行,之后要把修改发送给central server。central server会对收到的operation做transformation,并在central server自己的副本上执行,最后再传播给其他的client sites。这样直接避免了发生causality violation (因为相当于做了symchronization),同时大幅简化了transformation control算法。
为了convergence,Jupiter approach也对transformation operation做和GROVE一样的要求。不过其使用一个state space图,而不是线性的Log/HB,来记录所有可能的operation transfomration paths。Jupyter算法保证任何一对进行transformation的算法都来源于state space graph的同意starting state,其本质上和GOT中的context equivalent一样。所以Jupyter算法可以纠正GROVE的问题。
文中后面还提到了两个算法,等之后有时间再看看吧~