MongoDB 聚合框架性能在数百万个文档中变慢

标签 mongodb indexing mapreduce aggregation-framework

背景

我们的系统是运营商级的并且非常健壮,它已经过负载测试,每秒可以处理 5000 个事务,并且对于每个事务,一个文档被插入到一个 MongoDB 集合中(在这个应用程序中没有更新或查询,它是 write-只要)。 这相当于每天大约 700 毫米文档,这是我们的基准。

MongoDB 部署尚未分片,我们有 1 个副本集,其中 1 个主副本和 2 个从属副本,所有这些副本都是 ec2 上的 m2.2xlarge 类型的实例。 每个实例都由一个 1TB RAID0 strip 支持,该 strip 由 8 个卷(无 PIOPS)组成。 我们将 node-mongodb-native 驱动程序与 c++ 原生 BSON 解析器一起使用,以获得最佳的写入性能,并尝试对文档结构进行相应的建模。

注意

  • 文档很小(120 字节)
  • 文档包括一个“时间段”(h[our]、d[ay]、m[onth]、y[ear])以及“t[time]”字段
  • 我们在集合上有一个索引,可以通过“c[ustomer]”和“a”进行查询,这是一个高度随机但非唯一的标签
  • 我们研究了将数据划分为单独的集合,但在此示例中所有数据都是热的。
  • 我们也在研究预聚合,尽管这无法实时完成。

要求

  • 为了报告,我们需要计算每个月唯一“a”标签的数量,以及在任何给定时间段内按客户划分的总数
  • 对存储超过 2 小时的 9.5 毫米文档样本(完整集合)运行一份报告大约需要 60 秒。详情如下:

文档

{
  _id: ObjectID(),
  a: ‘string’,
  b: ‘string’,
  c: ‘string’ or <int>,
  g: ‘string’ or <not_exist>,
  t: ISODate(),
  h: <int>,
  d: <int>,
  m: <int>,
  y: <int>
}

索引

col.ensureIndex({ c: 1, a: 1, y: 1, m: 1, d: 1, h: 1 });

聚合查询

col.aggregate([
    { $match: { c: 'customer_1', y: 2013, m: 11 } },
    { $group: { _id: { c: '$c', y: '$y', m: '$m' }, a: { $addToSet: '$a' }, t: { $sum: 1 } } },
    { $unwind: '$a' },
    { $group: { _id: { c: '$_id.c', y: '$_id.y', m: '$_id.m', t: '$t' }, a: { $sum: 1 } } },
    { $sort: { '_id.m': 1 } },
    {
        $project: {
            _id: 0,
            c: '$_id.c',
            y: '$_id.y', 
            m: '$_id.m',
            a: 1,
            t: '$_id.t'
        }
    },
    { $group: { _id: { c: '$c', y: '$y' }, monthly: { $push: { m: '$m', a: '$a', t: '$t' } } } },
    { $sort: { '_id.y': 1 } },
    {
        $project: {
            _id: 0,
            c: '$_id.c',
            y: '$_id.y', 
            monthly: 1
        }
    },
    { $group: { _id: { c: '$c' }, yearly: { $push: { y: '$y', monthly: '$monthly' } } } },
    { $sort: { '_id.c': 1 } },
    {
        $project: {
            _id: 0,
            c: '$_id.c',
            yearly: 1
        }
    }    
]);

聚合结果

[
    {
        "yearly": [
            {
                "y": 2013,
                "monthly": [
                    {
                        "m": 11,
                        "a": 3465652,
                        "t": 9844935
                    }
                ]
            }
        ],
        "c": "customer_1"
    }
]

63181ms

聚合说明

{
        "cursor" : "BtreeCursor c_1_a_1_y_1_m_1_d_1_h_1",
        "isMultiKey" : false,
        "n" : 9844935,
        "nscannedObjects" : 0,
        "nscanned" : 9844935,
        "nscannedObjectsAllPlans" : 101,
        "nscannedAllPlans" : 9845036,
        "scanAndOrder" : false,
        "indexOnly" : true,
        "nYields" : 27,
        "nChunkSkips" : 0,
        "millis" : 32039,
        "indexBounds" : {
                "c" : [ [ "customer_1", "customer_1" ] ],
                "a" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ],
                "y" : [ [ 2013, 2013 ] ],
                "m" : [ [ 11, 11 ] ],
                "d" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ],
                "h" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ]
        }
}

问题

  1. 鉴于插入的频率很高,并且我们需要随着时间的推移执行范围聚合查询。考虑到应用程序可以在一个小时内插入 30MM 文档,时间桶是一种好的做法吗?

  2. 我们了解到 MongoDB 可以在几秒钟内查询数十亿个文档:

    • 确定我们对 9.5MM 文档的聚合查询可以在 1 秒左右返回吗?
    • 我们是否使用了正确的技术来实现这一点?如果没有,我们应该将精力集中在哪里,以便几乎立即获得报告结果?
    • 现阶段不分片是否可行?
  3. MapReduce(并行)会是更好的选择吗?

最佳答案

您想知道为什么您的聚合需要这么“长时间”。除了@Avish 在他的回答中提出的观点(您正在执行一些不必要的步骤),您还必须考虑您的物理资源以及时间的去向。

这是你“解释”的一部分:

    "n" : 9844935,
    "nscannedObjects" : 0,
    "nscanned" : 9844935,
    "scanAndOrder" : false,
    "indexOnly" : true,
    "nYields" : 27,
    "millis" : 32039,

值得注意的是,聚合耗时 32 秒(而不是 50 秒),它从不需要获取单个文档,因为它从索引本身获取所有信息。它不必进行任何内存排序。但它确实必须产生 27 次......这是为什么呢?读取器进程屈服的原因有两个 - 一个是写入等待(写入器优先,长时间运行的读取必须屈服于它们)或存在页面错误 - 所有操作在尝试访问任何数据时都必须屈服不在 RAM 中(这是为了防止进程在等待数据加载到 RAM 时阻止其他进程进行工作)。

想到的问题是:数据库冷吗?索引是否适合 RAM?是否在读取必须应对的同时发生写入?

我会检查索引是否适合 RAM,运行 the "touch" command为了确保它在 RAM 中,简化我的聚合管道不要做不必要的工作,然后再次运行它,连续运行几次,然后看看时间安排如何。

关于MongoDB 聚合框架性能在数百万个文档中变慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19982312/

相关文章:

mongodb - HBase、mongoDB、Cassandra - 小集群、小数据的开销

sql - 从文件系统到 SQL Server 索引 Word/PDF 文档

r - 为所有嵌套索引嵌套列表中的某些元素

python - 如何使 python lambda 表达式的索引静态

ruby-on-rails - 如何更有效地聚合这些数据?

javascript - 我们可以为 mongo gridfs 添加另一个值吗?

显示用户是否存在的 MongoDB 命令行(用于 puppet 'unless' 子句)

hadoop - 如何使用Hadoop折叠TB的单词?

javascript循环嵌套数组并根据另一个数组提取属性

java - 将变量发送到 Mapper 类