MongoDB中的常见问题(四)——存储

MongoDB存储

存储引擎基础

什么是存储引擎?

存储引擎是数据库中负责管理数据在内存和磁盘上存储的一个部分。许多数据库支持多种存储引擎,其中不同的引擎适用于特定的工作负载。例如,一个存储引擎可能会在重读的工作负载下提供更佳的性能,而另一个则可能对写入操作提供一个更高的吞吐。

我们能否在一个复制集中混合存储引擎?

是的,我们可以在复制集成员中使用不同的存储引擎。

在设计这些多个存储引擎的部署时,考虑下面几个因素:

  • 在不同的存储引擎之间,每个成员上的操作日志可能需要设置不同的大小,以便于用于吞吐上的不同。
  • 如果我们的备份从MongoDB中获取数据文件,那么从备份中的恢复可能会变得更加复杂。我们也许需要为每个存储引擎维护不同的备份。

WiredTiger存储引擎

我们能否将一个现有的部署升级到WiredTiger?

可以。

WiredTiger提供了多大的压缩率?

压缩后的数据大小和未压缩的数据大小的比例根据数据和使用的压缩库而定。默认地,WiredTiger中的集合数据使用Snappy块压缩;当然,WiredTiger中也提供了zlib压缩。索引数据默认使用前缀压缩。

我们应该将WiredTiger内部缓存设置为多大?

使用WiredTiger的情况下,MongoDB同时会利用WiredTiger内部缓存和文件系统缓存。

从3.4版本开始,默认的WiredTiger内部缓存将会使用大于下列值的缓存大小:
– RAM的50%减去1GB
– 256MB

通过文件系统缓存,MongoDB自动使用未被WiredTiger缓存或其它进程使用的空闲内存。文件系统缓存中的数据是已经被压缩的数据。

可以使用storage.wiredTiger.engineConfig.cacheSizeGB--wiredTigerCacheSizeGB来调整WiredTiger内部缓存的大小。以此来避免不断上升的WiredTiger内部缓存大小超过其默认值。

注意:storage.wiredTiger.engineConfig.cacheSizeGB限制了WiredTiger内部缓存的大小。操作系统将会使用可用的空闲内存作为文件系统缓存,这样可以使得压缩的MongoDB数据文件维持在内存中。此外,操作系统将会使用所有空闲的RAM来缓冲文件系统块和文件系统缓存。为了配置RAM的其它消耗,我们不得不降低WiredTiger内部缓存大小。

默认的WiredTiger内部缓存大小值假定每台机器上有一个单一的mongod实例。如果一个单台机器上包含多个MongoDB实例,那么我们应该降低这个配置来满足其它mongod实例。

如果我们在一个容器(例如,lxc,cgroups和Docker等)中运行mongod,对系统中可用的所有RAM 没有读取权限,我们必须将storage.wiredTiger.engineConfig.cacheSizeGB设置到一个少于容器中可用的RAM量的值。精确的值根据容器中运行的其它进程而定。

如果我们希望查看缓存和内存淘汰率,可以查看serverStatus命令返回的wiredTiger.cache字段。

WiredTiger写入磁盘的频率是多少?

MongoDB配置了WiredTiger来在60秒的时间价格或者日志数据的2GB的间隔时创建检查点(例如,将快照数据写入磁盘)

对于日志数据,MongoDB根据下面的间隔或条件将数据写入磁盘:

  • 从3.2版本开始:每50毫秒。
  • MongoDB将WiredTiger中的检查点设置在用户数据上,具体为:无论第一次存在什么时候,60秒时间间隔或者2GB的日志数据被写入时。
  • 如果写操作包括一个j:true的写关注,WIredTiger会强制WiredTiger日志文件的同步。
  • 因为MongoDB使用100MB大小的日志文件,WiredTiger会针对每100MB的数据创建新的日志文件。当WiredTiger创建一个新的日志文件时,WiredTiger会同步之前的日志文件。

MMAPv1存储引擎

什么是内存映射文件?

内存映射文件是一个操作系统通过mmap()系统调用来放置在内存中的数据。mmap()将文件映射到虚拟内存的区域。内存映射文件是MongoDB中MMAPv1存储引擎中非常重要的一个部分、通过使用内存映射文件,MongoDB可以将其数据文件的内容看做是数据在内存中。这就使得MongoDB可以使用一个特别快速和简单的方法来读取和操作数据。

内存映射文件如何工作?

MongoDB使用内存银蛇文件来管理和与所有数据交互。

内存映射根据一个直接的字节对字节的对应关系来将文件分配到虚拟内存的块中。MongoDB内存在其读取文档时将数据文件映射到内存。未读取的数据不会映射到内存中。

一旦映射,文件和内存之间的关系将会使得MongoDB与文件中的数据交互如同其在内存中一样。

MMAPv1将数据写入到磁盘中的频率如何?

在MMAPv1存储引擎的默认配置中,MongoDB每60秒将数据文件写入到磁盘中或者每100毫秒将数据粗略地写入到日志文件。

如果想要修改写入到数据文件中的时间间隔,我们可以使用storage.syncPeriodSecs设置。而关于日志文件的配置,可以查阅storage.journal.commitIntervalMs设置。

这些值表示着写操作完成和MongoDB将其写入数据文件或者日志文件之间的最大时间差值。在很多情况下,MongoDB和操作系统会更频繁地将数据刷到磁盘,因此上面的值表示了一个理论上的最大值。

为什么我们的数据文件夹中的文件大于数据库中的数据?

我们数据文件夹中的数据文件,默认配置中的/data/db文件夹,可能会大于插入数据库中的数据集。考虑下面可能的原因:

预分配的数据文件
MongoDB预先分配其数据文件以避免文件系统分割,因此,这些文件的大小并不真正反映我们数据的大小。

storage.mmapv1.smallFiles选项将会降低这些文件的大小,如果我们在磁盘上有许多小数据库,那么该选项将会非常有用。

操作日志
如果该mongod是复制集的成员,那么数据文件夹包括oplog.rs文件,是local数据库中预先分配的限制集。

默认的分配为64位安装中大约5%的磁盘空间。在大多数情况下,我们不应该对操作日志进行重定义大小。

日志
数据文件夹包括日志文件,其存储着在MongoDB将写操作应用到数据库之前对写入操作。

空记录
在删除文档和集合时,MongoDB维护着数据文件中的空记录列表。MongoDB可以重用这个空间,但是默认地并不会将这个空间返回给操作系统。

为了使得MongoDB更高效地重用空间,我们可以对我们的数据进行磁盘碎片重新整理。可以使用compact命令来对磁盘碎片进行整理。compact需要高达2GB的额外磁盘空间来运行。如果我们在磁盘空间比较少时,不要使用compact

compact只会从一个集合中的MongoDB数据文件中删除碎片,并不会将任何磁盘空间返回给操作系统。

我们如何回收磁盘空间?

在回收磁盘空间时,可以考虑下面几个选项:
注意:我们不需要回收MongoDB的磁盘空间以重用释放的空间。

repairDatabase
我们可以在数据库中使用repairDatabase以重构数据库,重新整理进程中相关存储的碎片。

repairDatabase要求等于当前数据集大小加上2GB大小的空闲磁盘大小。如果数据库路径的容量缺少充足的空间,我们可以挂载一个单独的磁盘并且使用该磁盘用于修复。

警告:如果我们只有非常少的磁盘空间时,千万不要使用repairDatabase。它将会阻塞其它所有的操作并且可能会花费非常长的一段时间才会完成操作。

我们只能在一个单机的mongod实例上运行repairDatabase

我们也可以通过使用--repair--repairpath选项重启我们的单机mongod实例来对服务器上的所有数据库进行repairDatabase操作。在这个操作过程中,服务器中的所有数据库将会变得不可用。

重新同步复制集的成员
对于复制集中的从节点成员,我们可以通过关闭从节点成员来重新同步,删除成员的数据目录中的所有数据和子目录,最后重启来执行一个成员的重新同步。

工作集是什么?

工作集表示在正常操作过程中应用使用的数据总大小。通常情况下,工作集是整体数据大小的子集,但是工作集的特定大小会根据实际的每时每刻的数据库使用而定。

如果我们运行一个要求MongoDB扫描集合中每个文档的查询,那么工作集将会扩大到包含每个文档。而根据物理内存大小的不同,有可能会造成工作集中的文档”移出分页”,或者被操作系统从物理内存中删除。下次MongoDB需要读取这些文档时,MongoDB可能会触发一个硬分页错误。

如果想要获得最佳的性能,我们大部分活跃的集合应该全部放在RAM中。

什么是分页错误?

如果使用MMAPv1存储引擎,MongoDB从一些目前不在物理内存中的数据文件中读取或写入数据时,将会触发分页错误。相反地,当物理内存耗尽或者物理内存的分页交换到磁盘时,操作系统的分页错误将会被触发。

如果当前有空闲的内存,那么操作系统将会找到磁盘中的分页然后直接将其导入内存。然而,如果没有空闲的内存,操作系统必须:

  • 找到内存中陈旧或不再需要的分页,然后将分页写入到磁盘上
  • 读取请求的分页然后将其倒入到内存

在一个活跃的系统中,这个进程可能会花费大量时间,特别是与读取一个已经在内存中的页面相比。

软分页错误和硬分页错误的区别是什么?

当MongoDB使用MMAP存储引擎需要存取目前不在活跃内存的数据时可能会触发分页错误。一个“硬”分页错误指的是当MongoDB必须访问磁盘来读取数据的情形。而相反地,一个“软”分页错误只是将内存分页从一个列表移动到另一个(例如,从操作系统文件缓存中移动)。

我们能否在更新时手动填充文档以防止移动?

从3.0.0版本开始修改

使用MMAPV1存储引擎时,如果文档大小成规模地增长,那么一个更新可能会造成文档的移动。MongoDB使用填充以最小化文档移动。

我们不应该手动填充,引文MongoDB默认使用2的幂次大小分配来自动增加填充。2的幂次大小分配保证了MOngoDB以2的幂次大小来分配文档大小,帮助保证MongoDB可能高效重用文档删除或者再定位创建的空闲空间,也可以减少许多情况下的重定位的存在。

然而,如果我们不得不手动填充文档,我们可以向文档中增加一个新的临时字段然后$unset该字段,如下面的示例所示:

  1. var myTempPadding = [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  2. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  3. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  4. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"];
  5. db.myCollection.insert( { _id: 5, paddingField: myTempPadding } );
  6. db.myCollection.update( { _id: 5 },
  7. { $unset: { paddingField: "" } }
  8. )
  9. db.myCollection.update( { _id: 5 },
  10. { $set: { realField: "Some text that I might have needed padding for" } }
  11. )

警告:不要在限制集合中手动填充文档。在一个限制集合中运行手动填充文档可能会中断复制。此外,如果我们重新同步MongoDB实例那么该填充将不会保存。

数据存储诊断

我们如何检查一个集合的大小?

如果想要查看一个集合的统计,包括数据大小等,在mongo shell上使用db.collection.stats()方法。 下面的示例在orders集合上使用一个db.collection.stats()

  1. db.orders.stats();

MongoDB也提供了下面的方法来返回集合的特定大小:
– db.collection.dataSize()返回该集合以字节为单位的数据大小
– db.collection.storageSize()返回以字节为单位的分配大小,包括未使用的空间
– db.collection.totalSize()返回以字节为单位的数据大小以及索引大小
– db.collection.totalIndexSize()返回以字节为单位的索引大小。

下面的脚本打印每个数据库的统计数据:

  1. db._adminCommand("listDatabases").databases.forEach(function (d) {
  2. mdb = db.getSiblingDB(d.name);
  3. printjson(mdb.stats());
  4. })

下面的脚本打印每个数据库上每个集合的统计数据:

  1. db._adminCommand("listDatabases").databases.forEach(function (d) {
  2. mdb = db.getSiblingDB(d.name);
  3. mdb.getCollectionNames().forEach(function(c) {
  4. s = mdb[c].stats();
  5. printjson(s);
  6. })
  7. })

我们如何检测一个集合的索引大小?

使用db.collection.stats()方法来查看分配给一个索引的数据大小并且在返回的文档中查看indexSizes字段。

我们如何得到一个数据库上的存储使用信息?

mongo shell 中的db.stats()方法返回“活跃”数据的当前状态。

打赏

mickey

记录生活,写给几十年后的自己。