bigywater.tech
MongoDB · 68

rollback:没复制出去的写入去哪了

没复制出去的写入去哪了?这篇在 3 节点副本集上让旧 primary 带着 5 个未复制的 w:1 写入崩溃,看新 primary 的选出、旧 primary 重启后的回滚过程、日志、rollback 目录里的 BSON 文件和最终的数据。

5 个文档
旧 primary 上 w:1 写入、未复制就崩溃,被回滚的文档数
约 0.5 秒
旧 primary 重启后 ROLLBACK 状态持续的时间(日志里 SECONDARY→ROLLBACK→SECONDARY)
rollback/
被回滚的文档保存的目录,里面是 BSON 文件

怎么造出这个场景

实测 · 本机执行输出
  节点 9901:15 个文档,_id>=100 的有 5 个
  节点 9902:10 个文档,_id>=100 的有 0 个
  节点 9903:10 个文档,_id>=100 的有 0 个

让两个 secondary 停止拉取 oplog,在 primary 上用 w:1 写入 5 个文档(没有任何副本有),再把 primary 杀掉。

新 primary

实测 · 本机执行输出
  10.6 s 后选出新 primary 9902(原来的 primary 是 9901)
  新 primary 上写入 3 个文档(_id 200~202,w:majority)后,文档数 13,_id>=100 的 [200, 201, 202]

两个 secondary 在约 10 秒后选出新 primary,继续接受写入,但它没有那 5 个文档。

回滚

实测 · 本机执行输出
  旧 primary 的状态(距重启秒数): [(0.62, 'SECONDARY')]

旧 primary 重启后 oplog 获取器发现与同步源分叉,进入 ROLLBACK,使用 recoverToStableTimestamp 方式,约 0.5 秒后恢复为 SECONDARY。

被回滚的数据

实测 · 本机执行输出
  状态转换(日志时间 → 新状态):11:52:38.213 STARTUP→STARTUP2;11:52:38.213 STARTUP2→RECOVERING;11:52:38.220 RECOVERING→SECONDARY;11:52:39.241 SECONDARY→ROLLBACK;11:52:39.725 ROLLBACK→SECONDARY
  11:52:38.030  Searching for local Rollback ID document
  11:52:38.031  Initializing rollback ID
  11:52:38.031  Incrementing the rollback ID after unclean shutdown
  11:52:38.034  Incremented the rollback ID
  11:52:38.035  Initial consistent data is now available
  11:52:39.240  Oplog fetcher returned error
  11:52:39.240  Starting rollback due to fetcher error
  11:52:39.240  Rollback using 'recoverToStableTimestamp' method
  11:52:39.240  Scheduling rollback
  11:52:39.240  Transition to ROLLBACK
  11:52:39.241  Replica set state transition
  11:52:39.241  Failed to refresh key cache

被撤销的文档保存在 dbpath/rollback/<集合 UUID>/removed.<时间>.bson 里,可以人工检查后再写回。

最终一致

实测 · 本机执行输出
  rollback 目录里的文件: ['rollback/9a3c21f6-f604-4c6a-a3ef-c4373d4a0f4f/removed.2026-10-12T03-52-39.1.bson', 'rollback/local.oplog.rs/removed.2026-10-12T03-52-39.0.bson']
  removed.2026-10-12T03-52-39.1.bson 里的文档: [{'_id': 101, 'src': 'old-primary-only'}, {'_id': 103, 'src': 'old-primary-only'}, {'_id': 104, 'src': 'old-primary-only'}, {'_id': 102, 'src': 'old-primary-only'}, {'_id': 100, 'src': 'old-primary-only'}]

回滚后三个节点都是 13 个文档,w:1 的 5 个文档不在集合里。

如何避免

实测 · 本机执行输出
  节点 9901:13 个文档,_id>=100 的 [200, 201, 202]
  节点 9902:13 个文档,_id>=100 的 [200, 201, 202]
  节点 9903:13 个文档,_id>=100 的 [200, 201, 202]

重要的写入使用 w: majority,已被多数确认的写入不会被回滚。

分步演示

回滚
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MongoDB 9.0.2(Homebrew 的 mongod、mongos)/ mongosh 2.12.0 / pymongo 4.18.3;每个实验在临时目录里起全新的 mongod(独立 dbpath,端口 9900 以上,只监听 127.0.0.1,WiredTiger 缓存 0.25 GB),需要副本集或分片集群时在本机起多个进程;命令输出来自真实的 mongosh,批量写入、计时与多线程用 pymongo;计时用 time.perf_counter,数字每次运行会有小幅抖动,ObjectId、时间戳等每次运行都不同。3 个 mongod 副本集;分叉通过 stopReplProducer 与 kill -9 构造;选主耗时每次运行不同(约 10 秒)
    • 没有验证:w:majority 的写入在同样场景下确实不会被回滚(只给出了建议);rollback 文件的大小上限(超过 300 MB 的回滚需要人工干预);各种 rollback 方式(recoverToStableTimestamp 之外);回滚期间对读写的影响;网络分区(而非 kill)造成的分叉
    京ICP备2026067502号-1京公网安备11010502063487号