图解 MongoDB 15|journal 与持久化:写入怎么不丢,崩溃怎么恢复

上一篇讲 Cache 淘汰,回答了「查询为什么会抖」。这一篇讲 journal 和持久化,回答另一个根本问题:写入怎么保证不丢,崩溃后怎么恢复。这是所有数据库都必须回答的问题,MongoDB 的答案落在 journal(预写日志)和 checkpoint 这套机制上。

理解这套机制,能解释几个关键决策:为什么 MongoDB 的写入不是「立即落盘」而是「最多延迟 100ms」?为什么 w: 1w: "majority" 的延迟差这么多?为什么 checkpoint 间隔影响崩溃恢复速度?这些都和 journal、checkpoint 的工作方式直接相关。

先把机制边界说清楚

WiredTiger 保证持久性的核心是 WAL(Write-Ahead Logging,预写日志) ,在 MongoDB 里叫 journal。它的规则很简单:任何修改内存页(Cache 脏页)之前,先把这次修改记到 journal。这样即使崩溃,journal 里有完整的修改记录,恢复时重放即可。

写入的完整链路是:

  1. 应用发起写入(insert/update/delete),复制集场景还要追加 oplog。
  2. 把修改记到 journal(WAL),最多延迟 commitIntervalMs(默认 100ms)刷盘。
  3. 修改 Cache 里的页(变成脏页),返回成功。此时数据在内存,还没进数据文件。
  4. checkpoint(默认 60s)把脏页刷成数据文件里的一致快照。

关键认知:journal 记录成功 = 数据不会丢(即使还没 checkpoint,崩溃也能从 journal 恢复)。而「写入返回成功」的那一刻,数据可能还在 Cache 里、没进数据文件------这就是为什么 MongoDB 的写入是「最多 100ms 后持久」,不是「立即持久」。

一次写入的不丢之路

把写入链路画出来,journal 的角色立刻清晰:它是介于「写入返回成功」和「数据真正进数据文件」之间的那道保险。没有 journal,Cache 里的脏页在崩溃时会全部丢失;有了 journal,脏页丢了也能从日志重放。

checkpoint:恢复快的保证

Checkpoint 是 WiredTiger 把 Cache 脏页刷成数据文件一致快照的机制,默认每 60 秒一次。它的价值不只是「让数据落盘」,更是缩短崩溃恢复时间

如果没有 checkpoint,崩溃恢复要回放从头到尾的全部 journal,journal 越长恢复越慢。有了 checkpoint,恢复时只要:

  1. 加载最近一次 checkpoint 的数据文件(一个一致的磁盘快照)。
  2. 回放 checkpoint 之后到崩溃点的 journal(这一段通常很短,最多 60 秒的写入)。
  3. 丢弃未提交的部分(journal 没记的 = 没成功的)。

所以 checkpoint 间隔短 = 恢复快(要回放的 journal 少);checkpoint 间隔长 = 恢复慢但刷盘频率低。这是个性能和恢复速度的权衡,默认 60 秒是个平衡点。

journal 的刷盘策略

journal 什么时候真正写到磁盘,由 storage.journal.commitIntervalMs(默认 100ms)控制。写入记到 journal 后,最多等 100ms 才批量刷盘。这种「攒一批再刷」的方式,把大量小写入合并成少量大写入,提升吞吐,代价是「最多 100ms 的持久延迟」。

有两个例外会让 journal 立即刷盘:

  • 写关注带 j: true:强制这次写入等 journal 刷盘成功才返回。这是「单机不丢」的强保证,但要承受刷盘延迟。
  • 写入量触发提前刷盘:journal 缓冲区攒到一定大小,会提前刷盘,不等 100ms。

理解了刷盘策略,就理解了 w: 1j: true 的差别:w: 1 默认不等 journal 刷盘(只是记到 journal 缓冲),j: true 才等真正刷盘。所以严格的单机持久性要 w: 1, j: true,不是单纯的 w: 1

writeConcern:持久性的档位选择

writeConcern 是 MongoDB 控制写入持久性和一致性的参数,它决定了「写入返回成功前要等什么」。先说一个容易踩的事实:5.0 之前复制集默认 writeConcern 是 w: 1,从 5.0 起默认已改为 w: "majority"------今天在 5.0+ 部署上不显式指定,其实就是 majority。下面三个核心档位按这个前提理解:

w: 0:不等任何确认,发了就算成功。最快,但可能丢。只适合可丢失的数据(日志、埋点、缓存预热)。

w: 1 :等主节点确认(记到 journal 缓冲或内存)。单机不丢(配合 j: true),但在复制集下,如果主节点记完就返回、还没来得及同步到从节点,此时主节点宕机,这条写入可能在 failover 后回滚丢失。所以 w: 1 是「单机持久,复制集可能回滚」。

w: "majority" :等多数节点确认写入。这是复制集下最强的不丢保证------多数节点都有这条数据,任何少数派宕机都不会丢。代价是延迟:要等多数节点同步确认,写入延迟通常是 w: 1 的两倍以上。

档位选择的核心判断是「这条写入能不能容忍丢失」:

  • 可丢(日志、埋点、计数):w: 0 或主动降到 w: 1,换吞吐。
  • 单机不丢即可(大多数业务):w: 1,必要时 j: true。注意 5.0+ 默认已经是 majority,显式写 w: 1 是为了把延迟换吞吐。
  • 绝对不能丢(订单、支付、账户):w: "majority",接受延迟代价。

崩溃恢复的完整流程

把前面几点串起来,崩溃恢复的完整流程是:

  1. mongod 重启,WiredTiger 打开数据文件,加载最近 checkpoint 的快照。
  2. 检查 journal,找到 checkpoint 之后的记录。
  3. 逐条重放这些 journal 记录,把已提交的修改重新应用到数据页。
  4. 丢弃 journal 里没有的(没记 = 没提交 = 丢弃)。
  5. 恢复完成,开始正常服务。

这个流程的关键是「journal 完整性」------只要 journal 没坏,已提交的写入就不会丢。journal 文件本身也是循环的(旧的 checkpoint 后会被删除),不会无限增长。

取舍与边界

这套 WAL + checkpoint 机制的优势是「写入快(journal 追加 + 内存改)+ 恢复可靠(journal 回放)」。代价和边界:

  • 写入不是立即持久 。默认最多 100ms 延迟才真正落 journal。绝对不能丢的写入要 j: truew: "majority"
  • checkpoint 期间 IO 上升。刷脏页带来 IO 尖峰,可能影响同时刻的其他操作。
  • 复制集下 w: 1 仍可能回滚 。主切换时,没同步到多数派的写入会被回滚。核心数据要 w: "majority"
  • journal 占用磁盘。journal 文件要留够空间,磁盘满了 journal 写不进去,写入会失败。

判断框架

  • 写入不丢靠 journal,不是靠「写入返回成功」。成功返回 ≠ 已落盘。
  • j: true 是单机持久性强保证,w: "majority" 是复制集不丢保证。
  • 5.0+ 默认 writeConcern 已是 w: "majority";4.x 及以前默认才是 w: 1。排查延迟时先确认默认值,别把 majority 的延迟当异常。
  • checkpoint 间隔短 = 恢复快 + 刷盘频繁;间隔长 = 恢复慢 + 刷盘少。
  • writeConcern 按数据价值选档:可丢 w:0/1,单机不丢 w:1+j:true,绝对不丢 w:majority
  • 复制集下 w:1 在主切换时可能回滚,核心交易必须 w:majority
  • 崩溃恢复时间 ≈ 重放 checkpoint 之后的 journal,保持 checkpoint 间隔合理。

下一篇讲压缩,把「存储怎么省」这条线补上。


关于十三Tech

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

相信 AI 是程序员的最佳搭档,帮助每一位开发者驾驭 AI。

公众号搜索「十三Tech」

本文首发:rubyfun.cn/posts/%E5%9...

相关推荐
葫芦和十三1 小时前
图解 MongoDB 16|压缩:snappy、zstd 和 zlib 的取舍
后端·mongodb·面试
苍何1 小时前
终于找到免费开源TTS模型,克隆声音不要钱,本地电脑也能跑
后端
用户593608741402 小时前
Spring AI 集成 DeepSeek 原生供应商并实现think模式
后端
追逐时光者2 小时前
别再满网找零散工具了,腾讯 QQ 浏览器这个“帮小忙”工具箱真能省时间
前端·后端
心静自然凉8002 小时前
Linux网络核心知识+bonding主备模式配置
后端
爻渡4 小时前
异步编程演进史:从回调到Promise再到Async/Await
后端·程序员
要阿尔卑斯吗5 小时前
企业级 RAG 系统的文件标签管理:三层架构与层级优化实战
后端
要阿尔卑斯吗5 小时前
Agent开发之为什么有了LangChain4j框架,我们却不能直接使用它?——桥接层设计详解
后端
用户7713970207065 小时前
从CMD到PowerShell:一个.NET开发者的命令行进化之路
后端