0%

MongoDB 无锁 Write FLow

MongoDB 无锁 Write FLow

同一 key 的无锁读写

MongoDB 的一个 B+树索引中具有三个数据结构来存储键值对数据:

  • WT_ROW 数组,存储原本 Check Point 时刻的值。
  • WT_UPDATE 跳跃表数组,存储已有键值对的更新值。
  • WT_INSERT_HEAD 跳跃表数组,存储新插入键值对的更新值。

MongoDB 中所有的写操作并不会直接修改原有的内存位置,而是将新数据写入到B+树索引节点中的跳跃表中。结合惰性删除机制,跳跃表是可以做到无锁读写的。

但是这种设计会使得读取的流程较长,因为要查询数据需要依次搜索WT_UPDATE,WT_ROW和WT_INSERT_HEAD 这三个数据结构,当插入数据过多时,跳跃表的搜索性能是要比 WT_ROW 的有序数组结构差的。

Check Point 技术

MongoDB 中会定期对 B+ 树进行整理,防止插入数据过多,影响读取性能,Check Point 技术会定期整理内存和硬盘中的 B+树结构。

当一次 Check Point 发生后,MongoDB 将会新建一个 B+树,所有的写入操作会被转移到新的 B+树中。而所有的刷盘以及分裂操作只会影响到旧的 B+ 树。具体过程比较复杂,可以参考《MongoDB 核心原理与实践》。

变长 Page 减少 B+ 树节点分裂

MongoDB 中的索引部分使用的是 B+ 树这种数据结构,一般来说 B+ 树在节点分裂的过程中是无法做到无锁的,但是 WiredTiger 基于变长 Page 的技术来避免 B+ 树分裂时加锁。

在数据库系统中,B+树在硬盘中的页面大小通常是硬盘扇区的大小,这主要是考虑到硬盘读写的原子性,防止破坏 B+树节点的内部结构。因为这种考虑,B+树的硬盘节点通常都比较小。而比较小的 B+树节点可能就意味着需要经常进行分裂操作来保持 B+树的平衡性。

MongoDB 的 B+ 树在内存中和在硬盘中的 page 大小是不相同的,内存中的 page 大小要远远大于硬盘中的 page 大小。当内存中的 B+树 page 需要刷盘时,会将内存 page 分裂为多个硬盘 page 写入到硬盘中,这就是变长 Page 技术。变长 Page 技术能够减少 B+ 树的持久化次数,还能够尽量避免 B+ 树因为插入而导致的分裂。引入了变长 Page 技术后,MongoDB 中索引 B+ 树的节点分裂会被转移到 reconcile 和 eviction 过程中,从而避免了在 Write Flow 中出现索引变更的情况。

插入过多时小粒度分裂

仅仅依靠变长 Page 还不能够解决所有的问题,如果在某一时刻发生大量的写入操作,导致一个B+树节点在短时间内就达到了内存 page 的上限,这种情况下依旧需要发生 B+树节点的分裂。

由于 MongoDB 中所有新插入的节点都是存在于 WT_INSERT_HEAD 跳跃表数组中的,当B+树节点因为插入数量过多需要分裂时,会将WT_INSERT_HEAD 中的最后一个跳跃表复制到新的节点中(MongoDB 中的主键值基本上是自增的,最后一个跳跃表中的节点在大部分情况下是最多的)。这种小粒度的分裂能够避免对B+树进行大面积修改。