You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB聚合管道性能优化:400万+用户最新轨迹追踪

问题描述

我有一个名为TrackingReport的MongoDB集合,存储用户位置追踪数据,当前已有400万+记录(预计将增长至数十亿条),需要高效获取每个用户的最新追踪记录。

当前索引

  • created_at: -1
  • user_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万+记录进行排序。

疑问

  1. 如何优化该聚合管道以提升性能?
  2. 复合索引是否有用?若有用,最优索引结构是什么?
  3. 是否有更高效的替代方案实现相同结果?

附加上下文

  • 集合规模: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 20:47:09