嵌入式文档过大、字段冗余、索引膨胀及冷数据滞留是导致WiredTiger缓存内存压力升高的四大主因;应拆分嵌套结构、清理废弃字段、精简索引并实施TTL归档。
当一个文档内嵌大量子文档(比如课程里塞几十个视频、测验、课件),每次查询或更新该文档时,整个文档都会被加载进 WiredTiger 缓存。即使只读取其中 1 个字段,bytes currently in the cache 仍会记录完整文档大小,造成缓存污染和内存浪费。
典型表现是 db.serverStatus().wiredtiger.cache 中 bytes currently in the cache 持续接近 maximum bytes configured,但实际热数据占比很低。
lessons、quizzes),用 ObjectId 引用代替内嵌course.lectures: { $size: { $lt: 20 } }),配合应用层分页BinData)或长文本(如 Base64 编码的图片),改用文件服务 + URL 字段MongoDB 不强制 schema,但放任字段随意增减会导致同一集合中文档结构高度不一致。WiredTiger 对每个文档单独压缩和缓存,字段名重复存储、字段顺序混乱、类型混用(如 "status": "1" 和 "status": 1)都会降低压缩率,增加缓存体积。
例如,10 万条用户文档若每条多存一个未使用的 temp_metadata 字段(平均 200B),仅此一项就额外占用约 20MB 内存缓存 —— 这些数据根本不会被查询,却长期挤占 LRU 缓存空间。
db.collection.aggregate([{$project: {fields: {$objectToArray: "$$ROOT"}}}, {$unwind: "$fields"}, {$group: {_id: "$fields.k", count: {$sum: 1}}}], {allowDiskUse: true}) 统计字段分布,清理低频/废弃字段int32 或 int64,避免混用 double)collation 并预处理(如 trim、小写化),减少因大小写/空格差异导致的索引和缓存碎片索引本身也驻留在 WiredTiger 缓存中。复合索引字段越多、值越长(如索引 user.profile.bio 这种长文本字段),索引条目体积越大。更隐蔽的问题是:如果索引键包含高基数字段(如 timestamp),会导致 B-tree 分支变深、页分裂频繁,进一步放大内存驻留量。
执行 db.collection.getIndexes() 后检查每个索引的 key 和 size(可通过 db.collection.stats().indexDetails 查看),常发现 30%+ 的索引从未被 explain("executionStats") 中的 executionStages.inputStage.indexName 引用过。
explain() 命中过的索引(尤其是以 _id 开头的冗余复合索引)db.logs.createIndex({level: 1, timestamp: -1}, {partialFilterExpression: {level: {$in: ["ERROR", "WARN"]}}})
text 索引替代字段前缀索引,或用哈希字段(sha256(content))代替原始内容没有 TTL 索引或归档机制时,日志、事件、临时会话等冷数据会长期堆积。它们虽不常被查询,但只要被读过一次,就会进入缓存;而 WiredTiger 的 LRU 策略无法区分“业务冷”和“系统冷”,导致热数据被频繁踢出。
尤其在分片集群中,某些 shard 上冷数据占比超 70%,但 db.serverStatus().wiredtiger.cache 显示缓存使用率仍达 90%+,说明内存正被无效数据低效占用。
db.sessions.createIndex({createdAt: 1}, {expireAfterSeconds: 3600})
db.old_logs.renameCollection("old_logs_archived_2025_q2")
{hot: true, ttl: ISODate("...")}),配合后台 job 批量迁移