存储引擎阶段的前几篇,分别讲了 B-tree 页、Cache 淘汰、journal、压缩。这一篇把它们收束到一个最实际的运维问题:当数据量超过内存时怎么办。这是 MongoDB 部署里迟早会遇到、也最容易判断错的容量拐点。

理解工作集和 Cache 的关系,能避免一个常见误区:以为「数据量 = 内存需求」。实际上,决定 MongoDB 性能的不是总数据量,而是工作集(working set)——也就是经常被访问的那部分热数据加上索引的大小。一个 1TB 的集合,如果只有 10GB 是热的,那 10GB 工作集装进内存就够了,剩下 990GB 冷数据放磁盘没问题。

先把机制边界说清楚

**工作集(working set)**是指 MongoDB 在正常业务中频繁访问的数据和索引的总大小。它不是全部数据,而是「活跃的那部分」。工作集大致包括:

  • 全部索引(或至少是热索引)。索引必须常驻内存才能高效,否则每次查询都要读盘加载索引页。
  • 频繁访问的文档。活跃用户的数据、最近订单、热内容。

工作集和 Cache 的关系,是 MongoDB 性能稳定性的核心:

  • 工作集 ⊂ Cache(工作集能装进内存):Cache 命中率高,查询稳定,淘汰压力小。理想状态。
  • 工作集 ≫ Cache(工作集超过内存):热数据频繁换入换出,大量读盘,淘汰飙升,查询延迟抖动。这就是容量超载。

健康状态 vs 超载状态

大集合与工作集:数据超过内存怎么办

把两种状态对比看,容量拐点清晰:不是「数据量超过内存」就出问题,而是「工作集超过内存」才出问题。这就是为什么有的集合几百 GB 还很稳,有的几十 GB 就抖——前者冷数据多,工作集小;后者全是热数据,工作集大。

怎么判断工作集超载

工作集超载有一组明确的监控信号:

bytes read into cache 持续高。大量从磁盘读页进 Cache,说明 Cache 命中率低,要访问的数据不在内存。这是超载最直接的信号。

pages evicted by application thread 飙高。应用线程被迫参与淘汰(正常应该是后台线程做),说明淘汰压力大到后台处理不过来,查询在被拖累。

查询延迟抖动。平时快的查询突然变慢,P99 飙升,通常是 Cache 命中率波动导致——热页被淘汰出去又读回来。

磁盘 IO 持续高。读盘频繁,IO 成为瓶颈。

判断工作集大小的实用方法:观察 Cache 稳定后的 current bytes in cache,加上频繁换入换出的量,大致能估算工作集。更精确的方法是逐步加载数据、观察 Cache 命中率拐点。但生产环境通常用监控信号判断就够了——出现上述信号,基本就是工作集超载。

超载时的四种应对

一旦确认工作集超载,应对手段按优先级和成本排列:

① 加内存:最直接但最贵

让 Cache 装下工作集,是最直接的解法。增加服务器内存,调大 wiredTiger.cacheSizeGB,让更多热页常驻内存。

优点是立竿见影,不用改架构。缺点是成本高(内存比磁盘贵得多),而且到顶——单机内存有上限,工作集继续增长就无效。适合「工作集还能装下,只是当前内存不够」的情况。但要注意:如果数据增长没有上限,加内存只是延缓,不是根治。

② 分片:把工作集分散

当单机内存装不下工作集时,分片(sharding)把数据分散到多个节点,每个节点只存一部分数据,工作集也随之分散。原来一台机器装不下的工作集,分到 N 台后每台只需 1/N。

这是应对「单机装不下的规模」的根本解,但代价是架构复杂度大增——要引入 mongos、config server、设计片键、处理跨分片查询。分片不能随便上,它适合数据量持续增长到单机无法承载的场景。后面阶段五会专门展开分片。

③ 冷热分离:让 Cache 只装热数据

很多场景的冷热边界很明显:最近 3 个月的订单是热的,更早的是冷的;活跃用户是热的,流失用户是冷的。把冷数据分离出去,Cache 只需装热数据,工作集就小了。

具体手段:

  • TTL 索引自动过期冷数据(适合日志、会话)。
  • 归档冷数据到单独的集合或单独的实例(适合历史订单、历史内容)。
  • 按时间分集合(月表、年表),老集合可以用更低配的存储。

冷热分离的判断点是「冷数据的访问频率」。如果冷数据基本不被访问,分离出去几乎无损;如果冷数据偶尔要查,分离会增加查询复杂度(要跨集合查)。

④ 优化访问:缩小实际工作集

前三者是「让 Cache 装下工作集」,这一条是「缩小工作集本身」:

  • 覆盖查询:让查询只读索引、不回表,减少要访问的数据页。
  • 投影:只返回必要字段,减少传输和反序列化。
  • 限制扫描范围:避免全表扫描污染 Cache,统计查询走从节点。
  • 紧凑文档:短字段名、去冗余,让单页装更多文档,等效缩小工作集。

这一条通常和前三种配合,不是独立解法,但能放大其他手段的效果。

工作集估算与容量规划

容量规划的核心是估算工作集,而不是总数据量。一个实用框架:

  1. 估算索引总大小(索引几乎要全部常驻内存)。db.collection.totalIndexSize()
  2. 估算热数据大小(活跃用户、最近数据)。按业务访问模式估算,通常占总数据的 5%–20%。
  3. 工作集 ≈ 索引大小 + 热数据大小。
  4. Cache 应至少是工作集的 1.5–2 倍(留淘汰缓冲)。
  5. 服务器内存 ≈ Cache + mongod 进程开销 + 操作系统 + 其他服务。

按这个框架规划,能避免「数据量看着不大,上线就抖」的尴尬。记住:90% 数据冷、10% 热,管好这 10% 才是关键。

判断框架

  • 性能拐点看工作集,不看总数据量。工作集超载才有问题。
  • 工作集 ≈ 热索引 + 热数据,不是全部数据。
  • 超载信号:bytes read into cache 高、application thread eviction 飙高、延迟抖动。
  • 应对优先级:能加内存就加(简单),冷热分离(治本但改架构),分片(终极但复杂)。
  • 优化访问(覆盖查询、投影、限扫描)始终配合使用,缩小工作集。
  • 容量规划按工作集 × 1.5–2 倍估 Cache,不要按总数据量。

这一篇是存储引擎阶段的收尾。到这里,「数据怎么在内存和磁盘之间流动」这条线就完整了。下一阶段进入复制集与高可用,回答「单点故障怎么避免」。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 MongoDB」,欢迎关注公众号 「十三Tech」。后续会按复制集、分片集群和架构选型这条线更新。

十三Tech公众号二维码