根本原因是WiredTiger访问未缓存的索引或数据页时必须触发磁盘I/O,而关键在于让热索引和热数据常驻内存;cache size过小导致频繁page fault,索引未全载入cache会使$lookup/sort/range查询性能断崖下降。

冷数据查询延迟高,根本原因不是“磁盘慢”,而是WiredTiger引擎在访问未缓存进内存的索引或数据页时,必须触发磁盘I/O——而这个过程在机械盘上可能耗时几十毫秒,在SSD上也要几百微秒。增加内存或换SSD只是手段,关键在于让热索引和热数据尽可能常驻内存。
WiredTiger cache size 设置过小导致频繁 page fault
WiredTiger默认只使用物理内存的50%(wiredTigerEngineConfig.cacheSizeGB),但实际业务中,若索引总大小超过该值,就会不断淘汰旧页、加载新页,表现为监控中 page faults/sec 持续走高、WT_CACHE_BYTES_READ 激增。
- 检查当前配置:
db.serverStatus().wiredTiger.cache,重点关注bytes currently in the cache和maximum bytes configured - 确认索引总大小:
db.collection.stats().totalSize与db.collection.stats().totalIndexSize对比 - 生产环境建议:将
cacheSizeGB设为物理内存的60%~75%,但不超过RAM - 2GB(预留系统及MongoDB自身开销) - 修改后需重启mongod,仅热更新不生效
索引未全部载入 cache 时 $lookup / sort / range 查询性能断崖式下降
复合查询(如带 $lookup 的聚合、含 $sort 的分页、时间范围扫描)依赖多个索引协同工作。哪怕主查询字段有索引,一旦关联集合的索引或排序字段索引不在 cache 中,就会触发多次随机磁盘读取。
- 典型现象:
explain("executionStats")显示nReturned小但executionTimeMillis超过1s,且totalDocsExamined与totalKeysExamined接近 - 验证方法:执行一次查询后立即查
db.serverStatus().metrics.document,观察returned和deleted是否突增(说明刚加载了大量索引页) - 临时缓解:用
db.collection.find().hint({field: 1}).limit(1)主动预热关键索引 - 长期策略:把高频关联字段、排序字段、范围查询字段的索引合并为一个覆盖索引,减少索引页数量
SSD 并不能自动解决冷查询,反而掩盖内存配置缺陷
换用SSD后,单次磁盘读延迟从20ms降到0.2ms,看似改善显著。但若每条查询仍需读取10个分散的索引页,总延迟仍达2ms——而同样操作在内存中只需0.02ms。更危险的是,SSD会让 page faults/sec 看似“可接受”,从而延缓对真正瓶颈(cache size 不足、索引设计冗余)的排查。
- SSD适用场景:日志写入密集、oplog同步压力大、临时性大批量扫描
- 不适用场景:高频点查、短连接+长查询混合负载(此时连接建立/销毁开销可能盖过IO收益)
- 验证是否真受益:对比
mongostat --host xxx中的netIn/netOut与faults比值;若faults未明显下降,说明问题不在IO子系统
最易被忽略的一点:WiredTiger cache 是按“页”管理的,而索引页大小固定为4KB。一个10MB的索引实际占用2560个页,哪怕只缺其中1页,整条查询就可能卡住——所以“索引是否全在cache中”比“cache用了多少GB”更关键。


















