我正在努力筛选一个给我们带来很多麻烦的事件的遗迹。除了向管理层报告发生的事情之外,我还试图更好地了解这种情况是如何发生的以及如何在未来避免这种情况。另外,对于长篇大论很抱歉,我只是想提供细节。
我们的 Mongo 服务器上有两个数据库。我们的服务器是:
数据库 A包含的数据或多或少是一个 LIFO 队列,数据被插入,并在一天内分批处理,最旧的首先被处理。处理完就删除了。这是两者中较大的一个,读/写活动较高的一个,约为 1.3Gb。在过去,它已达到10-13Gb。网站上的用户活动跟踪日志是必不可少的。出于所有意图和目的,它是大量的一维数组和相对较小的文档大小。
数据库 B包含用户的事务日志。数据是随机读取和写入的,但在连续的时间块中。当用户登录时,更有可能访问该特定用户的文档。它大约为 0.3Gb,并且已经增长,但只是最小的。
我们已经经历了一段时间的高磁盘 Io,但在过去的几天里,它从大约 28Mbps 跃升到 35Mbps,这是我们的极限(以 100% 注册)。发生这种情况时,我们的 CPU 稳步上升,但 RAM 保持在原来的位置,大约为 750Mb。
纵观MMS,在此期间,数据库A似乎隆隆愉快沿,但是数据库B突然开始得到了很多page_faults,accessesNotInMemory和pageFaultExceptionsThrown。我们没有得到任何b 树未命中。我们的totalOpen和clientCursor_size稳步上升,但从未达到峰值。
该storageSize数据库约为13GB,所以大约比实际数据大小大一个数量级。
我们重新启动了 mongo 服务器,这导致我们的全局 RAM 使用量从 705Mb 跳到了大约 1530Mb,并保持在那里,但 CPU 再次上升,并且 diskio 几乎直线上升。
MMS 在重启后数小时内显示出与上述相同的行为。
我们尝试在数据库 B上一一重新创建索引(而不是reIndex()以减轻我们高峰期的负载)。我们找到了一个索引,在我们删除它之后,我们会尝试确保索引,它会运行几秒钟,然后将我们发送回控制台,但是 db.collection.stats() 不会显示索引。我们设法通过创建逆序索引在某种意义上重新创建它。
在低谷期间,我们在两个数据库上都运行了db.repairDatabase(),这解决了我们所有的问题。DiskIO 几乎为零;RAM 现在略高,约为 1.7GB;负载和 CPU 几乎没有注册;DB storageSize 下降到 1.2Gb 左右;没有更多的页面错误;我们的游标计数大约为 0。
我完全不知道发生了什么。索引在内存中。MongoDB 看起来像是将数据库和索引都完全存储在 RAM 中(重新启动后)。但是我们仍然在数据库 B上获得了大量磁盘访问。
我的问题:为什么 MongoDB 没有像它应该的那样将数据库 B存储在 RAM 中,而是继续转到磁盘记录?
编辑:附加 NewRelic 和 MMS 图表。
这里有几个单独的点,但我不认为 MongoDB 如何在 RAM 中存储数据在这里真的很重要 - MongoDB 只是使用mmap()
调用,然后让内核负责内存管理(Linux 内核将使用 Least Nearly默认情况下使用(LRU)来决定要调出哪些内容以及保留哪些内容 - 对此有更多细节,但并不是非常相关)。
就您的问题而言,听起来您可能有一个损坏的索引,尽管证据有些间接。现在您已经完成了修复(validate()命令会事先确认/拒绝),当前数据中不会有任何证据,但您可能会在日志中找到更多证据,尤其是当您尝试重新创建索引,或在查询中使用索引。
至于页面错误、btree stats、日志、锁百分比和平均刷新时间的峰值,具有导致大量索引更新并导致大量IO的批量删除的所有特征。图中映射内存稍后下降的事实表明,一旦您运行修复,存储大小就会显着减少,这通常表明存在严重的碎片(批量删除以及增加文档的更新是碎片的主要原因)。
因此,我会寻找在日志中记录为缓慢的大型删除操作 - 它只会在完成后记录,因此请寻找它在 MMS 中的事件结束后出现。不在副本集中运行的怪癖之一是,像这样的批量操作相对不明显——它在 MMS 图中显示为单个删除操作(通常在噪音中丢失)。
这些批量删除操作通常倾向于在最近未使用的旧数据上运行,因此已被内核(再次 LRU)从活动内存中分页。要删除它,您必须将所有数据重新分页,然后将更改刷新到磁盘,当然删除需要写锁定,因此故障、锁定百分比等会出现峰值。
为了为已删除的数据腾出空间,您当前的工作集被分页,这将影响您的正常使用性能,直到删除完成且内存压力减轻。
仅供参考 - 当您运行副本集时,批量操作会在其中序列化oplog
,因此一次复制一个 - 因此您可以通过它们在辅助副本的复制操作统计信息中的足迹来跟踪此类操作。对于独立实例(不查看已完成操作的日志)和其他次要指示,这是不可能的。
至于将来管理大量删除,通常将数据分区到单独的数据库中(如果可能),然后在不再需要旧数据时通过简单地删除旧数据库来删除旧数据,效率要高得多。这需要在应用程序端进行一些额外的管理,但它不需要批量删除,完成速度更快,限制碎片,并且删除的数据库还会删除磁盘上的文件,防止过度使用存储。如果可能,绝对推荐您的用例。