mongodb——内存

sort操作内存溢出

报错

  exception: getMore runner error: Overflow sort stage buffered data 
  usage of 33638076 bytes exceeds internal limit of 33554432 bytes

这个异常出自mongodb的sort操作。当mongodb无法从索引获取排序顺序时,会在内存中执行排序(top-k排序)。而mongodb的排序内存默认只有32MB,如果排序的结果超过32MB会抛出异常。
解决这个问题有两种方式:

  1. 增大mongodb的排序内存,默认是32MB,可以视情况增大。
  2. 在排序的字段上增加索引。如果sort有索引,mongodb不会在内存中执行排序操作,而是直接通过索引进行排序。

mongodb内存管理

mongodb会把所有内存全部用光。
mongodb使用内存映射存储引擎,它会把磁盘IO转化为内存操作。读数据时,直接从内存读取数据;写数据时,将随机写转化为顺序写。mongodb不干涉内存管理,将内存管理工作交给操作系统去处理。在使用时必须随时监测内存使用情况,因为mongodb会把所有能用的内存都用完。

虚拟内存

操作系统把当前程序使用的部分内存保留在物理内存中,把其他没有使用的部分保留在磁盘中,并在使用时将这部分数据调入物理内存中。应用程序访问虚拟内存地址,由操作系统将虚拟内存映射到实际内存中。

Linux硬盘有一个swap区域,这是磁盘交换空间,当物理内存不足时,操作系统会在swap上保存数据。

内存映射

内存映射是一个文件到一块内存的映射,使应用程序可以通过内存指针对磁盘上的文件进行访问。其过程就像对加载了文件的内存访问。
在映射过程中,并没有实际的数据拷贝,文件并没有被放入内存,只是逻辑上放入了内存。具体到代码,就是建立并初始化了具体的数据结构。当要操作数据的时候,才会将文件数据从硬盘读入内存,如果物理内存不够用,则会使用swap空间。
内存映射比普通文件操作效率高,原因是普通文件读取执行了两次数据拷贝(硬盘-->内核空间-->用户空间),内存映射只进行了一次数据拷贝(硬盘-->用户空间)。而且普通IO操作从用户态转为内核态的不断切换十分耗时,内存映射省去了这个转换。

不推荐mongodb热数据超过物理内存大小。如果超过,操作系统会在物理内存和磁盘swap之间频繁交换数据,影响性能。

mongodb存储

mongodb将数据存放在磁盘文件上,一个数据文件有多个events组成,每个event可以保存集合数据或者索引数据(集合和索引数据不会区分保存)。每个events只保存一个集合的数据,不同集合的数据分散在不同的events中,一个集合的数据也被拆分在不同的events中。每个event保存的是单个文档数据。索引数据也是如此。collection的events信息被保存在命名空间文件中,只保存第一个event的信息,然后event之间通过双向链表组成events。event有额外的冗余空间padding,以便在数据更改的时候不用移动数据。
关于数据文件大小,从64M开始,每次新增数据文件,大小是上一个文件的两倍,直到2G为止。而且mongodb使用预分配,在当前数据文件快要用完时提前创建新的数据文件,并用0填充。这样可以使mongodb始终保持充足的空间,防止因为临时分配磁盘空间导致阻塞。
关于命名空间文件,后缀名为.ns,是数据库中最早生成的文件,负责保存数据库中的集合索引的元数据。默认情况下,.ns文件大小为16MB,可以存储24000个命名空间,也就是说数据库集合和索引的总数不超过24000。可以自行修改。

mongodb持久化

在传统数据库中,存在一个Redo Log的机制用来应对因为系统崩溃或掉电引发的数据丢失问题。原理是将操作写入日志,然后执行操作,然后操作完成后删除日志;如果发生系统崩溃,重启后首先会从Redo Log中重新执行操作。MongoDB 的journal就是实现这个目的的一种WAL日志。

不止传统数据库,Redo Log机制广泛存在于各种框架系统中。

mongodb的读写全部针对内存进行。如果没有journal,mongodb写入数据仅仅是到内存中,由操作系统决定什么时候将数据持久化到硬盘上,mongodb仅仅提供60s一次的强制写入硬盘。如果出现系统崩溃,那么从上一次刷盘到现在的数据就会丢失,一次丢失60s的数据。
从2.0开始,mongodb默认开启journal。数据写入首先会写入Journal Buffer,然后再写入内存,Journal Buffer每隔100ms写入磁盘,提交为单事务,全部成功或全部失败。这样就算系统崩溃,mongodb重启后会根据Journal文件恢复数据,最多丢失100ms数据。这个提交时间可以修改,从2ms到300ms之间。

如果journal开启,内存大小差不多翻番

如果不能忍受100ms的数据丢失,可以通过设定写关注来调整写入的可靠性。这里是通过放弃部分写入性能来提高写入的可靠性。

  1. {w: 0} Unacknowledged:对每个写入操作,mongodb不会返回一个是否成功的状态文档。通过放弃一切安全性换来了最好的性能。在这个级别下,向mongodb写入操作如果失败,客户端也不会有任何提示。很多使用mongodb保存日志文件使用这个级别,数据量大,就算丢失一两条数据也无所谓。
  2. {w: 1} Acknowledged:对每个写入操作,mongodb会确认数据写入主节点内存的情况。可以看重复主键, 网络错误,系统故障或者是无效数据等错误。从2.4开始,这个级别是默认级别。如果写入过程中发生了系统崩溃,只会损失内存中的100ms数据。
  3. {j:1} Journaled:对于每个写入操作,mongodb会确认数据通过journal落盘后才会返回。并不是每一次写操作都会刷新硬盘,mongodb在每次写操作后最多等待30ms,把30ms内的数据顺序写入硬盘。在这30ms内客户端处于等待状态。对于单个操作会有额外的延迟,但是对于高并发情况下,延迟和吞吐并不会有多大影响。如果存储系统针对顺序写进行优化,整个延迟会被降到最低。
    这里,如果mongodb是单机,那么以上级别已经足够安全。在集群情况下,可以使用下一个级别。
  4. {w: “majority”}:对于每个写入操作,写入到多数节点。mongodb的复制集一般为奇数节点。这个级别可以保障数据在集群环境中的一致性。

推荐阅读更多精彩内容