聚合查询在分片集群上易失败,因各shard本地聚合阶段默认内存上限100mb,且mongos不自动将allowdiskuse转发至shards;须在应用端调用aggregate时显式传入{allowdiskuse: true}作为第二个参数。

聚合查询在分片集群上为什么容易失败?
分片集群中执行 $group 或带 $sort 的聚合时,常见报错:Exceeded memory limit for $group, but didn't allow external sort。这不是单台 mongod 内存不足的简单问题,而是分片架构下内存限制被双重收紧:每个 shard 上的 pipeline 阶段有默认 100MB 内存上限,且协调节点(mongos)不会自动启用磁盘暂存——即使你开了 allowDiskUse,它只作用于当前连接发起的聚合,不穿透到各 shard 的本地执行阶段。
allowDiskUse: true 必须显式传给每个分片的聚合调用
mongos 不会把 allowDiskUse 自动转发给 shards;它只控制 mongos 自身是否允许将中间结果写入磁盘(极少需要)。真正起作用的是:每个 shard 在本地执行子聚合时,也得带上这个选项。但你不能手动去每台 shard 上跑命令——正确做法是,在应用端或 mongo shell 中发起聚合时,必须把 { allowDiskUse: true } 作为第二个参数传入:
db.orders.aggregate([
{ $match: { status: "shipped" } },
{ $group: { _id: "$region", total: { $sum: "$amount" } } }
], { allowDiskUse: true })
注意两点:
- 该选项必须是聚合方法的**第二个参数对象**,不是 pipeline 阶段里的字段
- 如果用驱动(如 Node.js 的 mongodb driver),也要确保传入的是 options 对象,而非拼进 pipeline 数组
- 旧版驱动(cursor.allowDiskUse() 链式调用
分片键与 $group 字段不匹配会导致全广播扫描
即使加了 allowDiskUse: true,若 $group 的 _id 字段和分片键无关(比如按 user_id 分片,却按 order_date 分组),mongos 就无法把分组逻辑下推到各 shard 并行执行,只能拉取全部数据到 mongos 做归并——这不仅慢,还极易触发 mongos 内存溢出。此时 allowDiskUse 对 mongos 无效(它不支持磁盘归并)。
优化方向很直接:
- 优先让
$group的_id包含分片键前缀,例如分片键是{ region: 1, user_id: 1 },就按{ region: "$region" }或{ region: "$region", type: "$type" }分组 - 避免在聚合开头用
$unwind后再按非分片字段分组,这几乎必然导致广播 - 用
explain("executionStats")看shards数组里每个分片的nReturned和totalDocsExamined是否接近——如果某个 shard 返回几百万文档而其他只返回几十条,说明分组没下推成功
大数据量分组时,$group 阶段仍可能在 shard 上 OOM
allowDiskUse: true 能缓解问题,但不保证解决。MongoDB 的磁盘暂存只用于单个 shard 上的本地 $group,其性能取决于 shard 本地磁盘 I/O 和临时文件路径空间。常见陷阱:
- 临时目录(
/tmp或--dbpath下的_tmp)磁盘满,聚合直接失败,错误类似:Unable to create temp file in /tmp - 分片节点未配置
storage.wiredTiger.engineConfig.directoryForIndexes: true,导致索引和临时文件争抢同一磁盘 - 聚合中用了
$addToSet或大数组字段,内存膨胀比$sum类型严重得多,即使开了磁盘也不一定能撑住
这时得换策略:把分组拆成两阶段,先用 mapReduce 或 Change Stream + 应用层预聚合,或者改用时间窗口 + TTL 集合做物化汇总。










