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.code和destination.code和serviceType属性对其进行分组。
我的聚合管道查询如下所示:
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)
但是分面性能很差。任何进一步改进这一点的想法都是最受欢迎的。
一般来说,有以下三种情况会导致查询速度变慢:
就您的查询而言,可能有一些关于查询性能的一般建议:
使用allowDiskUse在聚合管线单元,这是可能的查询将使用磁盘的某些阶段。磁盘通常是机器中最慢的部分,因此如果您可以避免这种情况,它将加快查询速度。
请注意,聚合查询仅限于 100MB 内存使用。这与您拥有的内存量无关。
该$group阶段不能使用索引,因为索引与文档在磁盘上的位置相关联。一旦聚合管道进入文档物理位置无关的$group阶段(例如阶段),就不能再使用索引。
默认情况下,WiredTiger 缓存是 RAM 的约 50%,因此 64GB 的机器将具有约 32GB 的 WiredTiger 缓存。如果发现查询很慢,可能是MongoDB需要去磁盘去获取相关文档。iostats在查询期间监视和检查磁盘利用率 % 将提供有关是否提供了足够 RAM 的提示。
一些可能的解决方案是: