MongoDB系列之WiredTiger引擎

概述

关系型数据库MySQL有InnoDB存储引擎,存储引擎很大程度上决定着数据库的性能。

在MongoDB早期版本中,默认使用MMapV1存储引擎,其索引就是一个B-树(也称B树)。

从MongoDB 3.0开始引入WiredTiger(以下简称WT)存储引擎,在性能及稳定性上都有明显的提升。从MongoDB 3.2开始,WT作为默认的引擎,在索引和集合的检索上借鉴B+树。

WT是一个优秀的单机数据库存储引擎,拥有诸多特性,支持BTree、LSM Tree索引,支持行存储和列存储,实现ACID级别事务、支持大到4G的记录。

现代计算机近20年来CPU的计算能力和内存容量飞速发展,但磁盘的访问速度并没有得到相应的提高,WT就是在这样的一个情况下研发出来,充分利用CPU并行计算的内存模型的无锁并行框架,使得WT引擎在多核CPU上的表现优于其他存储引擎。针对磁盘存储特性,WT实现一套基于BLOCK/Extent的磁盘友好访问算法,使得WT在数据压缩和磁盘I/O访问上优势明显。实现基于snapshot技术的ACID事务,snapshot技术大大简化WT的事务模型,摒弃传统的事务锁隔离又同时能保证事务的ACID。WT根据现代内存容量特性实现一种基于Hazard Pointer的LRU cache模型,充分利用内存容量的同时又能拥有很高的事务读写并发。

WiredTiger存储引擎在数据检索性能上做了许多优化,基于内存的二级的缓存提供高速读取数据能力,在写方面则是根据磁盘I/O的特点做缓冲式写入,这是基于空间、时间因素权衡的一种择优设计。

MongoDB在写入更新记录时使用基于version的乐观锁模式,当写冲突产生(尝试更新失败)时,WiredTiger内部会产生WT_ROLLBACK结果,MongoDB检测到该状态之后会抛出WriteConflictException,最终由写入的执行线程捕获该异常并重试。

WiredTiger对事务的支持同时包含:未提交读、提交读、快照一致性读。MongoDB事务采用快照一致性读。

原理

读缓存

理想情况下,MongoDB可提供近似内存式的读写性能。WiredTiger引擎实现数据的二级缓存,第一层是操作系统层级的页面缓存,第二层则是引擎提供的内部缓存:
在这里插入图片描述
读取数据时的流程:

  • 数据库发起Buffer I/O读操作,由操作系统将磁盘数据页加载到文件系统的页缓存区
  • 引擎层读取页缓存区的数据,进行解压后存放到内部缓存区
  • 在内存中完成匹配查询,将结果返回给应用。

如果数据已经被存储在内部缓存中,MongoDB则可以发挥最佳的读性能。稍差的情况是内部缓存中找不到,但数据仍然被存储在操作系统的页缓存中,此时需要花费一些数据解压缩的开销。直接从磁盘加载数据时,性能是最差的。因此MongoDB为了尽可能保证业务查询的热点数据能快速被访问,其内部缓存的默认大小达到内存的一半,该值由wiredTigerCacheSize参数指定,其默认计算公式: w i r e d T i g e r C a c h e S i z e = M a t h . m a x ( ( R A M − 1 G B ) , 256 M B ) wiredTigerCacheSize=Math.max((RAM-1GB),256MB) wiredTigerCacheSize=Math.max((RAM1GB),256MB)

写缓冲

当数据发生写入时,MongoDB并不会立即持久化到磁盘上,而是先在内存中记录这些变更,随后通过CheckPoint机制将变化的数据写入磁盘。即,非实时持久化。

采用延迟持久化方案,则避不开可靠性问题。

MongoDB单机下保证数据可靠性的机制包括以下两个部分:

  1. CheckPoint机制:快照(snapshot)描述某一时刻(point-in-time)数据在内存中的一致性视图,而这种数据的一致性是WiredTiger通过MVCC(多版本并发控制)实现的。当建立CheckPoint时,WiredTiger会在内存中建立所有数据的一致性快照,并将该快照覆盖的所有数据变化一并进行持久化(fsync)。成功之后,内存中数据的修改才得以真正保存。MongoDB默认每60s建立一次CheckPoint,在检查点写入过程中,上一个检查点仍然是可用的。这样可以保证一旦出错,仍能恢复到上一个检查点。
  2. Journal日志:一种预写式日志(write ahead log)机制,主要用来弥补CheckPoint机制的不足。如果开启Journal日志,那么WiredTiger会将每个写操作的redo日志写入Journal缓冲区,该缓冲区会频繁地将日志持久化到磁盘上。默认情况下,Journal缓冲区每100ms执行一次持久化。此外,Journal日志达到100MB,或是应用程序指定journal:true,写操作都会触发日志的持久化。一旦MongoDB发生宕机,重启程序时会先恢复到上一个检查点,然后根据Journal日志恢复增量的变化。由于Journal日志持久化的间隔非常短,数据能得到更高的保障,如果按照当前版本的默认配置,则其在断电情况下最多会丢失100ms的写入数据。

结合CheckPoint和Journal日志,数据写入的内部流程图:
在这里插入图片描述
步骤:

  • 应用向MongoDB写入数据(插入、修改或删除)
  • 数据库从内部缓存中获取当前记录所在的页块,如果不存在则会从磁盘中加载(Buffer I/O)
  • WiredTiger开始执行写事务,修改的数据写入页块的一个更新记录表,此时原来的记录仍然保持不变
  • 如果开启Journal日志,在写数据同时会写入一条Journal日志(Redo Log)。该日志在最长不超过100ms之后写入磁盘
  • 数据库每隔60s执行一次CheckPoint操作,此时内存中的修改会真正刷入磁盘。

Journal日志采用的是顺序I/O写操作,频繁地写入对磁盘的影响并不是很大。在MongoDB 3.4及以下版本中,当Journal日志达到2GB时同样会触发CheckPoint行为。如果应用存在大量随机写入,则CheckPoint可能会造成磁盘I/O的抖动。在磁盘性能不足的情况下,问题会更加显著,此时适当缩短CheckPoint周期可以让写入平滑一些。

缓存页管理

WiredTiger仍然使用Page(页)作为数据存取的单元。其中,内存和磁盘中的页结构是不同的,Block Manager被用于处理这些差异。

页块在内存中以类B+树的结构进行组织,中间节点用于存放key,而叶子节点则存放key和value:
在这里插入图片描述
与传统B+树结构稍微不同的是,叶子节点(Leaf Page)通过父级指针(Parent Pointer)来实现范围遍历操作(避免并发写产生DeadLock)。当读取数据时,会先通过B+树索引找到对应的叶节点页面,而在页内则使用二分查找来查找记录。

当叶节点产生数据写入时,这些更新记录会被写入节点的一块独立区域,此时该节点被标记为脏页:
在这里插入图片描述
Inserts和Updates是单独的跳跃表(skiplist)结构,分别存放插入和修改操作,删除操作也被认为是一种修改(状态变更为删除)。如果存在修改,则读取时还会从跳跃表中做合并查找。

在CheckPoint时,引擎会通过Block Manager发起Reconciliation过程。此时,CheckPoint线程会遍历内存中的全部页并找到所有的脏页进行持久化,为了不阻塞读,使用Copy-On-Write方式。

对于脏页的处理并不是就地更新,而是为需要变更的页块生成新的节点(包括其父级节点),每次CheckPoint都会产生一个新的根节点(Root Page)。当持久化工作完成后,由这个新的根节点接管操作,淘汰不用的节点。

CheckPoint时,WiredTiger需要将BTree修改过的PAGE都进行持久化存储,每个BTree对应磁盘上一个物理文件,BTree的每个PAGE以文件里的extent形式(由文件offset + size标识)存储,一个CheckPoint包含如下元数据:

  • root page地址:地址由文件offset,size及内容的checksum组成
  • alloc extent list地址:存储从上次checkpoint起新分配的extent列表
  • discard extent list地址:存储从上次checkpoint起丢弃的extent列表
  • available extent list地址:存储可分配的extent列表,只有最新的CheckPoint包含该列表
  • file size:如需恢复到该CheckPoint状态,将文件truncate到file size即可

在Reconciliation过程中,BlockManager需要将内存中的页块转换为磁盘上的页,内存页要比磁盘页大一些:

  1. memory_page_max:内部缓存页大小的最大值,默认是5MB
  2. internal_page_max:磁盘中间页大小的最大值,默认是4KB
  3. leaf_page_max:磁盘叶节点页大小的最大值,默认是32KB
  4. allocation_size:磁盘文件的存储单元,默认是4KB,internal_page_max、leaf_page_max必须是它的整数倍

其中,memory_page_max的取值会影响写入延迟。这个值如果太小,则会导致频繁地分裂和淘汰(阻塞写入),如果太大则会导致每次产生阻塞的时间变长。internal_page_max存储的是B+树的索引,因此它会影响树的深度。在需要大量扫描磁盘记录的场景中leaf_page_max需要加大,可减少I/O次数,而在特别关注读写时延的场景中则需要适当减小。allocation_size则需要与操作系统的页缓存大小对齐,以达到最好的效率。

触发Reconciliation的条件:

  • CheckPoint
  • 缓存中的页超过最大值(存在大量的修改),产生分裂,此时会触发evict命令并持久化
  • 缓存中的脏数据比例达到一定阈值,触发缓存淘汰(evict)。

缓存淘汰策略
WiredTiger基于LRU算法来实现缓存淘汰,常态下会由后台的evict线程来负责淘汰页面。如果内存非常紧张,则用户线程也会加入缓存淘汰的工作中,此时表现出读写请求有一定阻塞。WiredTiger存储引擎eviction策略的4个可配置参数:

参数默认值解释
eviction_target80当cache used超过eviction_target时,后台evict线程开始淘汰Clean Page
eviction_trigger95当cache used超过eviction_trigger时,用户线程也开始淘汰Clean Page
eviction_dirty_target5当cache dirty超过eviction_dirty_target时,后台evict线程开始淘汰Dirty Page
eviction_dirty_trigger20当cache dirty超过eviction_dirty_trigger时,用户线程也开始淘汰Dirty Page

数据

里一个典型的WiredTiger数据库存储布局大致如下:

$tree
.
├── journal
│   ├── WiredTigerLog.0000000003
│   └── WiredTigerPreplog.0000000001
├── WiredTiger
├── WiredTiger.basecfg
├── WiredTiger.lock
├── WiredTiger.turtle
├── admin
│   ├── table1.wt
│   └── table2.wt
├── local
│   ├── table1.wt
│   └── table2.wt
└── WiredTiger.wt

文件解释:

  • WiredTiger.basecfg:存储基本配置信息
  • WiredTiger.lock:用于防止多个进程连接同一个WiredTiger数据库
  • table*.wt:存储各个tale(数据库中的表)的数据
  • WiredTiger.wt:特殊Table,用于存储所有其他Table的元数据信息
  • WiredTiger.turtle:存储WiredTiger.wt的元数据信息
  • journal:存储Write ahead log

压缩

默认情况下,WiredTiger对集合使用块压缩,对索引使用前缀压缩。当页面被写入磁盘时执行压缩,而从磁盘中读入缓存时对页面进行解压。持久化的Journal日志也会采用Snappy压缩算法(块压缩的一种)

内部缓存和磁盘中的数据有着不同的格式:

  1. 磁盘中的数据和文件系统的缓存是一致的,这些都是经过压缩的。文件系统缓存是操作系统层的机制,这是为了减少磁盘I/O而做出的优化
  2. 集合数据在内部缓存中是未经过压缩的(方便直接读写),而在磁盘和页缓存中则保持压缩的格式
  3. 索引在磁盘和页面缓存中均保持前缀压缩的形态,其在内部缓存中是另外一种结构,但同样利用前缀压缩算法

参数

  • storage.journal.commitIntervalMs:Journal日志的刷新周期。3.4及以下版本默认值50ms,3.6版本之后调整到100ms
  • storage.syncPeriodSecs:CheckPoint刷新周期,默认60s。
  • storage.wiredTiger.collectionConfig.blockCompressor:用于指定集合数据的压缩算法,选项如下:
    • None:不启用压缩
    • Snappy:默认,谷歌开源的强大而稳定的压缩算法,最高可达30%以下的压缩比,性价比较好
    • Zlib:相比Snappy来说压缩率更好,但需要消耗更多的CPU
    • Zstd:Facebook提供的新型高速压缩算法,能以较低的CPU消耗实现更高的压缩比。MongoDB 4.2版本开始支持
  • storage.wiredTiger.indexConfig.prefixCompression:用于指定是否启用索引前缀压缩(key prefix compression),默认true。前缀压缩对于CPU的消耗很小,平均可达到近50%的压缩率。

参考

  • MongoDB进阶与实战:微服务整合、性能优化、架构管理
  • https://mongoing.com/archives/5367

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/383318.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arduino D1 中esp8266 没有ide的库

http://arduino.esp8266.com/stable/package_esp8266com_index.json https://arduino.esp8266.com/stable/package_esp8266com_index.json 这个是官网的包地址 拿到后复制到arduino ide中 然后在开发板管理器,搜索esp,搜出来后安装 去开发板选择 然后测…

导数的几何意义【高数笔记】

1. 高数中的导数几何意义,与中学中斜率的联系 2. 导函数与导数的区别和联系又是什么 3. 导数的几何意义的题型是什么 4. 这些题型又有哪些区别 5. 点在曲线外和点在曲线上,需要注意什么 6. 法线和切线有什么关系 7. 法线是什么

MySQL数据库-索引概念及其数据结构、覆盖索引与回表查询关联、超大分页解决思路

索引是帮助mysql高效获取数据的数据结构,主要用来提高检索的效率,降低数据库的IO成本(输入输出成本(Input-Output Cost)),同时通过索引对数据进行排序也能降低数据排序的成本,降低了CPU的消耗。 Mysql的默认存储引擎InnoDB,InnoDB采用的B树的…

STM32 7-8

目录 ADC AD单通道 AD多通道 DMA DMA转运数据 DMAAD多通道 ADC AD单通道 AD.c #include "stm32f10x.h" // Device header/*** brief 初始化AD所需要的所有设备* param 无* retval 无*/ void AD_Init(void) {RCC_APB2PeriphClockCmd(RCC_AP…

sheng的学习笔记-网络爬虫scrapy框架

基础知识: scrapy介绍 何为框架,就相当于一个封装了很多功能的结构体,它帮我们把主要的结构给搭建好了,我们只需往骨架里添加内容就行。scrapy框架是一个为了爬取网站数据,提取数据的框架,我们熟知爬虫总…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之AlphabetIndexer组件

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之AlphabetIndexer组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、AlphabetIndexer组件 可以与容器组件联动用于按逻辑结构快速定位容器显…

C#使用重载方法实现不同类型数据的计算

目录 一、涉及到的相关知识 1.重载的方法 2.Convert.ToInt32(String)方法 3.判断字符串是否带有小数点 二、实例 1.示例 2.生成成果 一、涉及到的相关知识 1.重载的方法 重载方法就是方法名称相同,但是每个方法中参数的数据类型、个数或顺序不同的方法。如果…

【电路】三个晶体管的声控开关电路

这种声控开关,可能是非常有用的,例如敲门声或拍手声可以激活一盏灯,灯光几秒钟后会自动关闭。另一种使用在防盗保护,如果有人想打开门或打破东西,灯就会亮起来,这表明有人在家。 该电路可以工作于任何5–1…

disql备份还原

disql备份还原 前言 本文档根据官方文档,进行整理。 一、概述 在 disql 工具中使用 BACKUP 语句你可以备份整个数据库。通常情况下,在数据库实例配置归档后输入以下语句即可备份数据库: BACKUP DATABASE BACKUPSET db_bak_01;语句执行完…

使用Cargo创建、编译与运行Rust项目

在 Rust 开发中,Cargo 是一个非常重要的工具,它负责项目的构建、管理和依赖管理。以下是如何使用 Cargo 创建、编译和运行 Rust 项目的详细步骤。 1. 创建新项目 首先确保你已经在计算机上安装了 Rust 和 Cargo。然后,在命令行中输入以下命…

【动态规划】1301. 最大得分的路径数目

作者推荐 【动态规划】【前缀和】【C算法】LCP 57. 打地鼠 本文涉及知识点 动态规划汇总 LeetCoce1301. 最大得分的路径数目 给你一个正方形字符数组 board ,你从数组最右下方的字符 ‘S’ 出发。 你的目标是到达数组最左上角的字符 ‘E’ ,数组剩余…

【Tauri】(1):使用Tauri1.5版本,进行桌面应用开发,在windows,linux进行桌面GUI应用程序开发,可以打包成功,使用 vite 最方便

1,视频地址: https://www.bilibili.com/video/BV1Pz421d7s4/ 【Tauri】(1):使用Tauri1.5版本,进行桌面应用开发,在windows,linux进行桌面GUI应用程序开发,可以打包成功&…

springboot177健身房管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计,课程设计参考与学习用途。仅供学习参考, 不得用于商业或者非法用途,否则,一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

Java图形化界面编程——弹球游戏 笔记

Java也可用于开发一些动画。所谓动画,就是间隔一定的时间(通常小于0 . 1秒 )重新绘制新的图像,两次绘制的图像之间差异较小,肉眼看起来就成了所谓的动画 。 ​ 为了实现间隔一定的时间就重新调用组件的 repaint()方法,可以借助于…

正则可视化工具:学习和编写正则表达式的利器

引言 正则表达式是一种强大的文本匹配和处理工具,但对于初学者和非专业开发者来说,编写和理解正则表达式可能是一项具有挑战性的任务。为了帮助人们更好地学习和编写正则表达式,正则可视化工具应运而生。本文将探讨正则可视化工具的优点&…

自动化AD域枚举和漏洞检测脚本

linWinPwn 是一个 bash 脚本,可自动执行许多 Active Directory 枚举和漏洞检查。该脚本基于很多现有工具实现其功能,其中包括:impacket、bloodhound、netexec、enum4linux-ng、ldapdomaindump、lsassy、smbmap、kerbrute、adidnsdump、certip…

力扣49. 字母异位词分组

Problem: 49. 字母异位词分组 文章目录 题目描述思路复杂度Code 题目描述 思路 1.我们利用一个无序映射以排序后的字符作为键、字符数组作为值; 2.每次我们从原始数组中取出一个字符串并对其进行排序,并将其添加到对应键所存的数组中; 3.创建…

OpenAI---提示词工程的6大原则

OpenAI在官方的文档里上线了Prompt engineering,也就是提示词工程指南,其中OpenAI有提到写提示词的6条大的原则,它们分别是: (1)Write clear instructions(写出清晰的指令) &#xf…

【漏洞复现】狮子鱼CMS文件上传漏洞(wxapp.php)

Nx01 产品简介 狮子鱼CMS(Content Management System)是一种网站管理系统,它旨在帮助用户更轻松地创建和管理网站。该系统拥有用户友好的界面和丰富的功能,包括页面管理、博客、新闻、产品展示等。通过简单直观的管理界面&#xf…

Java的接口

目录 1.接口的概念 2.语法规则 3.接口的使用 4.接口的特性 总结: 5.实现多个接口 6.接口间的继承 1.接口的概念 接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。 在Java中,接口可以看成…