Couchbase Mobile使用多版本并发控制(MVCC)技术来处理冲突。 基于MVCC的系统面临的一个挑战是,随着时间的推移,一个文档可能会有多个版本。 可以得出这样的结论:如果文档的所有修订都被无限期地保留,那么数据库可能会变得非常大。 这是不可取的。
这是我们在Couchbase Mobile中解决冲突系列的第二部分。 在我们的第一篇文章《揭开冲突解决方案的神秘面纱》中,我们在幕后观察了使用MVCC的Couchbase Mobile是如何处理文档修改和冲突的。 在这篇文章中,我们将讨论在Couchbase Mobile中用于管理文档修订树大小的技术以及应用程序在其中的作用。
Couchbase Mobile堆栈包括在设备上本地运行的Couchbase Lite嵌入式数据库和云中的同步网关,后者通常由在云中保存数据的Couchbase服务器提供支持。 同步网关处理跨设备的文档复制。 可以想象,一个文档可以由多个设备同时更新。
Couchbase Mobile中的文档由文档标识、当前修订标识、JSON正文和元数据组成。 其中,元数据保存文档的修订历史。 在当前的Couchbase Mobile产品版本中,元数据存储在文档中嵌入的特殊_sync属性中。 在V1。5的同步网关中,元数据已经从文档中移出到XATTRs中。
本文假设您熟悉Couchbase Mobile的文档修订树结构和当前修订的概念。 如果您想了解更多信息,请参阅“揭开冲突解决方案的神秘面纱”一文。
为了防止文档变得太大,Couchbase Mobile使用了三种技术,即:
-
压实。
-
修剪。
-
文档到期。
同步网关和Couchbase Lite处理清理过程略有不同。 在任何适用的情况下,差异将在帖子中注明。
压缩被定义为清除非叶修订的JSON主体的过程。 压缩过程中不会清除叶修订。
在同步网关上
在同步网关上,压缩由系统在后台定期运行。 此外,应用程序可以通过管理界面上的压缩应用编程接口手动调用压缩过程。
在Couchbase Lite上
在Couchbase Lite上,压缩只能通过数据库对象上的压缩应用编程接口手动调用。
冲突解决对压缩的影响
压缩过程不会删除叶节点的JSON主体。 因此,如果您有大量未解决的冲突分支,那么您将有大量的叶子节点,JSON主体就在周围。
- 案例1:当冲突未解决时,压缩会保留所有叶节点的JSON主体。
-
案例2:冲突解决后(即e. 非获胜的分支是墓碑),压缩移除了非叶节点的JSON主体。
因此,解决冲突对于确保删除所有旧的和未使用的叶修订非常重要。
修剪是删除与旧的相关联的元数据和/或JSON主体的过程 非叶的 修订版。 叶修订不受影响。 每次添加修订时,该过程都会自动运行。 尽管本质上是相同的,但是在同步网关和Couchbase Lite之间,剪枝算法的工作方式略有不同。
在同步网关上
“旧版本”是指那些比revs_limit数据库配置属性指定的值更早的版本。 该属性默认为1000,这意味着对应于最后1000个修订的元数据存储在同步网关中。 尽管剪枝过程不会立即删除旧版本的JSON正文,但是所有非叶版本的文档的JSON正文,如果比5分钟的默认TTL值旧,将被定期运行的后台进程自动清除。
- 找到所有叶版本的最小生成号gmin(第一个组件是一个版本号)。
- 删除所有代数g ≤ gmin的非叶修订
在本例中:
- 假设配置设置为2(仅用于说明)。
- 该文档在第3代有未解决的冲突修订。
“旧版本”是指那些比数据库的maxRevTreeDepth属性指定的值更早的版本。 maxRevTreeDepth值默认为20,这意味着对应于最后20个修订的元数据和JSON主体将保留在Couchbase Lite中。
基本上,与同步网关不同,同步网关需要旧版本的JSON主体的临时备份(TTL为5分钟),Couchbase Lite上的修剪过程删除了元数据以及旧非叶版本的JSON主体。
需要注意的是,在不同的Couchbase Lite平台上,剪枝算法的处理方式可能略有不同。 从用户的角度来看,应该注意到Couchbase Lite上的剪枝技术将删除元数据和基于该值的旧版本的JSON主体。
- 对于树中每个没有墓碑的分支,修剪掉其代标识为 < (Depth Of Branch – ).
修剪后,您的文档可能会出现断开的分支。 修订树没有处于损坏状态,选择获胜修订的逻辑仍然适用。 然而,这可能会使某些合并无法解决冲突,如果这是你的问题,应该避免。
在本例中:
- 假设配置被设置为2(同样,在实践中你也不会想这么做)。
- 该文档在第3代有未解决的冲突修订。
修剪过程不会修剪掉叶修订。 因此,如果您有大量未解决的冲突分支,那么您将有大量的叶节点。
在同步网关上,修剪算法应用于修订树中最短的非墓碑分支。 这意味着在有许多未解决的冲突分支的情况下,修剪可能不会删除一些旧的修订。考虑下面的例子,
场景1
当冲突未解决时,修剪可能不会删除所有旧的修订,因为最短的分支可能是冲突的分支
场景2
然而,在冲突得到解决的情况下, gmin 对应于获胜修订分支的叶节点。
在同步网关上,在未解决冲突的情况下,也有可能冲突版本的父节点被过早删除。 这将导致如前所述的断开分支状态。
当文档过期时,文档的所有痕迹,包括其所有修订都将从系统中删除。 用户可以选择控制文档何时过期。 请注意,用户在更改文档的过期值时必须小心,因为文档可能会过早过期。 此功能对于创建可能只在短期内需要的临时文档非常有用。
在同步网关上
当通过PUT文档或bulk_docs应用编程接口将文档写入同步网关时,用户可以在文档正文中设置属性,以指定文档何时必须过期。
该值支持的格式包括:
-
JSON号码。 小于30天时以秒为单位,大于30天时以Unix为单位。
-
JSON字符串(数字格式);与JSON编号相同。
-
JSON字符串(国际标准化组织–8601日期)。
-
JSON为空。 将到期时间设置为零(无到期时间)。
CBLDocument上的expirationDate属性可用于指定文档必须过期的时间。 请注意,通过手动指定过期日期,您有可能在脱机模式下添加到Couchbase Lite的文档有机会与同步网关同步之前过期。
如修剪一节所述,Couchbase Lite数据库的属性默认为20,同步网关的属性默认为1000。
该值影响元数据在修剪过程中幸存的修订数量。 因此,具有非常大的值意味着文档元数据历史会变得非常大,从而导致数据库存储需求增加。
为了节省空间,人们可能会降低这些值。 但是如下所述,将它们做得非常小会产生不良后果
冲突分支的父节点可能会在冲突解决之前被删除,从而导致孤立的叶节点。 如果应用程序希望 n-合并并发更改的方式。 如果你进入这种状态,那么解决冲突的唯一选择就是选择一个获胜的分支,并删除所有不获胜的冲突分支。
您可能会得到如下例所示的断开的分支。 断开的分支是没有共同祖先的分支。 虽然这本身可能不是一个大问题,但应用程序负责为不成功的分支做铺垫。
In the example below, after syncing, the device ends up with two disconnected branches [16.19] and [35.38] without a common ancestor.
请注意,使用默认的树深度属性值也可能发生上述情况。 但是,如果这些值设置得太低,进入这种状态的可能性会大大增加。
在基于MVCC的系统中,一个重要的考虑是管理修订树的大小,防止它膨胀。 这篇文章讨论了Couchbase Mobile中用于控制文档大小的技术,以及冲突解决方案对数据库大小的影响。
最后,感谢同步网关团队的崔恩·莱登的深入评论,以及Couchbase Lite团队的延斯·阿尔夫克和吉姆·波顿的投入。