mongodb聚合默认不走secondary,因$lookup(本地未分片集合)、$out/$merge等阶段强制主节点执行,且需显式会话、readconcern="available"及无强制主节点阶段才可真正路由到secondary。

不能直接把 aggregate 负载“分流”到分析节点——副本集里没有“分析节点”这个角色,只有主节点(primary)和从节点(secondary),而聚合是否能路由到 secondary,取决于管道结构、读取偏好和是否启用因果一致性。
为什么 aggregate 默认不走 secondary
MongoDB 副本集的 aggregate 操作默认使用 primary 读取偏好,哪怕你显式设置了 readPreference=secondary,某些聚合阶段仍会强制回退到 primary。原因包括:
-
$lookup引用同一数据库下的未分片集合时,必须在primary上执行合并; -
$out或$merge阶段会写入数据,只能在primary运行; - 未指定
readConcern且管道含$group等状态操作时,驱动可能自动降级为primary以保证一致性; - 客户端未开启会话(
ClientSession),无法启用因果一致性保障,导致读取偏好被忽略。
让 aggregate 真正发往 secondary 的实操条件
必须同时满足以下所有条件,aggregate 请求才会实际落到 secondary 节点上:
- 连接字符串或客户端配置中明确指定
readPreference=secondary(或nearest); - 聚合管道不含任何强制要求
primary的阶段(如$out、$merge、引用本地未分片集合的$lookup); - 使用支持会话的驱动(如 Java 4.11+、Node.js 4.15+),并为每次聚合调用显式传入
ClientSession; - 设置
readConcern=available(非majority或linearizable),否则部分驱动会在secondary上拒绝执行; - 确保目标
secondary节点已追平 oplog(optimeDate接近primary),否则可能被自动剔除在读取候选之外。
示例(Node.js):
const session = client.startSession();
await db.collection('orders').aggregate([
{ $match: { status: 'shipped' } },
{ $group: { _id: '$region', total: { $sum: '$amount' } } }
], {
readPreference: 'secondary',
readConcern: { level: 'available' },
session
}).toArray();
getMore 请求会破坏 secondary 聚合的负载分流
如果聚合结果集过大(如超过 16MB),MongoDB 会返回游标 ID 并拆成多次 getMore 请求。但 getMore 必须发往最初执行 find 或 aggregate 的同一个 mongod 节点——而副本集里没有全局游标协调机制,getMore 若被负载均衡打到另一个 secondary,就会报 CursorNotFound 错误。这意味着:
- 你不能依赖四层负载均衡(如 IP Hash)来分发聚合流量,除非确保整个游标生命周期绑定单节点;
- 应用层需禁用自动重试
getMore到其他节点的逻辑(某些旧版驱动会这么做); - 更稳妥的做法是:提前预估结果集大小,用
allowDiskUse: true+ 合理batchSize控制首轮响应体积,减少getMore次数。
真正需要长期分析负载的场景,应该用独立的分析集群或变更流 + 数据湖方案,而不是在生产副本集的 secondary 上扛聚合压力——后者容易拖慢复制延迟,甚至触发选举震荡。











