关键在于明确聚合执行主体与参数:primary节点直接受理复杂聚合易oom,应改由secondary执行并设readpreference=secondary、maxtimems;或下沉至应用层流式处理;allowdiskuse仅mongos有效且需正确传参,直连副本集时无效。

避免 MongoDB 副本集执行复杂聚合触发 OOM,关键不是“在副本集上做不做聚合”,而是“谁来执行聚合、在哪执行、带什么参数执行”。副本集节点本身(尤其是 PRIMARY)若直接受到含 $sort、$group、$lookup 的聚合请求,且未加约束,极易因内存暴涨被系统 OOM killer 杀掉——这不是配置问题,是执行模型误用。
别让 PRIMARY 节点承担聚合协调工作
副本集默认没有 mongos 层,所有客户端连接直接打到某个节点(如 PRIMARY)。一旦发一个跨集合、带排序的聚合,该节点就得自己拉数据、合并、排序、分页——它既是数据提供者,又是聚合协调者,双重压力叠加。
- 确认应用连接字符串是否误用了
mongodb://host:port/db(直连单节点),而不是mongodb://mongos-host:27017/db(经路由层) - 若确实只需副本集、不用分片,也应强制让聚合在
SECONDARY上执行,并设置readPreference=secondary+maxTimeMS,避免压垮PRIMARY - 更稳妥的做法:把聚合逻辑下沉到应用层。例如用
find()流式读取 + 内存/磁盘归并排序,比让PRIMARY一次性 load 全量再$group更可控
allowDiskUse: true 必须显式传给驱动,且仅对 mongos 有效
很多人在聚合 pipeline 里写 { $sort: { ts: -1 } },然后以为加了 allowDiskUse: true 就万事大吉——但这个选项是客户端发起命令时的 top-level 参数,不是 pipeline 阶段。而且它只在 mongos 进程里起作用;直连副本集节点时,allowDiskUse 被忽略(MongoDB 会静默降级为纯内存处理)。
- 直连副本集时,
allowDiskUse: true不生效,$sort或$group超过 100MB 内存限制会直接报错errmsg: "Sort exceeded memory limit..." - 即使你用的是
mongos,也要检查驱动调用是否真的把{ allowDiskUse: true }作为命令选项传入,而非塞进pipeline或cursor字段里 - Node.js 驱动示例:
collection.aggregate(pipeline, { allowDiskUse: true, maxTimeMS: 30000 })—— 缺少allowDiskUse或写成{ cursor: { allowDiskUse: true } }都无效
大字段不投影就等于主动申请 OOM
副本集节点内存有限(尤其云主机常配 4–8GB),而一个 content 字段平均 50KB,查 1 万条就是 500MB 内存占用。哪怕你最终只 $count,只要 pipeline 中任何 stage 出现过 content: 1,数据就全被加载进内存再丢弃。
- 聚合开头第一件事就是
$project排除所有非必要字段:{ title: 1, ts: 1, _id: 0, content: 0, raw_data: 0 } - 真要统计文本长度?用
{ len: { $strLenCP: "$content" } },不加载原文本 - 避免
$lookup无条件关联大集合——如果被 lookup 的集合没走索引或没加$match下推,PRIMARY 会拉回全部文档再过滤
临时目录路径和权限经常被忽略
allowDiskUse: true 开了,但 mongos 启动时没指定 --tempPath,或指定的路径是 /tmp(很多容器里是 tmpfs 内存盘,仅 256MB),结果磁盘溢出失败,mongos 回退到内存处理,照样 OOM。
- 检查
mongos启动参数是否有--tempPath /data/mongos-tmp,且该路径挂载在真实磁盘上(df -h /data/mongos-tmp+mount | grep /data/mongos-tmp) - 确认目录可写:
sudo -u mongod touch /data/mongos-tmp/test - 日志里搜
No space left on device或Failed to create temp directory,这类错误不会返回给客户端,只会导致聚合静默变慢或失败
最易被忽略的一点:副本集场景下,根本不要依赖 mongos 的 allowDiskUse 机制——因为它不存在。真正能落地的方案只有两个:要么上分片集群并配好 mongos 和 --tempPath,要么放弃服务端聚合,改用客户端分页 + 流式处理。选哪个,取决于你愿不愿意为一次聚合,多维护一层基础设施。











