应优先排查读偏好未生效和writeconcern被隐式覆盖问题,再检查secondary同步延迟、慢操作及journal与oplog磁盘分离;仅当持续写入超5k ops/sec且无法通过bulkwrite+w:1+io分离缓解时,才评估分片。

主节点写入压力过大,不是靠“加机器”或“调参数”硬扛,而是要拆开看:到底是写入量真大,还是读流量误打到主节点,又或是副本同步本身拖垮了主节点。
为什么读请求会跑到主节点上
很多应用默认用 readPreference=primary(这是驱动的默认行为),哪怕你配了副本集,所有读也全打在主节点上。主节点一边要处理写、一边要回响应、还要往 oplog 里写日志、还要给从节点发复制流——四件事挤在同一颗 CPU 和同一块磁盘上。
- 检查连接字符串或驱动初始化代码里有没有显式设置
readPreference,没设就等于primary - 用
db.runCommand({ replSetGetStatus: 1 })看各节点状态,确认 secondary 确实在线且optimeDate落后 - 查
mongostat的netIn/netOut,如果主节点网络流量明显高于从节点,基本就是读没分流
bulkWrite + w:1 是写入降压最直接的组合
大批量写入卡住,80% 情况下不是磁盘慢,而是 writeConcern 在等多数节点确认。比如设了 w: "majority",但某个从节点延迟高或临时断连,整个 bulkWrite 就卡在那儿等满 30 秒超时。
- 对日志、埋点、监控类数据,必须显式传
{ writeConcern: { w: 1 } },只等主节点落盘即可 - 别依赖配置文件里的全局默认值,驱动层可能被覆盖;
w: 1下 WiredTiger journal 已保障崩溃恢复,延迟通常 - 用
bulkWrite替代insertMany,并设ordered: false:单条失败不中断其余,驱动能更快释放连接 - 批量大小控制在 100–500 条/批;太小浪费网络,太大触发 WiredTiger 内存压力或超时
分片不是万能解药,先确认是否真需要
分片解决的是“单节点存储或计算撑不住”的问题,不是“主节点读太多”的问题。盲目上分片,反而把简单问题变复杂:路由层(mongos)引入额外跳转,跨分片事务性能差,运维成本翻倍。
- 先跑
rs.printSecondaryReplicationInfo(),看从节点 oplog 落后是否 > 30s;如果落后严重,说明复制链路本身已瓶颈,分片前得先修它 - 执行
db.currentOp({ secs_running: { $gt: 10 } }),确认慢操作是不是集中在某几个集合(比如没索引的count({})),这类问题优化索引比上分片快十倍 - 只有当单副本集写入持续 > 5k ops/sec 且无法通过
bulkWrite+w:1+IO 分离缓解时,才该评估分片;此时分片键选型比部署更重要
journal 和 oplog 磁盘共用是隐形杀手
journal 是高频小块写,oplog 是顺序追加写,两者 IO 模式冲突。如果都落在同一块 SATA SSD 上,wt:log 延迟飙升、iowait 超 30%,写入吞吐直接腰斩。
-
storage.journal.path必须和storage.dbPath分开挂载,且物理磁盘不同(例如 NVMe 设备分离) - journal 目录建议用 XFS 文件系统,挂载时加
nobarrier(仅限带电容保护的 SSD) - 不要调
storage.journal.commitIntervalMs到低于 50ms;小于 30ms 会导致 journal 日志碎片化,反而拖慢刷盘 - oplogSize 至少容纳 24 小时变更量,否则从节点追不上,主节点写入会被阻塞
真正容易被忽略的,是“读偏好没生效”和“writeConcern 被隐式覆盖”这两点。它们不会报错,但会让所有优化努力白费——流量还在主节点上,写入还在等多数确认,而你却在调缓存大小或换磁盘型号。











