MongoDB是第一个支持因果一致性的数据库商业产品

18-10-25 banq
                   

MongoDB版本3.6.4和4.0.0-rc1实现因果一致性(CC)支持,只要用户一直连接到majority 读写会话上,就能实现因果一致性,如果这种CC会话失败出错,也能提供不变性约束(数据完整性)。

背景

MongoDBJepsen的长期用户。在过去的三年中,Jepsen已经为MongoDB进行了多次分析,MongoDB已经将一个庞大的Jepsen测试套件集成到他们的CI系统中。3月,MongoDB要求Jepsen对以前未经测试的配置进行分析:分片群集。我们还开展了关于建模和验证因果一致性的新研究,这是MongoDB 3.6中的一个新安全功能。

分片集群sharded cluster

将一个文档看成一个集合,然后切分成元素,这称为分片,每个分片基于一个字段称为shard key,分片键。每个分片是独立分别存储在MongoDB的不同复制节点服务器上,一个路由处理器称为mongos的会将客户端请求路由转发到相应的节点服务器,这些相互复制的节点服务器集合称为configsvr,维持集群中状态的一致性,包括哪个分片位于哪个节点上的信息。

由于分片的大小可能会随着时间的推移而变化,因此MongoDB会将分片进一步划分为块chunks。如果分片变得太大,由配置服务器configsvr驱动的负载平衡器进程将分片拆分成块,并发布到到其他服务器产生更均匀的分布。

因果一致性

因果一致性(CC)是分布式数据库的一致性模型,它保证始终以相同的顺序观察与因果相关(可以想象成时间先后,这个时间有物理时间和逻辑时间)的操作。例如,只有在问题是可见的情况下才会出现对问题的答复,这样问题与答复就形成了依赖性的因果关系。在此模型中,没有依赖关系的操作被认为是并发的,并发操作可能没有明显的固定前后因果顺序。

因果是最终一致和线性化系统之间的众多中间 选择之一(banq注:线性化过于注重事务正确性,牺牲性能,比如区块链线性化,区块是通过前后连这根线串联起来,最终一致性是过于注重性能而暂时牺牲一些事务正确性,限制了应用场景)。

在最终一致的系统中,只要它们最终收敛,就可以按任何顺序观察操作。在线性化系统中,操作必须以与每个观察者相同的顺序出现,具有硬实时界限。因果一致性允许客户端仅等待相关操作的子集,而不是等待所有操作的总顺序。当总顺序太昂贵或无法提供并且允许实现提供改进的可用性时,这尤其有用。

到目前为止,因果一致性一般仅限于研究项目,如COPSBolt-on Causal ConsistencyAntidoteDB ; MongoDB是我们知道的第一个提供实现的商业数据库之一。

那么MongoDB如何处理因果一致性呢?如果我们将MongoDB集合标识为一组读写寄存器,那么MongoDB声称存在因果一致性的四个保证

  1. 读取你的写入:能够读取到之前的写入结果。
  2. 单调Monotonic读取:读取操作不会返回比先前一个读取操作更早的数据状态结果。
  3. 单调写入:在其他写入之前执行的写入操作必须在其他写入之前执行。
  4. 写入跟随读取:一个在读取以后发生的写操作必须在读取操作之后执行。

MongoDB客户端利用会话的概念捕获因果关系:单线程上下文,每个数据库操作都是在上一个操作之后排序的因果关系都放在这个上下文中。会话与客户端连接一起存在,并与单个客户端关联。每次读取和写入时,会话都会传递到其调用目标节点服务器,为客户端提供看到的最高服务器时间(highest server times)的节点服务器。

复制副本的节点使用操作日志(oplog),其中每个操作都由optime标识。Optimes是选举ID和时间戳的元组,它唯一地标识每个操作。节点只会单调地提前其时间戳以匹配本地挂钟,或者像Lamport时钟一样,来自任何其他节点的最高观察时间戳。

会话使用时间戳为操作提供单调排序关系。当会话要求服务器执行操作时,它包括会话观察到的最后一个时间戳; 服务器必须等到达到该时间戳才能为请求提供服务。即使在不同的分片之间也是如此:所有副本集中的所有节点基本上共享一个时间戳,而选举ID仅在各个副本集中有意义。

这提供了单调性,因为一旦操作大部分提交到oplog,则没有后续操作可以以较低的时间戳进行majority多数提交。同样地,当以特定时间戳执行多数读取时,具有相等或更高时间戳的多数读取器永远不会观察到该结果。

会话将其时间戳显示为因果标记。该令牌可用于强制一个会话观察另一个会话的结果。也就是说,用户可以存储令牌并将令牌传递给其他客户端,甚至是其他节点,以保留因果排序。

问题

我们确实发现了MongoDB的因果一致性问题:除非用户同时使用majority多数读写,否则它不起作用,并且因果一致性文档没有提到这一点。虽然MongoDB会以更安全的linearizable读取级别和不安全的写入问题拒绝因果请求的确认,但它会很乐意接受中间级别,如写入关注2或本地读取级别。由于许多用户出于性能原因使用次要多数sub-majority操作,并且由于因果一致性通常用于高性能本地操作,不需要与其他集群节点协调,因此用户可以合理地假设因果会话将确保其sub-majority操作的因果安全性; 但却不是。

此后,Mongo 向一致性文档添加了大量警告,建议用户其保证仅适用于majority/ majority操作,这有助于指导用户正确使用该功能。

即使使用因果会话,即使有majority写入保证,次要多数sub-majority读取也可能无法在先前成功写入时因果关系观察到,或者未能观察到先前读取的值。相反,次要多数写入可能是可见的,然后在领导者节点服务器转换领导权的情况下丢失,这意味着后续读取可能无法观察到成功确认的先前写入。我们将此解释为违反因果一致性。

MongoDB已经按照设计工作关闭了这个问题,认为对于次要多数写入和majority读取,会话实际上确实保持了因果一致性:

即使写入问题少于多数,也会保留已提交写入的因果顺序。但是,不保证耐用性durable。

MongoDB使用来自挂钟wall clock的单调时间戳,消息可能来自其他服务器的,或来自因果关系激活的客户端,使用时间戳作为相关操作之间的排序依据,具有不同选举ID的两个领导者可能具有类似的,本地单调的时间戳。因此,基于时间戳的因果会话仅可以对两个独立演进的领导者节点执行次要多数sub-majority 操作,同时仍然遵守单调时间戳顺序。这就是为什么我们观察到次要多数sub-majority操作异常的原因:MongoDB操作的因果结构(通常)不应仅仅通过时间戳捕获。