【问题标题】:MongoDB insert performance with 2nd index使用第二个索引的 MongoDB 插入性能
【发布时间】:2015-08-08 12:55:24
【问题描述】:

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

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

  • 250 GB 磁盘
  • 1 GB 内存
  • 2 GB 交换
  • 2.1 GHz 单核 CPU

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

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

  • 大约 每秒 5000 次插入(完全可以接受)
  • 在完成所需的 14 小时内,该速率几乎保持不变
  • 完成后_id 上的索引大小接近 2.5GB。请注意,这是我的物理 RAM 的两倍多。
  • 根据 mongostat,进程的 RES 不超过 450 MB。
  • 无交换
  • top 似乎表明 CPU 时间并没有全部花在等待磁盘上(因此大量时间花在了用户空间,大概是 snappy 代码中的 WiredTiger)

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

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

  • 每秒 80 次插入(有时较慢)
  • RES 保持在 750 MB 左右
  • top 表示几乎 100% 的 CPU 用于等待 IO
  • 当然,负载会通过屋顶。

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

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

是什么造成了巨大的性能损失?在没有新硬件的情况下如何解决?我并没有真正依赖 MongoDB,所以没有这些性能特征的替代品很好。我有一个想法,只使用可能会工作的平面文件,但我不想编写所有代码。

【问题讨论】:

  • _id 索引是什么样的,即什么数据类型以及数据是如何分布的?
  • 就是这样:docs.mongodb.org/manual/reference/object-id(MongoDB 开箱即用)
  • 好吧,ObjectIds 是单调地创建的,并且不断增加。这意味着使索引保持最新非常容易,因为可以这么说,我们总是一边写一边写。当然,B-Tree 需要时不时地进行平衡,但是与将随机值(名称)插入到散布在磁盘周围且无法保存在 RAM 中的索引中相比,这应该会导致更少磁盘读取。如果主内存引用需要 100 秒 (gist.github.com/hellerbarde/2843375),单次磁盘读取大约需要 20 周时间,仅此一项就可能导致问题。
  • @mnemosyn 你可以扩展它并将其添加为答案然后我会接受吗?我实际上认为它们是故意随机帮助分片的,但我错了。

标签: performance mongodb indexing bulkinsert


【解决方案1】:

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

  1. 性能抖动:有时,B-Tree 存储桶可能已满,需要拆分存储桶,因此比“简单”插入操作要多得多
  2. 插入目标必须随时可用

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

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

另外,当bucket被一个单调递增的值填满时,MongoDB会在bucket填满90%时拆分;使用随机插入,分裂会更早发生,为 50%,因此在这种情况下,树会更“密集”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-07
    • 2012-06-09
    • 2012-09-04
    • 1970-01-01
    • 2022-01-07
    相关资源
    最近更新 更多