MongoDB技术分享:WiredTiger存储引擎

3,470 阅读7分钟

内容来源:2018 年 10 月 27 日,MongoDB中文社区联席主席郭远威在“2018年MongoDB中文社区 广州大会”进行《WiredTiger存储引擎介绍》的演讲分享。IT 大咖说作为独家视频合作方,经主办方和讲者审阅授权发布。

阅读字数:2969 | 8分钟阅读

获取嘉宾演讲视频及PPT,请点击:t.cn/ELK5siB

摘要

本次分享的主题是WiredTiger的存储引擎,主要包含四部分内容,首先介绍MongoDB的插件式存储引擎的架构,然后是WiredTiger的事物,第三部分将介绍Checkpoint机制,最后通过一个案例,分析WiredTiger的cache分配和压缩特性。

插件式存储引擎架构

这个图最下层是存储引擎的最底层,中间还有一个内存的存储引擎。这些存储引擎的上面是MongoDB的文档数据模型,因此不管采用什么样的存储引擎,对于上层的应用程序开发者来说都是透明的。最上层是通过MongoDB数据库支撑的各种应用。可以看到总体的架构,实际上与Mysql有点类似,都是插件式的存储引擎架构。

事务特性与快照隔离级别

关系数据库中的事务是有隔离性的,而MongoDb也支持事务,且符合ACID事务的标准特性。通常事物会有写提交、读未提交、快照等隔离级别,MongoDB默认使用的是快照形式的隔离级别,任何事物开始的时候,先会对内存里面所有写操作但是还未完成的事务做一个拍照,然后记录这些写操作未完成事务的一个状态信息。

对于写操作事务,在写入之前先需要判断操是否与之前其他未完成的事务是否有冲突,如果有冲突的话就会执行失败,过一段时间后再重新提交事务,这里关键的在于能够判断写操作是否与其它的事务发生冲突。

MVCC实现事物的冲突检测与并发

多版本并发控制机制的引入,让我们可以检测是否冲突,它实际上是通过记录每一个事物开始的时候所操作的这条记录的版本号来实现的,这种方式在其他数据库里面同样存在。

比如我们有AB这两个写操作的事务。A事务首先从表中读取到要修改的行数据,读取到库存值为100,行记录的版本号为1。B事务也从中读取到要修改的相同行数据,读取值为100,行记录版本号为1。A事务修改库存值都提交,同时行记录版本号加1,大于一开始读取到的版本号1,因此A事务可以提交。但B事务提交时发现此时行记录版本号已经为2,产生了冲突,所以B事务会提交失败。接着B事务会尝试重新提交,在读取的版本号基础上加1,这样就不会再产生冲突,正常提交了。通过这种多版本并发控制的机制就可以防止B事物修改错误的数据。

典型操作事务执行流程

写操作事务开始执行之前,会对所有正在执行的还未提交事务进行快照。然后将本次写操作的动作保存到Operation_array中,可以从中提取出动作进行回滚,其次将修改的数据以日志形式记录下来,记录到日志缓存区域。写操作事务提交,首先会将日志缓冲区中的数据刷到磁盘上,写入到log文件,数据库意外宕机恢复时需要读取这个文件,重演文件里面的动作。

写操作引起的数据变化,首先写入到WiredTiger存储引擎的cache中,cache中的数据以btree的结构组织,btree的叶子节点是真正存放数据的page,当数据发生更改时page就变未“脏页”(在内存中);存储引擎默认从每60s将“脏页”中的数据写到物理磁盘上进行持久化。

引入checkpoint机制后的典型写操作流程

引入checkpoint后整个流程会发生相应的变化,主要是图中圈出来的地方。Checkpoint会产生在两个地方,一个是在默认情况下每60秒刷新磁盘的时候,将内存里面的脏数据刷到磁盘的时间点上,会在对应的数据文件上产生。另外一个时机是开启journal日志功能后,当日志文件达到2gb的时候,也会发生一次checkpoint。

checkpoint有两个重要的作用,一个是恢复数据库的时候,让我们只需要从最新的checkpoint时间点进行恢复,有效的缩减数据库恢复的时间。另一方面由于checkpoint完成以后,就可以认为内存里面在checkpoint时间点之前的数据都已经安全完整的写到磁盘上了,因此可以释放内存“脏页”所占的内存空间,达到节省内存空间的目的。

WiredTiger对内存的使用情况

wiredTiger对内存使用会分为两大部分,一部分是内部内存,另外一部分是文件系统的缓存。内部内存默认值有一个计算公式{ 50% of(RAM-1GB) ,or256MB },索引和集合的内存都被加载到内部内存,索引是被压缩的放在内部内存,集合则没有压缩。wiredTiger会通过文件系统缓存,自动使用其他所有的空闲内存,放在文件系统缓存里面的数据,与磁盘上的数据格式一致,可以有效减少磁盘I/O。

internal Cache的内部结构

在内存中数据是以btree的结构形式进行存储的,任何数据在写之前,都会先读取到internal cache里面。如上图第一步操作是调用块管理器,块管理器会将磁盘上的数据读取到内存。第二步修改数据的的时候,会重新分配一个新的page,在此基础上进行修改,修改完成后,原来page就会变成“脏页”,接着通过wiredTiger的内部机制将这个“脏页”重新通过块管理器刷到磁盘里面。

与internal cache相关的几个数据结构

首先是原始的page,通过列表形式把所有的数据给串起来。如果有修改动作,会再维护一个修改的page,在修改的page里面又会维护两个链表,保存的是链表的头,插入链表和修改链表的时候分别对应着两个数据结构,这样wiredTiger就不会将每一次的修改和插入操作直接写到磁盘上。当修改操作或插入操作累积到一定程度以后,在内存里面会将这些操作进行规整,整理以后,然后同时一次性的写入到的磁盘里面去。

案例:应用程序开发连接池问题

最后我们看一下与应用程序开发者比较相关的连接池的问题。当时我们有很多应用都用到了MongoDB,所有应用都创建了Mongo Client,这些应用经常会做些增删改查的动作,但是由于我们的服务器配置集成人员可能对分片集群的部署不是很熟悉,所以有些参数也没有关注到,导致了大量连接超时的的一个问题。

后续我们对此进行了分析,找到了具体原因。对于MongoDB来说,它的连接分为有两个部分,一个是驱动程序的连接词,另外一个是在服务器上,其中有一个参数决定了该服务器所能支持的最大的并发连接数。如果驱动程序的连接池远大于服务器所能支持的并发连接数,那么即使客户端程序没有出现连接问题,服务端也会出现连接拒绝的错误。为解决这方面的问题,我们可以分别从客户端和服务端的层面修改他们的默认连接池大小。

以上为今天的分享内容,谢谢大家!

编者:IT大咖说,转载请标明版权和出处