如何缓存MongoDB聚合公共步骤 优化用户统计查询性能?
解决方案
下面是生产环境常用的成熟方案,你可以根据业务场景选择:
1. 应用层缓存(最通用,改造成本最低)
- 缓存键设计:按用户维度做唯一标识,格式参考
user:${user._id}:infos_aggregate_base,天然隔离不同用户的缓存数据。 - 缓存内容:直接序列化前置聚合输出的结果集即可,从你的explain结果看单用户返回仅585条,数据量极小,完全没有存储压力。
- 失效策略:
- 主动失效:在
Infos集合的新增/修改/删除逻辑、以及关联的tracks集合对应字段修改逻辑中加钩子,操作完成后直接删除对应用户的缓存键,保证数据更新后下次查询自动重新生成缓存。 - 兜底TTL:给缓存设置1~24小时的自动过期时间(根据业务对数据延迟的接受度调整),避免漏触发主动失效的极端情况导致数据不一致。
- 主动失效:在
- 组件选择:用户量小的场景用服务内置的LRU缓存即可,用户量大直接用Redis即可。
2. MongoDB 物化视图(无需引入额外组件)
- 你可以直接将前置聚合的结果固化为一个专门的物化视图集合,比如命名为
user_infos_aggregate_base,存储结构和你前置聚合的输出结构完全一致。 - 刷新策略:可以选择写入时触发单用户数据刷新,或者用定时任务按周期做增量/全量刷新,适合不想引入缓存组件、对数据新鲜度要求不高的场景。
- 优势是查询逻辑和原有MongoDB操作完全兼容,还可以给这个集合按需加索引,后续的过滤查询效率更高。
3. 现有聚合逻辑优化(改完可能直接不需要缓存)
从你贴的explain结果可以看出来,$lookup阶段返回了14695条数据,后续$match过滤后只剩585条,说明你现在的逻辑是先全量关联再过滤,你可以把和track相关的过滤条件下推到$lookup的内置pipeline中,关联阶段就直接过滤掉不需要的track数据,这一步改造通常就能把$lookup的耗时降到200ms以内,完全满足性能要求。
改造后的参考写法:
Infos.aggregate([ { $match: { owner: user._id } }, { $lookup: { from: 'tracks', let: { trackId: "$trackId" }, pipeline: [ { $match: { $expr: { $eq: ["$id", "$$trackId"] }, // 把你原来后面$match里和track相关的条件直接写到这里 // 比如你的原过滤是 track.status: "active",就直接加在这里 status: "active" }} ], as: 'track' }}, { $unwind: '$track' }, ])
4. 预聚合方案(性能最高,适合统计指标固定的场景)
如果你的后续统计指标是固定的,不需要灵活的自定义查询,可以直接在写入Infos或者tracks数据的时候,实时计算对应的统计值,存入单独的user_stats集合,查询的时候直接读单条记录即可,耗时基本在10ms以内,唯一的成本是需要额外开发写时计算的逻辑。
内容的提问来源于stack exchange,提问作者Yooooomi
相关产品推荐
相关产品推荐

