【问题标题】:MongoDB Aggregation PerformanceMongoDB 聚合性能
【发布时间】:2015-02-17 02:34:15
【问题描述】:

我们遇到了长时间运行(几分钟)聚合查询的问题。

收藏:

我们收集了 2.5 亿个文档,每个文档大约有 20 个字段, 集合的总大小为 110GB。

我们对“our_id”和 dtKey 字段有索引。

硬件:

记忆:

24GB RAM(6 * 4GB DIMM 1333 Mhz)

磁盘:

Lvm 11TB 由 4 个 3TB 磁盘构建而成:

  • 600MB/s 最大瞬时数据传输。

  • 7200 RPM 主轴。平均延迟 = 4.16ms

  • RAID 0

CPU:

2* E5-2420 0 @ 1.90GHz 总共 12 个内核,24 个线程。 戴尔 R420。

问题: 我们正在尝试对以下内容进行聚合查询:

db.our_collection.aggregate(
    [
        {
            "$match":
            {
                "$and":
                    [
                        {"dtKey":{"$gte":20140916}},
                        {"dtKey":{"$lt":20141217}},
                        {"our_id":"111111111"}
                    ]
            }
        },
        {
            "$project":
            {
                "field1":1,
                "date":1
            }
        },
        {
            "$group":
            {
                "_id":
                {
                    "day":{"$dayOfYear":"$date"},
                    "year":{"$year":"$date"}
                },
                "field1":{"$sum":"$field1"}
            }
        }
    ]
);

这个查询需要几分钟才能运行,当它运行时我们可以看到以下内容:

  • Mongo 当前操作产生超过 300K 次
  • 在 iostat 上,我们看到 ~100% 的磁盘利用率

这个查询完成后,它似乎在缓存中,可以在瞬间再次完成,

在为 3 – 4 个用户运行后,第一个似乎已经从缓存中换出,查询又需要很长时间。

我们测试了匹配部分的计数,发现我们有 50K 文档的用户以及 500K 文档的用户,

我们试图只得到匹配的部分:

db.pub_stats.aggregate(
    [
        {
            "$match":
            {
                "$and":
                    [
                        {"dtKey":{"$gte":20140916}},
                        {"dtKey":{"$lt":20141217}},
                        {" our_id ":"112162107"}
                    ]
            }
        }
    ]
);

而且查询似乎占用了大约 300-500M 的内存,

但运行完整查询后,似乎占用了 3.5G 内存。

问题:

  1. 为什么聚合的流水线需要这么多内存?
  2. 我们如何提高性能,使其在 HTTP 请求的合理时间内运行?

【问题讨论】:

    标签: mongodb mongodb-query


    【解决方案1】:
    1. 为什么聚合的流水线需要这么多内存?

    只需执行$match 就不必读取实际数据,它可以在索引上完成。通过投影对field1的访问,必须读取实际的文档,并且它可能也会被缓存。

    另外,分组can be expensive。通常情况下,如果你的分组阶段需要超过 100M 的内存,它应该会报错 - 你使用的是什么版本?它需要在产生之前扫描整个结果集,而 MongoDB 至少必须存储组中每个元素的指针或索引。我猜内存增加的关键原因是前者。

    1. 我们如何提高性能,使其在 HTTP 请求的合理时间内运行?

    您的dtKey 似乎对时间进行了编码,并且分组也是根据时间完成的。我会尝试利用这一事实 - 例如,通过预先计算每天的聚合和 our_id 组合 - 如果没有更多标准并且数据不再发生太大变化,这很有意义。

    否则,我会尝试将{"our_id":"111111111"} 标准移动到第一个位置,因为相等应该始终在范围查询之前。我猜聚合框架的查询优化器足够聪明,但值得一试。此外,您可能想尝试将您的两个索引转换为单个复合索引{ our_id, dtkey }。现在支持索引交集,但我不确定它的效率有多高,真的。使用内置配置文件和.explain() 分析您的查询。

    最后,MongoDB 专为大量写入使用而设计,在几毫秒内从磁盘扫描数百 GB 的数据集在计算上根本不可行。如果您的数据集大于您的 RAM,您将面临数十毫秒甚至更高、数万或数十万次的大规模 IO 延迟,因为所有必需的磁盘操作。请记住,使用随机访问,您将永远无法接近理论上的顺序磁盘传输速率。如果你不能预先计算,我想你需要更多的内存。也许 SSD 会有所帮助,但这只是猜测。

    【讨论】:

    • 索引的顺序很重要,所以它应该是db.pub_stats.ensureIndex({dtkey:1,our_id:1})
    • 是的,顺序很重要,但它应该是{our_id:1,dtKey:1},因为our_id 是一个相等条件,dtKey 在一个范围查询中。查询中的顺序应该相应地改变,这就是我在答案中写的,但可能不是很好。
    • @mnemosyn - 很好的解释
    • “我猜你需要更多的 RAM”并不能解决任何问题,因为 Mongo 的每个聚合步骤有一个烦人的 100mb 限制。
    猜你喜欢
    • 1970-01-01
    • 2019-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-18
    • 2020-01-10
    相关资源
    最近更新 更多