Mongo DB 聚合组性能

use*_*527 2 c# mongoose mongodb node.js mongodb-query

我对 mongo DB 还很陌生,正在为我们的一个应用程序进行试验。我们正在尝试实现 CQRS 和查询部分,我们正在尝试使用 node.js 和命令部分,我们正在通过 c# 实现。

我的一个集合中可能有数百万个文档。我们会有一个scenarioId字段,每个场景可以有大约 200 万条记录。

我们的用例是比较这两个场景数据,并对场景的每个字段进行一些数学运算。例如,每个场景都可以有一个属性avgMiles,我想计算这个属性的差异,用户应该能够过滤这个差异值。由于我的设计是将两个场景数据保存在单个集合中,因此我尝试按场景 ID 进行分组并进一步对其进行投影。

我的文档示例结构如下所示。

{ 
    "_id" : ObjectId("5ac05dc58ff6cd3054d5654c"), 
    "origin" : {
        "code" : "0000", 
    }, 
    "destination" : {
        "code" : "0001", 
    }, 
    "currentOutput" : {
        "avgMiles" : 0.15093020854848138, 
    },
    "scenarioId" : NumberInt(0), 
    "serviceType" : "ECON"
}
Run Code Online (Sandbox Code Playgroud)

当我分组时,我会根据origin.codedestination.codeserviceType属性对其进行分组。

我的聚合管道查询如下所示:

  db.servicestats.aggregate([{$match:{$or:[{scenarioId:0}, {scenarioId:1}]}},
    {$sort:{'origin.code':1,'destination.code':1,serviceType:1}},
    {$group:{
      _id:{originCode:'$origin.code',destinationCode:'$destination.code',serviceType:'$serviceType'},
          baseScenarioId:{$sum:{$switch: {
                branches: [
                  {
                    case: { $eq: [ '$scenarioId', 1] },
                    then: '$scenarioId'
                  }],
                default: 0
                  }
        }},
        compareScenarioId:{$sum:{$switch: {
                branches: [
                  {
                    case: { $eq: [ '$scenarioId', 0] },
                    then: '$scenarioId'
                  }],
                default: 0
                  }
        }},
            baseavgMiles:{$max:{$switch: {
                branches: [
                  {
                    case: { $eq: [ '$scenarioId', 1] },
                    then: '$currentOutput.avgMiles'
                  }],
                default: null
                  }
        }},
        compareavgMiles:{$sum:{$switch: {
                branches: [
                  {
                    case: { $eq: [ '$scenarioId', 0] },
                    then: '$currentOutput.avgMiles'
                  }],
                default: null
                  }
        }}
    }
    },
    {$project:{scenarioId:
      { base:'$baseScenarioId',
        compare:'$compareScenarioId'
      },
    avgMiles:{base:'$baseavgMiles', comapre:'$compareavgMiles',diff:{$subtract :['$baseavgMiles','$compareavgMiles']}}
      } 
    },
    {$match:{'avgMiles.diff':{$eq:0.5}}},
    {$limit:100}
    ],{allowDiskUse: true} )
Run Code Online (Sandbox Code Playgroud)

我的小组管道阶段将有 400 万个文档。您能否建议我如何提高此查询的性能?

我在 group by 条件下使用的字段上有一个索引,并且我添加了一个排序管道阶段来帮助 group by 更好地执行。

任何建议都是最受欢迎的。

由于 group by 在我的情况下不起作用,我使用 $lookup 实现了左外连接,查询如下所示。

    db.servicestats.aggregate([
{$match:{$and :[ {'scenarioId':0}
  //,{'origin.code':'0000'},{'destination.code':'0001'}
  ]}},
//{$limit:1000000},
{$lookup: { from:'servicestats',
  let: {ocode:'$origin.code',dcode:'$destination.code',stype:'$serviceType'},
  pipeline:[
  {$match: {
                  $expr: { $and:
                       [
                         { $eq: [ "$scenarioId", 1 ] },
                         { $eq: [ "$origin.code",  "$$ocode" ] },
                         { $eq: [ "$destination.code",  "$$dcode" ] },
                         { $eq: [ "$serviceType",  "$$stype" ] },
                       ]
                    }

              }
  },
  {$project: {_id:0, comp :{compavgmiles :'$currentOutput.avgMiles'}}},
  { $replaceRoot: { newRoot: "$comp" } }
  ],
  as : "compoutputs"
}},
{
          $replaceRoot: {
             newRoot: {
                $mergeObjects:[
                   {
                      $arrayElemAt: [
                         "$$ROOT.compoutputs",
                         0
                      ]
                   },
                   {
                      origin: "$$ROOT.origin",
                      destination: "$$ROOT.destination",
                      serviceType: "$$ROOT.serviceType",
                      baseavgmiles: "$$ROOT.currentOutput.avgMiles",
                      output: '$$ROOT'
                   }
                ]
             }
          }
       },
       {$limit:100}
])  
Run Code Online (Sandbox Code Playgroud)

以上查询性能良好,70ms返回。

但在我的场景中,我需要实现一个完整的外部连接,我知道 mongo 目前不支持,并使用 $facet 管道实现,如下所示

    db.servicestats.aggregate([
{$limit:1000},
{$facet: {output1:[
  {$match:{$and :[ {'scenarioId':0}
  ]}},
{$lookup: { from:'servicestats',
  let: {ocode:'$origin.code',dcode:'$destination.code',stype:'$serviceType'},
  pipeline:[
  {$match: {
                  $expr: { $and:
                       [
                         { $eq: [ "$scenarioId", 1 ] },
                         { $eq: [ "$origin.code",  "$$ocode" ] },
                         { $eq: [ "$destination.code",  "$$dcode" ] },
                         { $eq: [ "$serviceType",  "$$stype" ] },
                       ]
                    }

            }
  },
  {$project: {_id:0, comp :{compavgmiles :'$currentOutput.avgMiles'}}},
  { $replaceRoot: { newRoot: "$comp" } }
  ],
  as : "compoutputs"
}},
//{
//          $replaceRoot: {
//             newRoot: {
//                $mergeObjects:[
//                   {
//                      $arrayElemAt: [
//                         "$$ROOT.compoutputs",
//                         0
//                      ]
//                   },
//                   {
//                      origin: "$$ROOT.origin",
//                      destination: "$$ROOT.destination",
//                      serviceType: "$$ROOT.serviceType",
//                      baseavgmiles: "$$ROOT.currentOutput.avgMiles",
//                      output: '$$ROOT'
//                   }
//                ]
//             }
//          }
//       }
  ],
  output2:[
    {$match:{$and :[ {'scenarioId':1}
  ]}},
{$lookup: { from:'servicestats',
  let: {ocode:'$origin.code',dcode:'$destination.code',stype:'$serviceType'},
  pipeline:[
  {$match: {
                  $expr: { $and:
                       [
                         { $eq: [ "$scenarioId", 0 ] },
                         { $eq: [ "$origin.code",  "$$ocode" ] },
                         { $eq: [ "$destination.code",  "$$dcode" ] },
                         { $eq: [ "$serviceType",  "$$stype" ] },
                       ]
                    }

            }
  },
  {$project: {_id:0, comp :{compavgmiles :'$currentOutput.avgMiles'}}},
  { $replaceRoot: { newRoot: "$comp" } }
  ],
  as : "compoutputs"
}},
//{
//          $replaceRoot: {
//             newRoot: {
//                $mergeObjects:[
//                   {
//                      $arrayElemAt: [
//                         "$$ROOT.compoutputs",
//                         0
//                      ]
//                   },
//                   {
//                      origin: "$$ROOT.origin",
//                      destination: "$$ROOT.destination",
//                      serviceType: "$$ROOT.serviceType",
//                      baseavgmiles: "$$ROOT.currentOutput.avgMiles",
//                      output: '$$ROOT'
//                   }
//                ]
//             }
//          }
//       },
       {$match :{'compoutputs':{$eq:[]}}}

  ]
  }
}




       ///{$limit:100}
])
Run Code Online (Sandbox Code Playgroud)

但是分面性能很差。任何进一步改进这一点的想法都是最受欢迎的。

kev*_*adi 7

一般来说,有以下三种情况会导致查询速度变慢:

  1. 查询没有索引,不能有效地使用索引,或者模式设计不是最优的(例如高度嵌套的数组或子文档),这意味着 MongoDB 必须做一些额外的工作才能获得相关数据。
  2. 查询正在等待一些缓慢的事情(例如从磁盘获取数据,将数据写入磁盘)。
  3. 配置不足的硬件。

就您的查询而言,可能有一些关于查询性能的一般建议:

  • 使用allowDiskUse在聚合管线单元,这是可能的查询将使用磁盘的某些阶段。磁盘通常是机器中最慢的部分,因此如果您可以避免这种情况,它将加快查询速度。

  • 请注意,聚合查询仅限于 100MB 内存使用。这与您拥有的内存量无关。

  • $group阶段不能使用索引,因为索引与文档在磁盘上的位置相关联。一旦聚合管道进入文档物理位置无关的$group阶段(例如阶段),就不能再使用索引。

  • 默认情况下,WiredTiger 缓存是 RAM 的约 50%,因此 64GB 的机器将具有约 32GB 的 WiredTiger 缓存。如果发现查询很慢,可能是MongoDB需要去磁盘去获取相关文档。iostats在查询期间监视和检查磁盘利用率 % 将提供有关是否提供了足够 RAM 的提示。

一些可能的解决方案是:

  • 提供更多 RAM,以便 MongoDB 不必经常访问磁盘。
  • 重新设计架构以避免在文档中出现大量嵌套的字段或多个数组。
  • 调整文档模式,使您更容易查询其中的数据,而不是根据您认为数据的存储方式来调整模式(例如,避免关系数据库设计模型中固有的大量规范化)。
  • 如果您发现已达到单台机器的性能限制,请考虑分片以水平扩展查询。但是,请注意,分片是一种需要仔细设计和考虑的解决方案。