聚合卡在“waiting for primary”是因驱动缓存拓扑30秒,需配置heartbeatfrequencyms:1000、禁用拓扑缓存、显式设readpreference和maxtimems;$out/$merge受w:"majority"影响,需预建索引或拆分操作;secondary延迟超2秒会压垮primary,应检查iops与同步源;聚合超时需单独设maxtimems和allowdiskuse:true。

聚合任务卡在“waiting for primary”阶段怎么办
这不是连接断开,而是客户端在等待一个可用的主节点响应——网络抖动导致 rs.status() 返回的主节点地址短暂失效,但驱动没及时刷新视图。Node.js 驱动(如 mongodb@6.x)默认会缓存拓扑状态 30 秒,期间所有新聚合请求都可能 stuck 在这个状态。
解决方法不是加重试逻辑,而是让驱动更快感知拓扑变化:
- 显式配置
heartbeatFrequencyMS: 1000(最低支持值),替代默认 10000;注意:必须全副本集节点统一启用heartbeatIntervalMillis: 1000,否则心跳节奏错位会加剧视图不一致 - 禁用拓扑缓存:在连接字符串中加入
?appName=agg-worker&directConnection=false&maxStalenessSeconds=90,避免驱动复用过期的 secondary 列表 - 聚合语句本身加上
readPreference: "primary"和maxTimeMS: 300000,防止某次卡住拖垮整个 worker 进程
为什么 w: "majority" 会让聚合中途失败
聚合操作本身不直接受 writeConcern 影响,但如果你在聚合 pipeline 中用了 $out 或 $merge,写入目标集合时就会触发写关注检查。网络抖动期间,多数派节点可能暂时无法通信,w: "majority" 就会阻塞并超时,报错 WriteConcernFailed。
真实场景中更隐蔽的问题是:$merge 的 on 字段若没建索引,会在写入前自动建(后台),而建索引过程会独占 collection 锁——此时哪怕网络恢复,聚合也已因锁等待超时失败。
- 提前在目标集合上建好
on字段的唯一索引(带background: true) - 把
$merge拆成两步:先$out到临时集合,再用db.collection.bulkWrite()原子更新,显式控制写关注 - 绝对不要在聚合中混用
w: 1和readConcern: "majority",语义冲突会导致读到未提交中间态
从节点同步延迟如何拖慢聚合性能
聚合默认路由到 primary,但如果 pipeline 里有 $lookup 关联另一个分片或副本集,mongos 可能将部分 stage 下推到 secondary 执行——前提是该 secondary 的 optimeDate 落后不超过 secondaryDelaySecs(默认 0)。一旦抖动导致 oplog 同步延迟跳到 5 秒以上,mongos 就会拒绝把读请求发给它,转而全压到 primary,CPU 瞬间拉满。
验证方式很简单,在主节点运行:
rs.printSecondaryReplicationInfo()
如果看到某成员显示 xxx secs behind the primary,且数值持续 >2,就得干预:
- 检查该从节点磁盘 IOPS 是否打满(
Disk IOPs接近上限时,WiredTiger 刷盘跟不上) - 确认没开启
slaveDelay,且syncSourceHost指向的是低延迟节点(避免跨机房同步) - 聚合任务高峰期,临时在连接字符串加
readPreference=primary,绕过自动负载均衡
真正容易被忽略的点:聚合上下文不继承连接级配置
你给 MongoClient 设置了 socketTimeoutMS: 60000,但聚合命令本身有自己的超时控制,而且优先级更高。驱动不会把连接层 timeout 自动透传给 aggregate 命令。
这意味着:即使连接还活着,聚合也可能因单次 getMore 请求卡住而永远不返回。必须在每条聚合调用里单独声明:
db.collection.aggregate(pipeline, { maxTimeMS: 300000, allowDiskUse: true })
另外,allowDiskUse: true 不是可选项——当 pipeline 中有 $group 或多层 $sort,内存超 100MB 就强制报错,而网络抖动常伴随 GC 暂停,进一步挤压可用内存。











