在进行聚合时,MongoDB似乎选择了错误的索引

时间:2015-03-27 06:21:32

标签: mongodb aggregation-framework

在Amazon EC2上运行的测试mongodb(版本3.0.1)(3.14.33-26.47.amzn1.x86_64,t2.medium:2 vcpus,4G mem)。

收集“access_log”(约40,000,000条记录,每天1,000,000条记录),以及一些索引:

...

db.access_log.ensureIndex({ visit_dt: 1, 'username': 1 })

db.access_log.ensureIndex({ visit_dt: 1, 'file': 1 })
...

当执行以下“聚合”时,它非常慢(需要几个小时):

db.access_log.aggregate([
    { "$match": { "visit_dt": { "$gte": ISODate('2015-03-09'), "$lt": ISODate('2015-03-11') } } },
    { "$project": { "file": 1,  "_id": 0 } },
    { "$group": { "_id": "$file", "count": { "$sum": 1 } } },
    { "$sort": { "count": -1 } }
])

此聚合的所有字段 在第二个索引中都是包含({visit_dt:1,'file':1},即“visit_dt_1_file_1”)。< / p>

所以我很困惑,为什么mongodb不使用这个索引,而是另一个。

在解释计划时,总是得到以下信息,我根本不了解。

你可以帮忙吗?非常感谢!

> db.access_log.aggregate([
...     { "$match": { "visit_dt": { "$gte": ISODate('2015-03-09'), "$lt": ISODate('2015-03-11') } } },
...     { "$project": { "file": 1,  "_id": 0 } },
...     { "$group": { "_id": "$file", "count": { "$sum": 1 } } },
...     { "$sort": { "count": -1 } }
... ], { explain: true } );
{
        "stages" : [
                {
                        "$cursor" : {
                                "query" : {
                                        "visit_dt" : {
                                                "$gte" : ISODate("2015-03-09T00:00:00Z"),
                                                "$lt" : ISODate("2015-03-11T00:00:00Z")
                                        }
                                },
                                "fields" : {
                                        "file" : 1,
                                        "_id" : 0
                                },
                                "queryPlanner" : {
                                        "plannerVersion" : 1,
                                        "namespace" : "xxxx.access_log",
                                        "indexFilterSet" : false,
                                        "parsedQuery" : {
                                                "$and" : [
                                                        {
                                                                "visit_dt" : {
                                                                        "$lt" : ISODate("2015-03-11T00:00:00Z")
                                                                }
                                                        },
                                                        {
                                                                "visit_dt" : {
                                                                        "$gte" : ISODate("2015-03-09T00:00:00Z")
                                                                }
                                                        }
                                                ]
                                        },
                                        "winningPlan" : {
                                                "stage" : "FETCH",
                                                "inputStage" : {
                                                        "stage" : "IXSCAN",
                                                        "keyPattern" : {
                                                                "visit_dt" : 1,
                                                                "username" : 1
                                                        },
                                                        "indexName" : "visit_dt_1_username_1",
                                                        "isMultiKey" : false,
                                                        "direction" : "forward",
                                                        "indexBounds" : {
                                                                "visit_dt" : [
                                                                        "[new Date(1425859200000), new Date(1426032000000))"
                                                                ],
                                                                "username" : [
                                                                        "[MinKey, MaxKey]"
                                                                ]
                                                        }
                                                }
                                        },
                                        "rejectedPlans" : [
  ...
                                                {
                                                        "stage" : "FETCH",
                                                        "inputStage" : {
                                                                "stage" : "IXSCAN",
                                                                "keyPattern" : {
                                                                        "visit_dt" : 1,
                                                                        "file" : 1
                                                                },
                                                                "indexName" : "visit_dt_1_file_1",
                                                                "isMultiKey" : false,
                                                                "direction" : "forward",
                                                                "indexBounds" : {
                                                                        "visit_dt" : [
                                                                                "[new Date(1425859200000), new Date(1426032000000))"
                                                                        ],
                                                                        "file" : [
                                                                                "[MinKey, MaxKey]"
                                                                        ]
                                                                }
                                                        }
                                                },
...
                                        ]
                                }
                        }
                },
                {
                        "$project" : {
                                "_id" : false,
                                "file" : true
                        }
                },
                {
                        "$group" : {
                                "_id" : "$file",
                                "count" : {
                                        "$sum" : {
                                                "$const" : 1
                                        }
                                }
                        }
                },
                {
                        "$sort" : {
                                "sortKey" : {
                                        "count" : -1
                                }
                        }
                }
        ],
        "ok" : 1
}

3 个答案:

答案 0 :(得分:1)

您可能需要阅读the docs regarding $sort performance

  

$ sort运算符在放置在管道的开头或放在$ project,$ unwind和$ group聚合运算符之前时可以利用索引。如果在$ sort操作之前发生$ project,$ unwind或$ group,则$ sort不能使用任何索引。

另外,请记住,出于某种原因,它被称为“聚合管道”。匹配后排序的位置无关紧要。所以解决方案应该非常简单:

db.access_log.aggregate([
  {
       "$match": { 
          "visit_dt": {
             "$gte": ISODate('2015-03-09'),
             "$lt": ISODate('2015-03-11')
           },
           "file": {"$exists": true }
        } 
  },
  { "$sort": { "file": 1 } },
  { "$project": { "file": 1,  "_id": 0 } },
  { "$group": { "_id": "$file", "count": { "$sum": 1 } } },
  { "$sort": { "count": -1 } }
])

当保证每个记录中都存在该字段时,可能不需要检查文件字段是否存在。这并没有伤害,因为该领域有一个索引。与附加排序相同:因为我们确保只有包含文件字段的文档进入管道,所以应该使用索引。

答案 1 :(得分:0)

您可以找到一些信息here

  

如果查询计划程序选择索引,则说明结果包括a   IXSCAN阶段。该阶段包括诸如索引键之类的信息   模式,遍历方向和索引边界。

您的explain()输出表明发生了IXSCAN,因此您的索引似乎正在按预期工作。

尝试运行aggregate命令而不进行排序或分组,您很可能会看到更好的结果 - 如果是这样,您可以将问题缩小到这些操作中的任何一个。

如果不是这种情况,您还应该在运行此查询时尝试监视系统内存。最有可能发生的事情是Mongo无法在内存中保留40,000,000条记录的索引,因此在运行查询时它会从磁盘交换索引数据(非常慢)。

答案 2 :(得分:0)

感谢@Markus W Mahlberg。

我将查询更改为:

db.access_log.aggregate([
    {
        "$match": {
            "visit_dt": {
                "$gte": ISODate('2015-03-09'),
                "$lt": ISODate('2015-03-11')
            },
        }
    },
    { "$sort": { "visit_dt": 1, "file": 1 } },
    { "$project": { "file": 1,  "_id": 0 } },
    { "$group": { "_id": "$file", "count": { "$sum": 1 } } },
    { "$sort": { "count": -1 } }
], { explain: true })

然后得到了正确的执行计划:

...
   "winningPlan" : {
           "stage" : "FETCH",
           "inputStage" : {
                   "stage" : "IXSCAN",
                   "keyPattern" : {
                           "visit_dt" : 1,
                           "file" : 1
                   },
                   "indexName" : "visit_dt_1_file_1",
                   "isMultiKey" : false,
                   "direction" : "forward",
                   "indexBounds" : {
                           "visit_dt" : [
                                   "[new Date(1425859200000), new Date(1426032000000))"
                           ],
                           "file" : [
                                   "[MinKey, MaxKey]"
                           ]
                   }
           }
   },
   "rejectedPlans" : [ ]
...

虽然它仍然有点慢,但我认为这只是因为我的CPU,Mem,Disks。

非常感谢你!