我正在尝试使用WiredTiger将大约2.5亿个文档(每个大约400个字节)插入到MongoDB 3.0中。我只需搜索一个短字符串键_user_lower
。虽然我现在正在使用WiredTiger,这比MMAPv1要好得多,但我确实首先使用了MMAPv1并且遇到了类似的问题。
我的服务器(非常便宜的VPS)有:
我知道这台机器真的很慢,我要求它做一些不切实际的事情。但我对它如何以一个索引开始这么快感到困惑,而第二个只是破坏了性能:
我插入了当时(大约250M行)的所有数据,没有任何索引,_id
除外。考虑到我糟糕的硬件,这表现非常好:
_id
完成后的索引大小接近2.5GB。请注意,这是物理RAM的两倍以上。top
似乎表明CPU时间并非全部用于等待磁盘(因此在用户空间中花费了大量资金,可能是snappy
代码中的WiredTiger)然后我在我需要查询的唯一字段_user_lower
上构建了一个(非唯一)索引。这花了7.7个小时,这很好,因为这是一次性交易。该索引最终为1.6 GB,与_id
索引相比,这对我来说似乎很低。 RES上升到大约750 MB。
然后,我下载了一个要加载的新数据集。它只有 102 MB(238 K文件)。我使用mongoimport
以相同的方式加载它,但这一次:
top
表示几乎100%的CPU用于等待IO 我可以理解相当大的性能损失,因为该索引必须更新。但我没想到这么多。我已经阅读了我的索引应该适合RAM的所有地方,但是在初始插入期间性能很好,索引很快就超出了我的记忆。
我可以优化_user_index
索引吗?我不知道这甚至意味着什么,但也许只能索引前几个字符?我绝对愿意将查询性能减半,以换取插入性能增加三倍。
对于大规模性能影响的原因是什么?如何在没有新硬件的情况下修复它?我并没有真正依赖MongoDB,所以没有这些性能特征的替代品都可以。我有一个想法,只使用平面文件可能会工作,但我不想写所有的代码。
答案 0 :(得分:3)
将新项目添加到集合时,数据库必须使索引保持最新。由于默认情况下MongoDB中的索引是B树,这意味着它必须在树中插入一个项目。虽然在最好的情况下这不是特别昂贵的操作,但它带来两个潜在的性能问题:
在这种情况下,后者很可能会引起麻烦:因为名称的插入会触及树中的随机节点(即,名称插入不遵循模式)并且您的RAM小于索引,必须从磁盘获取目标的可能性很高。不幸的是,磁盘搜索的性能是orders of magnitude lower than main memory references。如果你运气不好,第一个ref位置需要另一个磁盘搜索,以便在MongoDB甚至开始写入之前,对于单个插入需要多个磁盘读取。这可能需要数百毫秒,旋转磁盘或典型IaaS基础设施上的争用甚至几秒钟。
因为ObjectIds是单调生成的(时间戳是最重要的部分),所以插入总是在最后发生,并且可以将目标保持在RAM中。性能抖动,即问题1可能仍然是一个问题,因为存储桶拆分可能需要磁盘搜索,但与第一种情况相比,它很少发生,因为它不会破坏平均性能,这应该可以解释观察到的行为。
此外,当桶以单调递增的值填充时,MongoDB将在90%填充时拆分桶;随机插入,分裂将在50%之前发生很多,因此在这种情况下树更加“密集”。