MongoDB WiredTiger中的底层时间戳
MongoDB中的一些最新特性(如多文档ACID事务)需要对底层的WiredTiger存储引擎中进行基础性的增强。
在这个由六篇文章组成的系列中,我们将一起看一下在MongoDB中使得数据库核心可以支持事务的一些变化。这些变化包括:
MongoDB/WiredTiger中的底层时间戳
MongoDB中的逻辑会话
支持本地快照读
实现全局逻辑时钟
启用安全从节点读取
增加可重试写入特性
我们将逐项检查这些特性,以回答这些问题:“添加了什么功能?”、“为什么这样实现?”以及“从整体上说它对事务有什么影响?”。
我们现在从MongoDB和WiredTiger的底层时间戳开始。
概述
MongoDB写操作的时间戳现在作为一项附加的元数据出现在WiredTiger存储层中。这使得MongoDB的时间和顺序在概念上变为可查询的,以便可以只检索特定时间或之前的数据。它通过创建MongoDB快照,允许数据库操作和事务可以从一个公共时间点开始工作。
背景
为了启用副本集的复制特性,MongoDB会维护一个操作日志,称为oplog。oplog是服务层中的一个专用集合,它列出了应用于数据库的最新操作。通过在从节点上重放这些操作,可以使副本保持最新状态,从而与主节点保持一致。oplog中的操作顺序对于确保副本正确反映主节点的内容至关重要。
MongoDB负责管理oplog的排序以及副本如何以正确的顺序访问oplog。使用WiredTiger这一的更强大的存储引擎以及它底层的顺序模型意味着充分利用WiredTiger的能力,这需要协调服务层和存储层的两个顺序模型。
WiredTiger存储引擎
WiredTiger将所有数据存储在一个包含键和值的树状结构中。作为MongoDB的存储层时,该数据可能是一个文档或某个索引的一部分,这两者都存储在WiredTiger的树中。当对某个键的值进行更新时,WiredTiger将创建一个用于更新的结构。此结构包含有关事务、已更改的数据以及指向其后任何更改的指针的信息。然后,WiredTiger将其附加到原始值,之后的更新会将自己添加到前一个结构的末尾,随着时间的推移创建一个不同版本值的链式结构。
这就是WiredTiger所实现的多版本并发控制组件。WiredTiger有着自己用于读取更新结构以获取某个值“当前”状态的规则。WiredTiger应用这些更新的顺序与MongoDB的oplog顺序并不相同。这个顺序上的差异来源于WiredTiger会在可能的情况下将多个写操作并行应用到从节点。由于主节点可以接受许多并行的写入,因此从节点需要能够达到相同的吞吐量,这就要让其自己的复制写入过程也是并行的。
时间戳
为了在WiredTiger存储引擎中保留MongoDB的顺序,我们在更新结构上扩展出了一个“timestamp”字段。此字段的值由MongoDB传递到WiredTiger层,并被WiredTiger视为一个重要的元信息。当使用WiredTiger进行查询时,可以指定一个时间戳以获取那个特定时刻数据的确切状态。这提供了一种在MongoDB顺序和WiredTiger顺序之间进行映射的方法。
从节点读取
当一个从节点从主节点同步时,它通过从oplog中读取一批更新来进行同步。然后,它尝试将这些更改应用到自己的存储中。如果没有时间戳,那么直到完成一批更新,应用操作的过程将阻塞读取查询,以确保用户不会看到无序的写入。有个这个时间戳,现在可以使用从当前批次开始的时间戳继续提供读取查询服务,该时间戳将确保对查询提供一致性的响应。这意味着从节点读取现在不会被复制更新中断。
复制回滚
当MongoDB集群中的多个从节点通过复制进行更新时,它们会处于与主节点同步的不同阶段。这意味着我们会有“多数提交点(majority commit point)”这一概念:即大多数从节点已经达到的时间点。当主节点发生故障时,所有节点上都保证只有达到该多数提交点的数据是可用的。而通过基于RAFT的共识协议,其中一个从节点会被选举为新的主节点,这就是从节点的工作方式。
当之前的主节点回到集群时,将该节点与集群的其余部分同步的过程非常复杂。因为它可能拥有一些公共点之外的数据,它必须找出它所做的那些集群并不知道的更改,并获取到它所更改的记录的旧版本。
引入时间戳之后可以从根本上简化这一过程。通过获取多数提交点的时间戳并将其应用于原主节点的存储层,而在该时间戳之后发生的更改可以删除。完成后,这个节点就可以重新加入集群并开始从主节点进行复制了。
时间戳和事务
通过将时间戳信息推送到WiredTiger的树结构中,可以使用WiredTiger的多版本并发控制来减少锁操作并简化重新同步的过程。快照时间点的能力还使服务器能够回滚到该时间点,这是多文档ACID事务正确性保证的基础。