优化对大型索引对象的 MongoDB 聚合查询

Posted

技术标签:

【中文标题】优化对大型索引对象的 MongoDB 聚合查询【英文标题】:Optimizing MongoDB aggregate query on large Index objects 【发布时间】:2020-08-12 02:05:45 【问题描述】:

我的 MongoDb 集合中有 2000 万个对象。目前在具有 7.5Gb 内存和 40Gb 磁盘的 M30 MongoDb 实例上运行。

数据像这样存储在集合中 -


 _id:xxxxx,
 id : 1 (int),
 from : xxxxxxxx (int),
 to : xxxxxx (int),
 status : xx (int)
 .
 .
 .
 .
,


 _id:xxxxx,
 id : 2 (int),
 from : xxxxxxxx (int),
 to : xxxxxx (int),
 status : xx (int)
 .
 .
 .
 .

.
.
.
. and so on..

id 是唯一索引,from 是此集合中的索引。

我正在运行一个查询以将“to”分组并返回给我最大 id 并在给定条件下按最大 id 排序,即“来自”

$collection->aggregate([
            ['$project' => ['id'=>1,'to'=>1,'from'=>1],
            [ '$match'=> [
                        '$and'=> 
                                [ 
                                    [ 'from'=> xxxxxxxxxx],
                                    [ 'status'=> xx ],
                                ] 
                        ] 
            ],
            ['$group' => [
                        '_id' => 
                                '$to',
                                'max_revision'=>['$max' => '$id'],
                        ]
            ],
            ['$sort' => ['max_revision' => -1]],
            ['$limit' => 20],

]);

上面的查询在索引 from 上的小型数据集上运行得很好(约 2 秒),例如在集合中具有 50-100k 的相同 'from' 值。但是对于像这样的情况,例如,如果 2M 个对象具有相同的“来自”值,那么执行并给出结果需要超过 10 秒。

一个简单的例子, 案例 1 - 如果使用 from 作为 12345 执行相同的查询,则在 2 秒内运行,因为 12345 在集合中出现 50k 次。

案例 2- 如果查询以 from 作为 98765 执行,则查询需要 10 秒以上,因为 98765 在集合中出现了 2M 次。

编辑:下面的解释查询-


  "command": 
    "aggregate": "mycollection",
    "pipeline": [
      
        "$project": 
          "id": 1,
          "to": 1,
          "from": 1
        
      ,
      
        "$match": 
          "$and": [
            
              "from": 
                "$numberLong": "12345"
              
            ,
            
              "status": 22
            
          ]
        
      ,
      
        "$group": 
          "_id": "$to",
          "max_revision": 
            "$max": "$id"
          
        
      ,
      
        "$sort": 
          "max_revision": -1
        
      ,
      
        "$limit": 20
      
    ],
    "allowDiskUse": false,
    "cursor": ,
    "$db": "mongo_jc",
    "lsid": 
      "id": 
        "$binary": "8LktsSkpTjOzF3GIC+m1DA==",
        "$type": "03"
      
    ,
    "$clusterTime": 
      "clusterTime": 
        "$timestamp": 
          "t": 1597230985,
          "i": 1
        
      ,
      "signature": 
        "hash": 
          "$binary": "PHh4eHh4eD4=",
          "$type": "00"
        ,
        "keyId": 
          "$numberLong": "6859724943999893507"
        
      
    
  ,
  "planSummary": [
    
      "IXSCAN": 
        "from": 1
      
    
  ],
  "keysExamined": 1246529,
  "docsExamined": 1246529,
  "hasSortStage": 1,
  "cursorExhausted": 1,
  "numYields": 9747,
  "nreturned": 0,
  "queryHash": "29DAFB9E",
  "planCacheKey": "F5EBA6AE",
  "reslen": 231,
  "locks": 
    "ReplicationStateTransition": 
      "acquireCount": 
        "w": 9847
      
    ,
    "Global": 
      "acquireCount": 
        "r": 9847
      
    ,
    "Database": 
      "acquireCount": 
        "r": 9847
      
    ,
    "Collection": 
      "acquireCount": 
        "r": 9847
      
    ,
    "Mutex": 
      "acquireCount": 
        "r": 100
      
    
  ,
  "storage": 
    "data": 
      "bytesRead": 
        "$numberLong": "6011370213"
      ,
      "timeReadingMicros": 4350129
    ,
    "timeWaitingMicros": 
      "cache": 2203
    
  ,
  "protocol": "op_msg",
  "millis": 8548

【问题讨论】:

一些相关信息:Query Optimization - 请参阅主题选择性 Pipeline Optimization。 向问题添加解释的查询计划。 @D.SM 添加了解释查询的要点 【参考方案1】:

对于这种特定情况,mongod 查询执行器可以使用索引进行初始匹配,但不能用于排序。

如果您要重新排序和稍微修改阶段,它可以使用from:1, status:1, id:1 上的索引进行匹配和排序:

$collection->aggregate([
            [ '$match'=> [
                        '$and'=> 
                                [ 
                                    [ 'from'=> xxxxxxxxxx],
                                    [ 'status'=> xx ],
                                ] 
                        ] 
            ],
            ['$sort' => ['id' => -1]],
            ['$project' => ['id'=>1,'to'=>1,'from'=>1],
            ['$group' => [
                        '_id' => '$to',
                        'max_revision'=>['$first' => '$id'],
                      ]
            ],
            ['$limit' => 20],

]);

这样它应该能够将$match$sort 阶段组合成一个索引扫描。

【讨论】:

有道理,但这会使管道超过 100 Mb 的 ram 使用量,并最终在磁盘上运行以进行排序。仍然需要> 10秒才能执行。分析器要点 - gist.github.com/TomarAditya/30444b158613b2b967fafb11860b8664 呸,我打错了 - 在$group 之前排序时,需要在分组之前使用该字段的名称。现已编辑。此外,您需要在 from:1, status:1, id:1 上创建复合索引

以上是关于优化对大型索引对象的 MongoDB 聚合查询的主要内容,如果未能解决你的问题,请参考以下文章

005.MongoDB索引及聚合

大数据查询mongodb、聚合、单索引或复合索引

聊聊MongoDB中连接池索引事务

Mongodb:聚合排序限制查询的索引?

MongoDB使用: 条件操作,排序,索引,分页查询,聚合函数

优化 MongoDB 聚合查询性能