图 1:本文封面,WiredTiger 的写入路径三段式。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料整理,与任何公司的当前招聘无关。「MongoDB 的存储特性与内部原理」这类题,面试官真正想听的是你能不能以 WiredTiger 为骨架讲清三件事:写入路径怎么走、并发怎么控、磁盘怎么省。以下机制描述均以 MongoDB 官方文档口径为准。
一句话结论:WiredTiger 用「内存缓存吸收写、journal 日志保崩溃、checkpoint 定一致」的三段式写入路径,用 MVCC 加文档级并发换掉旧引擎的集合级锁,再用压缩拿 CPU 换 I/O。
写入路径:缓存、日志、检查点。
写请求先进入内存里的 B-tree 缓存页,页面被标脏,此时数据文件一个字节都没动。与此同时,每次写操作追加一条 journal 记录——先写日志的 WAL 套路,journal 默认用 snappy 压缩,缓冲的记录默认每 100 毫秒刷一次盘。真正批量落盘的是 checkpoint:MongoDB 把 WiredTiger 配置为每 60 秒把一致性快照写进数据文件一次(默认最大检查点体积 2 GB 日志)。崩溃恢复的链条因此很清晰:数据文件保证一致到最后一个 checkpoint,更近的修改靠重放 journal 补齐。新 checkpoint 写完前旧 checkpoint 依然有效,所以即使落盘途中宕机,也能回到上一个有效检查点。
缓存管理:默认拿物理内存的一半。
WiredTiger 内部缓存的默认大小取「0.5 × (RAM − 1 GB)」和 0.256 GB 的较大者——4 GB 内存的机器就是 1.5 GB。这个缓存是整个 mongod 实例共享的,不按库或集合切分。读请求先查缓存,未命中才读数据文件并回填;脏页由后台线程择机驱逐,驱逐前必须保证对应日志已落盘。缓存之外还有第二层:操作系统的文件系统缓存,MongoDB 数据文件以压缩格式躺在里面,等于免费的二级缓存。注意一个常见误区:单机跑多个 mongod 实例时默认缓存假设会被打破,必须手工下调。缓存顶住之后还有一道闸:存储引擎对并发事务数本身有上限——读写票据(read/write tickets)各不超过 128,新版本会根据负载动态下调基线,票据排队变长正是集群过载的早期信号。
并发控制:MVCC 加文档级,这是相对 MMAPv1 最大的改动。
WiredTiger 用多版本并发控制:每个操作开始时拿到数据的一份时间点快照,读写各看各的快照,互不阻塞。写操作是文档级并发控制——多个客户端可以同时修改同一集合的不同文档。多数读写走乐观并发控制,全局、库、集合三个层级只保留意图锁,两个操作真撞上时由冲突方报 write conflict,MongoDB 透明重试。旧引擎 MMAPv1 是集合级锁:一个写入锁住整个集合,其他读写全部排队,高并发写入场景直接被打垮。MMAPv1 的思路是把数据文件直接 mmap 进虚拟内存、缓存管理完全甩给操作系统,自己不维护缓存也没有压缩;WiredTiger 则把缓存、快照、压缩全部收回引擎手里。从集合级到文档级,就是这道题的题眼。
压缩:用 CPU 换存储和 I/O。
WiredTiger 对集合数据默认开 snappy 块压缩,可选更狠的 zlib 或 zstd(时序集合默认就是 zstd);索引统一用前缀压缩——相邻索引键共享的公共前缀只存一份。官方文档明说取舍:压缩以额外 CPU 为代价最小化存储占用,对多数负载默认设置就是平衡点。还有一个细节值得提:数据在 WiredTiger 内部缓存里是未压缩的表示,落到磁盘和文件系统缓存里才是压缩格式,所以「压缩省内存」的说法只对索引前缀压缩成立,集合数据省的是磁盘和 I/O。
图 2:读写流向、三张机制卡与崩溃恢复链条。
面试现场的答题结构。
按「写入路径 → 并发模型 → 压缩与缓存 → 对比旧引擎」推进。追问预案两个。一,60 秒才 checkpoint 一次,宕机丢不丢数据:不丢已提交的写——journal 默认 100 毫秒刷盘且可配,重放补齐 checkpoint 之后的修改;真要对单条写做强保证,写关注里开 j:true 让日志落盘再返回。二,为什么缓存默认只给一半内存:另一半留给文件系统缓存和 mongod 自身,压缩的数据文件在文件系统缓存里依然是压缩态,等于花一份内存缓存两份内容。答到「checkpoint 是一致性边界,journal 负责 checkpoint 之间的窟窿」这层,持久化设计就讲透了。你在生产上调过 WiredTiger 的缓存或压缩参数吗?踩过什么坑?欢迎聊聊。