MongoDB 插入第二个索引的性能

MongoDB insert performance with 2nd index

我正在尝试使用 WiredTiger 将大约 2.5 亿个文档插入 MongoDB 3.0,每个文档大约 400 个字节。我只需要搜索一个短字符串键 _user_lower。虽然我现在用的是 WiredTiger,比 MMAPv1 好很多,但我确实先用了 MMAPv1,也有类似的问题。

我的服务器(非常便宜VPS)有:

我知道这台机器真的很慢,我要求它做一些有点不切实际的事情。但我很困惑它是如何用一个索引启动得如此之快,而第二个索引却毁了性能:

我插入了我当时拥有的所有数据(大约 250M 行)除了 _id 之外没有任何索引。考虑到我糟糕的硬件,这表现非常好:

然后我在我需要查询的唯一字段 _user_lower 上建立了一个(非唯一)索引。这花了 7.7 个小时,这很好,因为这是一次性交易。索引最终为 1.6 GB,与 _id 索引相比,这对我来说似乎真的很低。 RES 上升到大约 750 MB。

然后,我下载了一个新的数据集来加载。它只有 102 MB(238 K 文档)。我以相同的方式加载它,使用 mongoimport,但这次:

我可以理解相当大的性能损失,因为必须更新该索引。但是没想到这么多。我已经阅读了所有关于我的索引应该适合 RAM 的地方,但是在初始插入期间性能非常好,索引很快就超出了我的记忆。

我完全可以优化 _user_index 索引吗?我什至不知道这意味着什么,但也许只索引前几个字符?我绝对愿意将查询性能减半以换取三倍的插入性能。

是什么导致了巨大的性能下降? 在没有新硬件的情况下如何修复它?我并不是很喜欢 MongoDB,所以没有这些性能特征的替代品也可以。我有一个想法,只使用平面文件,这可能会起作用,但我不想编写所有代码。

向集合中添加新项目时,数据库必须使索引保持最新。由于默认情况下 MongoDB 中的索引是 B 树,这意味着它必须在树中插入一个项目。虽然在最好的情况下这不是一个特别昂贵的操作,但它有两个潜在的性能问题:

  1. 性能抖动:有时,B-Tree 桶可能已满,需要拆分桶,因此比 'simple' 插入
  2. 需要更多操作
  3. 插入目标必须随时可用

在这种情况下,后者可能会引起麻烦:因为名称的插入会命中树中的随机节点(即名称插入不遵循模式)并且您的 RAM 小于索引,很有可能必须从磁盘中获取目标。不幸的是,磁盘寻道的性能是 orders of magnitude lower than main memory references。如果你不走运,第一个 ref 位置需要另一个磁盘搜索,这样在 MongoDB 甚至 开始 写入之前,对于单个插入需要多次磁盘读取。这可能需要数百毫秒,旋转磁盘或典型 IaaS 基础设施上的一些争用甚至几秒钟。

因为 ObjectIds 是单调生成的(时间戳是最重要的部分),插入总是发生在最后,并且可以将目标大部分保留在 RAM 中。性能抖动,即问题 1 可能仍然是一个问题,因为存储桶拆分可能需要磁盘寻道,但与第一种情况相比,它很少发生,不会破坏平均性能,这应该可以解释观察到的行为。

另外,当桶被单调递增的值填满时,MongoDB会在桶满90%时拆分桶;通过随机插入,分裂会发生得更早,达到 50%,所以在这种情况下树会多一点 'dense'。