requestedAtTimestamp_-1
上有一个索引,但查询经常需要超过 5 秒,Mongo Atlas 对我们大喊“The ratio of documents scanned to returned exceeded 1000.0
”。
是否有其他索引可以帮助加快此聚合速度?
{
"$match": {
"requestedAtTimestamp": {
"$gte": 1730419200000
}
}
},
{
"$sort": {
"requestedAtTimestamp": 1
}
},
{
"$addFields": {
"requestedAt": {
"$toDate": "$requestedAtTimestamp"
}
}
},
{
"$project": {
"user": 1,
"requestedAt": 1,
"y": {
"$year": "$requestedAt"
},
"m": {
"$month": "$requestedAt"
},
"d": {
"$dayOfMonth": "$requestedAt"
},
"h": {
"$hour": "$requestedAt"
},
"x1": 1,
"x2": 1
}
},
{
"$group": {
"_id": {
"user": "$user",
"y": "$y",
"m": "$m",
"d": "$d",
"h": "$h"
},
"x1": {
"$sum": {
"$cond": [
"$x1",
1,
0
]
}
},
"x2": {
"$sum": {
"$cond": [
"$x2",
1,
0
]
}
},
"total": {
"$sum": 1
}
}
}
Operation Execution Time 8.29 s
Examined:Returned Ratio 6,169.51
Keys Examined 623,121
Docs Returned 101
Docs Examined 623,121
Num Yields 693
Response Length 16,593
"planSummary": "IXSCAN { requestedAtTimestamp: -1 }",
"planningTimeMicros": 1520,
"cursorid": 7596442613226102117,
"keysExamined": 623121,
"docsExamined": 623121,
"numYields": 693,
"nreturned": 101,
"queryHash": "08BAB40F",
"planCacheKey": "AEE30401",
"queryFramework": "classic",
"reslen": 16593,
"locks": {
"FeatureCompatibilityVersion": {
"acquireCount": {
"r": 733
}
},
"Global": {
"acquireCount": {
"r": 733
}
}
},
"readConcern": {
"level": "local",
"provenance": "implicitDefault"
},
"writeConcern": {
"w": "majority",
"wtimeout": 0,
"provenance": "implicitDefault"
},
"storage": {
"data": {
"bytesRead": 2950570755,
"timeReadingMicros": 3032242
},
"timeWaitingMicros": {
"cache": 2352
}
},
"cpuNanos": 5626765637,
"remote": "3.238.26.30:53820",
"protocol": "op_msg",
"durationMillis": 8292,
"v": "7.0.15",
"isTruncated": false
文档格式
{
"_id": {
"$oid": "673601b421ea2eec217ce968"
},
"__v": {
"$numberInt": "0"
},
"lastUpdatedTimestamp": {
"$numberDouble": "1731592626011.0"
},
"requestedAtTimestamp": {
"$numberDouble": "1731592624752.0"
},
"x1": "sadasdasdadasdasdasdasdasdasdasdasdasdasdasdsad",
"x2": "0x7605ffddabf72cb9fa7ebba35c9c252d5cb4064cc04a314fe34ad2eea4a19c2bff90df2e1b6228ccd136a56b49c1d4e7dde039e94ccf676bbfa00e0ea216e681",
}
要检查此管道的性能,请考虑每个阶段正在做什么:
{"$match": {
"requestedAtTimestamp": {"$gte": 1730419200000}
}},
根据计划总结,本阶段使用
{ requestedAtTimestamp: -1 }
上的索引。 由于排序阶段,它实际上会反向使用该索引,但这里完成的工作是相同的:
1730419200000
{"$sort": {"requestedAtTimestamp": 1}},
这个阶段是完全没有必要的,因为管道中稍后会有一个分组阶段,并且即使输入已排序, $group 也不能保证输出文档的顺序。 但是,由于之前的 $match 使用了该字段上的索引,因此文档已按排序顺序获取,因此无论如何,此阶段实际上是无操作的。
{"$addFields": {"requestedAt": { "$toDate": "$requestedAtTimestamp"}}},
这会将包含双精度值的
requestedAtTimestamp
字段转换为 BSON 日期时间。
根据 BSON 规范,双精度值存储为
0x01 + field name + 64-bit double value
UTC 日期时间是
0x09 + field name + unsigned 64-bin integer millisecond since epoch
所以这里没有太大区别,只是 double 到 int 的转换。
{
"$project": {
"user": 1,
"requestedAt": 1,
"y": {"$year": "$requestedAt"},
"m": {"$month": "$requestedAt"},
"d": {"$dayOfMonth": "$requestedAt"},
"h": {"$hour": "$requestedAt"},
"x1": 1,
"x2": 1
}},
此阶段将删除除 _id、user、requestedAt、x1 和 x2 之外的所有原始字段。 这里最重要的是它将根据requestedAt 字段计算年、月、日和小时。 各一别。 由于该管道检查了 623,121 个文档,这意味着它将毫秒数转换为日期 4 次,总共进行了 2,494,484 次转换。 另请注意,如果不指定时区,这些转换将基于 UTC。
{"$group": {
"_id": {
"user": "$user",
"y": "$y",
"m": "$m",
"d": "$d",
"h": "$h"
},
"x1": {"$sum": {"$cond": ["$x1",1,0]}},
"x2": {"$sum": {"$cond": ["$x2",1,0]}},
"total": {"$sum": 1}
}}
此阶段按小时累积一些值。 这是唯一的阻塞阶段。 在该阶段处理完“所有”输入文档之前,无法返回第一个结果文档。
这里需要注意的是,使用非布尔字段值作为 $cond 条件可能会产生意想不到的结果,例如 ""
为 true。
这里最大的改进是减少 $project 阶段完成的工作量。
一些想法:
使用 $dateToString 一次性投影年、月、日和小时,因此每个文档仅进行 1 次计算,而不是 4 次。也许
$dateToString: {
date: {$toDate: "$requestedAtTimestamp"},
format: "%Y-%m-%dT%H",
timezone: "UTC"
}}
_id:{
user: "$user",
hour: {$divide:["$requestedAtTimestamp",3600000]}
}
这会将日期划分为小时,而不需要计算年、月或日。 在after
分组中,使用项目将其转换为年、月、日和小时,这样这些计算就会完成数百次,而不是数十万次。