2. MongoDB存储引擎与持久化机制

要理解 MongoDB 的备份恢复为什么能成立、恢复到什么时间点、恢复后副本集如何重新同步,必须先看清 WiredTiger 存储引擎在磁盘上留下了哪些“证据”。MongoDB 从 3.2 起默认使用 WiredTiger 作为存储引擎,它同时维护三类关键持久化对象:数据文件(collection-*.wt / index-*.wt)、journal 日志(WiredTigerLog),以及副本集层面的 oplog(local.oplog.rs)。三者职责不同、写入时机不同,共同决定了备份的边界与恢复的精度。

一、WiredTiger 的写入路径:从内存到磁盘

WiredTiger 采用“先写日志、后写数据文件”的持久化策略。一次写操作进入引擎后,大致经历如下路径:

  1. 写入操作先进入 WT 内存中的页缓存(cache),并记录到 journal 缓冲区;
  2. 默认情况下(writeConcern: {j: true} 或副本集多数确认),journal 缓冲区按约 100ms 的周期刷盘,形成 journal 文件;
  3. 内存中的脏页由 eviction(淘汰)线程在达到阈值时刷入数据文件;
  4. 每隔 60 秒(或 journal 数据达到 2GB)触发一次 checkpoint,把当前一致的内存视图落成数据文件中的一致点,并更新元数据。

因此,journal 保证“崩溃可恢复”,checkpoint 保证“数据文件本身一致”。二者缺一不可:只有 checkpoint 没有 journal,崩溃后会丢失最近一次 checkpoint 之后的所有写入;只有 journal 没有 checkpoint,恢复时需要重放大量日志,启动极慢。

二、checkpoint、journal 与数据文件的关系

可以把 WiredTiger 的磁盘状态理解为“数据文件 = 若干历史 checkpoint 的叠加”。每个 checkpoint 在数据文件中标记一个一致点,journal 则记录该 checkpoint 之后的所有变更。恢复时,MongoDB 从最近一次有效 checkpoint 开始,重放 journal 中尚未落盘的记录,使数据文件回到崩溃前的一致状态。

需要特别注意的是:journal 只服务于单节点崩溃恢复,不参与跨节点复制。副本集节点之间的数据同步依赖的是 oplog,而不是 journal。这是备份恢复设计中极易混淆的一点。

三、oplog:副本集层面的“操作流水”

oplog 是 local 库中的固定大小 capped collection(默认约为磁盘空间的 5%,可在 1GB~50GB 之间调整)。主节点把每一次数据变更以幂等操作的形式写入 oplog,从节点拉取并重放这些操作,从而保持数据一致。oplog 记录的是逻辑操作(如 i 插入、u 更新、d 删除、c 命令),而不是物理页变更。

oplog 有两个关键属性:幂等性(同一条 oplog 重放多次结果一致)和时间窗口(由 oplog 大小和写入速率共同决定)。如果从节点落后超过 oplog 窗口,就会进入 RECOVERING 状态,需要全量重新同步。

四、三者的协同关系

下图展示了 WiredTiger 内部持久化对象与副本集 oplog 之间的层次与数据流关系:

客户端写入 insert / update / delete 主节点 Primary WiredTiger 内存页缓存 + journal 缓冲区 oplog (local.oplog.rs) 逻辑操作 · 幂等 · capped 默认约磁盘 5% 从节点 Secondary 拉取并重放 oplog Journal 日志 约 100ms 刷盘 · 崩溃恢复 Checkpoint 约 60s 或 journal 达 2GB 数据文件 collection-*.wt / index-*.wt WiredTiger.wt 元数据 崩溃恢复路径 最近 checkpoint + 重放 journal 备份一致性边界 checkpoint + oplog 时间点 图例说明 内存 / 主节点写入 journal 日志 checkpoint 数据文件 oplog 从节点同步

五、对备份恢复的直接影响

理解了上述关系,就能解释几个备份恢复中的关键结论:

  • 物理备份(如文件系统快照)必须与 checkpoint 对齐。直接复制数据文件时,如果复制过程中发生了写入,得到的文件可能处于不一致状态。正确做法是:先执行 db.fsyncLock() 锁定写入,再复制数据文件,最后 db.fsyncUnlock() 解锁。这样复制到的就是某个 checkpoint 的一致视图。
  • 逻辑备份(mongodump)不依赖 checkpoint。它通过读取集合数据生成 BSON 文件,但默认不包含 oplog 增量,因此只能恢复到备份开始时刻的状态。
  • 时间点恢复(PITR)依赖 oplog。要恢复到“昨天 14:30”这样的精确时间点,必须同时具备:一份基础全量备份 + 从该备份时间点开始的完整 oplog 片段。恢复时先还原全量备份,再用 mongorestore --oplogReplay 重放 oplog 到目标时间戳。
  • oplog 窗口决定增量备份的可行性。如果两次备份间隔超过 oplog 窗口,中间的操作已经滚出 oplog,就无法做增量恢复,只能重新全量备份。
实践提示:在规划备份策略前,先用 db.getReplicationInfo() 查看 oplog 的 timeDiff(窗口秒数)和 tFirst/tLast 时间戳。如果窗口只有几小时,而备份周期是每天一次,就必须扩大 oplog 或缩短备份间隔,否则 PITR 能力形同虚设。

六、一个可参考的检查清单

在搭建备份恢复体系前,建议逐项确认以下内容,确保底层依据清晰:

检查项查看方式判断标准
存储引擎类型db.serverStatus().storageEngine应为 wiredTiger
journal 是否开启db.serverStatus().wiredTiger.log生产环境必须开启
checkpoint 间隔默认 60s,可通过 --wiredTigerCheckpointDelaySecs 调整与备份窗口匹配
oplog 大小与窗口db.getReplicationInfo()窗口 ≥ 备份周期 + 恢复缓冲
oplog 起始时间db.oplog.rs.find().sort({$natural:1}).limit(1)早于最近一次全量备份
数据文件路径db.serverStatus().wiredTiger 或配置文件快照/复制路径正确

把这三类持久化对象的关系理清之后,后续无论是选择物理快照、mongodump 还是 oplog 增量,都能明确知道“备份到了什么”“能恢复到哪一刻”“恢复后副本集如何重新对齐”。这正是备份恢复体系能够被信任的底层依据。