MongoDB聚合管道性能优化:400万+用户最新轨迹追踪
问题描述
我有一个名为TrackingReport的MongoDB集合,存储用户位置追踪数据,当前已有400万+记录(预计将增长至数十亿条),需要高效获取每个用户的最新追踪记录。
当前索引
created_at: -1user_id: 1
当前聚合管道
[ { $sort: { created_at: -1 } }, { $group: { _id: "$user_id", latestTracking: { $first: "$$ROOT" } } }, { $project: { _id: 0, id: "$_id", name: "$latestTracking.user.name", code: "$latestTracking.user.code", phone: "$latestTracking.user.phone", propic_url: "$latestTracking.user.propic_url", latest_tracking_date: "$latestTracking.created_at", latest_tracking: { user_id: "$latestTracking.user_id", lat: "$latestTracking.lat", long: "$latestTracking.long", address: "$latestTracking.address", battery_percent: "$latestTracking.battery_percent", created_at: "$latestTracking.created_at", updated_at: "$latestTracking.updated_at" } } }, { $sort: { latest_tracking_date: -1 } }, { $skip: 0 }, { $limit: 15 } ]
性能问题
初始的$sort阶段导致严重性能问题,管道需要在按user_id分组前对400万+记录进行排序。
疑问
- 如何优化该聚合管道以提升性能?
- 复合索引是否有用?若有用,最优索引结构是什么?
- 是否有更高效的替代方案实现相同结果?
附加上下文
- 集合规模:400万+记录(将增长至数十亿)
- 需求:带分页的每个用户最新追踪记录
- 当前瓶颈:初始排序操作
解决方案
1. 聚合管道优化
直接移除初始的全集合$sort阶段,改用按用户分组后定位最新记录的逻辑,避免对全量数据排序。调整后的管道可结合索引快速定位每个用户的最新数据,而非先全局排序。
优化后的管道示例:
[ { $group: { _id: "$user_id", latestCreatedAt: { $max: "$created_at" }, latestTracking: { $push: "$$ROOT" } } }, { $project: { _id: 0, user_id: "$_id", latestTracking: { $filter: { input: "$latestTracking", cond: { $eq: ["$$this.created_at", "$latestCreatedAt"] } } }, latest_tracking_date: "$latestCreatedAt" } }, { $unwind: "$latestTracking" }, { $project: { id: "$user_id", name: "$latestTracking.user.name", code: "$latestTracking.user.code", phone: "$latestTracking.user.phone", propic_url: "$latestTracking.user.propic_url", latest_tracking_date: 1, latest_tracking: { user_id: "$latestTracking.user_id", lat: "$latestTracking.lat", long: "$latestTracking.long", address: "$latestTracking.address", battery_percent: "$latestTracking.battery_percent", created_at: "$latestTracking.created_at", updated_at: "$latestTracking.updated_at" } } }, { $sort: { latest_tracking_date: -1 } }, { $skip: 0 }, { $limit: 15 } ]
2. 复合索引设计
复合索引非常关键,最优结构是{ user_id: 1, created_at: -1 }。该索引可让MongoDB快速定位每个用户的所有记录,并按时间倒序排列,直接取第一条即为最新记录,彻底规避全集合排序操作。
创建索引命令:
db.TrackingReport.createIndex({ user_id: 1, created_at: -1 })
3. 更高效的替代方案
方案一:使用$setWindowFields(MongoDB 5.0+支持)
利用窗口函数按user_id分组,给每个用户的记录按created_at倒序排名,筛选排名为1的记录,可直接复用上述复合索引,性能最优:
[ { $setWindowFields: { partitionBy: "$user_id", sortBy: { created_at: -1 }, output: { rowNumber: { $rowNumber: {} } } } }, { $match: { rowNumber: 1 } }, { $project: { _id: 0, id: "$user_id", name: "$user.name", code: "$user.code", phone: "$user.phone", propic_url: "$user.propic_url", latest_tracking_date: "$created_at", latest_tracking: { user_id: "$user_id", lat: "$lat", long: "$long", address: "$address", battery_percent: "$battery_percent", created_at: "$created_at", updated_at: "$updated_at" } } }, { $sort: { latest_tracking_date: -1 } }, { $skip: 0 }, { $limit: 15 } ]
方案二:预计算最新记录
若对实时性要求不高,可定期(如每分钟)运行后台任务,将每个用户的最新追踪记录同步到单独集合(如UserLatestTracking),查询时直接从该小集合分页获取,性能远超聚合查询,适合数十亿级数据规模。
同步逻辑示例:
db.TrackingReport.aggregate([ { $sort: { user_id: 1, created_at: -1 } }, { $group: { _id: "$user_id", latest: { $first: "$$ROOT" } } }, { $merge: { into: "UserLatestTracking", whenMatched: "replace", whenNotMatched: "insert" } } ])
查询示例:
db.UserLatestTracking.find() .sort({ "latest.created_at": -1 }) .skip(0) .limit(15) .projection({ _id: 0, id: "$_id", name: "$latest.user.name", code: "$latest.user.code", phone: "$latest.user.phone", propic_url: "$latest.user.propic_url", latest_tracking_date: "$latest.created_at", latest_tracking: { user_id: "$latest.user_id", lat: "$latest.lat", long: "$latest.long", address: "$latest.address", battery_percent: "$latest.battery_percent", created_at: "$latest.created_at", updated_at: "$latest.updated_at" } })
内容的提问来源于stack exchange,提问作者Komol Chandra Devnath
相关产品推荐
相关产品推荐

