mongo内存

 

🔍 WiredTiger 引擎缓存限制为 8GB,但系统进程 RSS 占用却达到了 12GB

很多人看到这种情况第一反应是“MongoDB 内存泄漏”,但实际上这往往是 正常的引擎行为可解释的额外内存占用
下面我分层详细分析这个现象的真正原因、验证方式和判断标准。


🧭 一、问题背景与现象重述

指标来源数值
WiredTiger cache (maximum bytes configured) db.serverStatus().wiredTiger.cache ≈ 8 GB
RSS (Resident Set Size) top / db.serverStatus().mem.resident ≈ 12 GB

💡 现象:MongoDB 进程实际驻留内存比引擎配置的 cache 大约多出 4 GB。


🧩 二、关键结论(核心要点)

MongoDB 的 RSS ≠ WiredTiger cache。

RSS 包含:

  1. WiredTiger 内部 cache(storage.wiredTiger.engineConfig.cacheSizeGB

  2. 索引与表元数据 cache(metadata)

  3. 压缩与解压缓冲区(zlib/zstd/snappy)

  4. Journal 写缓存(预写日志缓冲)

  5. Aggregation / $lookup / sort 等临时内存

  6. 连接缓冲区与 socket buffer

  7. 线程栈与内核映射页(mmap header / 文件描述符表)

因此,RSS 高于 cache 是预期行为。
只要增幅合理(通常不超过 cache 的 50%),就不是泄漏。


⚙️ 三、分层分析方法(MongoDB 内部命令)

我们仅使用 MongoDB 自身信息(不依赖 Linux 工具)。


1️⃣ 查看 WiredTiger 缓存状态

db.serverStatus().wiredTiger.cache

重点字段:

字段含义
maximum bytes configured 8 GB(你的设置)
bytes currently in the cache 实际 cache 占用
tracked dirty bytes in the cache 脏页
pages evicted by application threads 驱逐频繁度
pages read into cache / bytes read into cache 读放大情况

🔍 如果 bytes currently in the cache 接近上限(例如 7.9GB),说明 WiredTiger 部分是稳定的。RSS 的额外部分来自别处。


2️⃣ 检查数据库与集合占用分布

db.stats()
db.getCollectionNames().forEach(function(c){
  var s = db.getCollection(c).stats();
  print(c, Math.round(s.wiredTiger.cache["bytes currently in the cache"]/1024/1024), "MB");
});

分析哪些集合在内存中最活跃。
如果发现个别集合 cache 占用非常高,说明热点数据集中在这部分。


3️⃣ 检查连接数与游标

db.serverStatus().connections
db.serverStatus().metrics.cursor
指标含义说明
connections.current 当前连接数 每个连接约 1 MB 内存
metrics.cursor.open.total 活跃游标 长时间未关闭的 cursor 会占缓存

如果连接数高(例如 3000),就会额外吃掉 3 GB 内存。


4️⃣ 检查聚合/排序类操作的内存使用

db.currentOp({ "active": true })

关注:

  • "op": "command"

  • "command.aggregate" 存在

  • "usedDisk": false

如果有 $group$sort$lookupusedDisk=false,说明这些操作在内存中堆积中间结果,会造成瞬时内存暴涨
这些不会计入 WiredTiger cache。


5️⃣ 检查 journal / 压缩缓冲区

虽然 Mongo 没有直接字段展示 journal buffer,但可以通过:

db.serverStatus().wiredTiger.transaction

查看:

  • "transaction checkpoints" 频率(如果很低)

  • "transaction range of IDs currently pinned" 较大
    → 表示 checkpoint 延迟、脏页堆积

压缩缓冲区和 journaling 通常会占用 0.5–1 GB 额外内存。


🧮 四、WiredTiger 内部结构导致的额外内存来源

模块典型占用说明
WT cache 8 GB 配置上限
索引元数据(btree metadata) ~0.5–1 GB schema + handle cache
压缩缓冲区(Snappy / Zstd) 0.5–2 GB 压缩块缓存与线程缓冲
Journal buffer 0.5–1 GB 写日志缓存
Aggregation / Sort / Cursor 内存 动态 pipeline 或 client 端操作
连接与线程栈 0.5–1 GB 每连接 + 线程栈
内核 mmap header 与共享页 约数百 MB 文件描述符与 mmap 区头部

合计:8 GB + ~4 GB = 12 GB
完全匹配你的现象。


🔍 五、判断是否异常(是否属于“内存泄漏”)

判断点正常情况异常提示
bytes currently in the cache ≈ 设定上限 ✅ 正常 ❌ 持续超过上限(不可能)
RSS / cache 比例 < 1.5 ✅ 正常范围 ❌ >2 倍说明异常增长
pages evicted by application threads 稳定 ✅ 正常 ❌ 持续爆增
connections.current 稳定 ✅ 正常 ❌ 持续上升
db.currentOp() 长时间高内存聚合 ❌ 出现时临时暴涨  
serverStatus().mem.resident 稳定 ✅ 正常 ❌ 持续爬升说明泄漏或长生命周期对象堆积

🧰 六、可执行诊断步骤(仅用 MongoDB 命令)

你可以按顺序执行下列命令:

// 1. 全局内存摘要
db.serverStatus().mem

// 2. WiredTiger cache 占用
db.serverStatus().wiredTiger.cache

// 3. 数据库与集合 cache 分布
db.stats()
db.getCollectionNames().forEach(function(c){
  var s = db.getCollection(c).stats();
  print(c, Math.round(s.wiredTiger.cache["bytes currently in the cache"]/1024/1024), "MB");
});

// 4. 当前连接与游标
db.serverStatus().connections
db.serverStatus().metrics.cursor

// 5. 检查是否存在占用内存的聚合
db.currentOp({ "active": true, "op": "command" })

执行完你就能判断:

  • 是否热点数据或聚合导致;

  • 是否连接/游标堆积;

  • WiredTiger cache 是否真的满;

  • 额外 4 GB 是否可解释。


🧩 七、若确实异常(超出正常范围)的应对

可能原因验证方式优化措施
聚合或排序大量内存 db.currentOp() 启用 allowDiskUse:true
连接数暴涨 db.serverStatus().connections 启用连接池 / 限制连接
缓存配置过小,导致 page 频繁驱逐 pages evicted 提高 cacheSizeGB
checkpoint 延迟导致脏页堆积 transaction 状态 调整 checkpoint=(wait=60)
压缩缓冲区膨胀 无直接指标 换算法(Snappy→Zstd)或更新 Mongo 版本
游标未关闭 metrics.cursor.open.total 确保客户端关闭 cursor

✅ 八、总结(面试/生产解答版)

结论说明
RSS > WiredTiger cache 是正常现象 因为 RSS 包含其他内存区
一般情况下 1.3~1.6 倍属正常 例如 cache 8G → RSS 12G
可通过 serverStatus() 内部分析验证 不需系统工具
异常增长常来自聚合、连接、压缩缓冲或 journaling 可用 currentOpconnections 验证
若 RSS 超 cache 2 倍以上且长期不回落 可能存在内存泄漏或长生命周期对象问题

 

posted on 2025-11-07 16:10  吃草的青蛙  阅读(0)  评论(0)    收藏  举报

导航