我在 MongoDB 7 中有一个集合,其中包含数百万个文档,如下所示:
{
_id: ...,
event_type: "view",
programId: "12345",
timestamp: ISODate("2024-01-01T00:00:00Z")
}
timestamp
和programId
字段上有索引。
我想创建一个聚合管道,计算给定
programId
每天(或周、月、季度等)的事件数量。这看起来是一个简单的问题,但我不知道如何让 MongoDB 以有效的方式完成它。
我尝试过以下方法:
$group
阶段,使用 $dateTrunc
字段上的 timestamp
作为 _id
$bucket
阶段,每天/每周/等都有预定义的存储桶$facet
阶段,每个所需的聚合桶都有一个[$match, $count]
管道$match
阶段在它们之前这些方法都没有达到我想要的性能。其中一些使用
IXSCAN
,而另一些最终使用 COLLSCAN
,但它们都需要一秒钟以上的时间来执行(令人惊讶的是,使用索引时它们通常比不使用索引时慢)。
如果我尝试使用简单的
[$match, $count]
管道查找单个存储桶的计数,MongoDB 将使用 COUNT_SCAN
并在 2 毫秒内返回。有没有办法在聚合多个(可能是数十个或数百个)存储桶时获得这种性能?
我意识到“按需物化视图”或类似的方法可以解决我的问题,但考虑到我想要的信息在索引中很容易获得,如果可以通过简单的管道来完成,我宁愿不引入这种复杂性。
$dateTrunc
。如果您能够更改架构,则可以预先计算要对其进行计数的值。如果您只想按天获取programId 的计数,则可以将
$dateTrunc
的结果存储在字段 day
中。如果您想更灵活一点,可以使用 $dateToParts
将时间戳拆分为具有年、月、日、小时等属性的对象。我在我的机器上使用 1000 万个文档运行测试,其架构类似于此示例:
{
"_id": 1,
"programId": "93544",
"timestamp": {
"$date": "2024-01-23T15:06:00.000Z"
},
"parts": {
"year": 2024,
"month": 1,
"day": 23
}
}
在程序 id 和零件上创建索引
programId_1_parts.year_1_parts.month_1_parts.day_1
后,我运行了以下聚合,性能非常好:
[
{
$match: {
programId: "11596",
},
},
{
$group: {
_id: {
year: "$parts.year",
month: "$parts.month",
day: "$parts.day",
},
cnt: { $sum: 1 },
},
},
]
原因是查询已被索引覆盖,因此可以从内存中提供服务。
您的里程可能会有所不同,但我希望这有助于加快速度。