mongodb聚合操作默认只在primary执行,因默认readpreference=primary;需显式配置readpreference(如secondarypreferred)并确保secondary同步延迟在maxstalenessseconds内,才能分流聚合负载。

聚合操作默认只在Primary上执行
MongoDB 的聚合管道(aggregate)默认走 readPreference=primary,哪怕你连的是副本集、URI 里写了多个节点,只要没显式指定读偏好,所有 $match、$group、$lookup 都会在 Primary 上完成。Secondary 节点完全不参与——它们只负责复制 oplog 并提供只读查询能力,不承担聚合计算负载。
这意味着:即使你部署了 3 个节点的副本集,一个复杂聚合查 100 万文档,CPU 压力 100% 落在 Primary 上,Secondary 的 CPU 可能还不到 5%。
哪些聚合阶段特别吃 CPU
不是所有聚合都一样重。以下阶段在 Primary 上会显著拉升 CPU:
-
$group+$sum/$avg:需在内存中维护分组状态,数据量大时触发allowDiskUse: true也难缓解,WiredTiger 缓存压力加剧 -
$lookup(尤其非 pipeline 形式):对每个输入文档发起一次从集合的全表扫描或索引查找,I/O 和 CPU 双高 -
$unwind后接$group:数组展开后文档数暴增,中间结果集膨胀,CPU 花在序列化/反序列化和哈希分组上 - 缺失
$match前置:比如先$unwind再$match,导致处理了大量本可提前过滤掉的文档
为什么不能直接把聚合发给 Secondary
技术上可以,但有强一致性风险,且受限于同步延迟:
-
readPreference=secondary允许聚合发到从节点,但要求该 Secondary 的optimeDate与 Primary 差距在maxStalenessSeconds内(默认无上限,但生产建议设为 60);否则驱动自动跳过该节点 - 事务内聚合、刚写入就查的场景(read-after-write),Secondary 可能还没收到 oplog,结果为空或旧值
-
$lookup关联主集合时,若被关联集合在 Secondary 上尚未同步完成,可能报错或返回空 - 部分聚合阶段(如涉及
$facet或带变量的$let)在旧版本 MongoDB(≤4.2)中根本不支持 secondary 执行
怎么缓解 Primary 的聚合 CPU 压力
优先优化聚合本身,其次才考虑分流:
- 加
.read('secondaryPreferred')(Mongoose)或&readPreference=secondaryPreferred(连接字符串),让非关键报表类聚合尽量走 Secondary - 确保聚合开头就是
{$match: {...}},且字段上有高效索引;用explain("executionStats")确认是否命中IXSCAN而非COLLSCAN - 避免在聚合里做
$sort大结果集;如必须排序,前置$limit,或改用应用层排序 - 高频聚合考虑物化视图:用
db.collection.aggregate(...).forEach()定时写入汇总集合,查的时候只读静态结果 - 确认 WiredTiger 缓存大小(
wiredTigerCacheSizeGB)足够;太小会导致频繁刷盘和 page fault,间接拉高 CPU
真正棘手的不是“能不能分”,而是“该不该分”——很多聚合逻辑依赖强一致数据,盲目切到 Secondary 只会换来不可靠结果。先看 db.currentOp() 抓出慢聚合的具体 opid 和 query,再决定是调索引、改写法,还是加节点承接只读流量。











