跳到内容

4.3 WAL、Checkpoint 与 Crash Recovery

一次突然断电让部分数据页落盘、部分仍在内存,重启后的档案城必须判断哪些修改应该保留。

Buffer pool 允许 data page 先在内存修改、稍后批量写回。代价是 crash 可能留下“某些 page 已写、某些还没写”的状态。Write-Ahead Logging 用有序 log 建立恢复依据,让 commit latency 不必等待所有 dirty pages 落盘。

五个不能混在一起的事件

text
1. transaction 修改 in-memory page
2. engine 生成/插入 WAL record
3. WAL flush 到 durable boundary
4. dirty data page 写回
5. transaction commit acknowledgement

核心 write-ahead rule:包含某项修改的 data page 在持久化前,足以 redo 该修改的 WAL 必须先持久化。

对承诺 synchronous durability 的 commit,commit record 及必要先前 WAL 要在响应成功前达到产品定义的 durable boundary。配置可允许 async/lower durability,性能更高但 crash loss window 更大,必须明确写入服务契约。

所以“WAL 必须在修改 buffer page 前落盘”不准确。内存修改与 WAL construction 可以先发生;被约束的是 WAL flush、data-page flush 和 commit acknowledgement 的顺序。

Why WAL

data pages 往往散布在多个 files,而 WAL 是 append-oriented。commit 时顺序追加并 group flush,通常比同步写回所有 modified pages 更高效。

crash 后:

  • durable WAL 中记录、data page 尚未包含的 change 可 REDO;
  • incomplete transaction 如何处理,取决于 engine 的 undo/MVCC design;
  • checkpoint 限制 recovery 起点,但不是“所有 transaction 都结束、所有 dirty page 都同步写完”的同义词。

LSN 与 pageLSN

Log Sequence Number 标识 log position/order。不同 engine 的 encoding 不同;在 PostgreSQL 中 LSN 表示 WAL byte position,可比较距离。不要把所有系统的 LSN 都定义成普通文件内不变 offset。

ARIES-style design 常在 page 记录 pageLSN。redo record 的 LSN 不大于 pageLSN 时,说明该效果可能已经在 page 中;实际 redo 条件还需 page identity、record type 和 engine-specific checks。

ARIES 的三阶段模型

ARIES 经典恢复流程:

  1. Analysis:从 checkpoint 信息恢复 transaction table 与 dirty-page table,识别 winner/loser;
  2. Redo:从最早需要的 recLSN 开始 repeat history,包括某些未提交 transaction 的 actions,把 database 恢复到 crash 时刻的物理状态;
  3. Undo:按 log backward chain 撤销 losers,写 Compensation Log Records,使 recovery 本身再次 crash 时可继续。

ARIES 常结合 steal/no-force buffer management:

  • steal:uncommitted dirty page 可以写回,所以需要 undo information;
  • no-force:commit 不强制所有 data pages 写回,所以需要 redo information。

这是理解算法的通用模型,不应直接写成“PostgreSQL 和 InnoDB 都完整照搬同一三阶段实现”。

PostgreSQL 不是简单的 ARIES 三阶段

PostgreSQL WAL 为 data-file change 提供 roll-forward REDO。crash recovery replay WAL,把 page 带到一致状态。未提交 transaction 生成的 tuple versions 由 transaction status/MVCC visibility 判为不可见,之后由 VACUUM 等机制回收;它不是在 crash recovery 中对每个 loser 执行经典 ARIES physical UNDO。

PostgreSQL 还需要处理 torn-page risk。full-page writes 会在 checkpoint 后首次修改 page 时记录完整 page image(受配置和具体 record 影响),让 recovery 能修复只写入一部分的 page。

官方入口:

InnoDB 的 redo 与 undo

InnoDB 使用 redo log roll-forward committed/possibly persisted changes,并使用 undo information 处理 incomplete transactions 与 MVCC。background rollback/purge、doublewrite buffer 和 checkpoint 共同参与 durability/recovery。

redo log、undo log 与 binary log 职责不同:

  • redo:storage-engine crash recovery;
  • undo:rollback 和 consistent read 所需旧版本;
  • binary log:server-level replication/PITR event stream(具体格式与配置不同)。

不要把三者统称为 WAL,也不要从一项 log durable 推出其他 log/replica 已同步。

官方入口:

Checkpoint 做什么

checkpoint 记录一个 recovery boundary,并推动脏页/metadata 的可恢复进度。不同算法可能 fuzzy checkpoint:允许 transactions 和 page updates 继续,不要求暂停世界后一次写净全部 dirty pages。

checkpoint 太频繁会增加 write pressure;太稀疏会延长 recovery、扩大 WAL 保留与 dirty working set。调优要共同观察:

text
WAL generation rate
checkpoint frequency/reason
checkpoint write and sync time
dirty page volume
recovery objective
replication/archive retention
storage latency and burst

把 checkpoint 写得过急会形成周期 I/O spike;许多 engine 会在 checkpoint interval 内平滑写出。

Group commit

多个 transactions 的 commit records 可以共享一次 WAL flush:

text
T1 commit record --\
T2 commit record ----> one durable flush -> acknowledge T1/T2/T3
T3 commit record --/

它在不放弃 synchronous durability 的前提下 amortize flush latency。吞吐收益取决于 concurrency、device latency 和 scheduler;低并发下可合并的 commits 较少。

fsync 与硬件边界

database 请求 flush 后,可靠性依赖整个 storage stack 正确履约:filesystem、kernel、hypervisor、controller、device cache 和 power-loss protection。设备若虚假报告 flush 完成,WAL protocol 也无法凭软件恢复尚未真正持久化的 bytes。

不要为了 benchmark 关闭 fsync/full-page protection 后仍声称具有 crash durability。若测试 lower-durability mode,结果必须明确标注并单独报告潜在 loss/corruption boundary。

Crash recovery 不等于 backup

WAL 能恢复 crash 前已 durable 的 database state,却不能单独防御:

  • 用户误删并已提交;
  • storage/media 全毁;
  • log 与 data 同时丢失;
  • application 逻辑写入错误;
  • attacker 删除/加密所有副本;
  • silent corruption 未被及时发现。

可靠恢复还需要 base backup/snapshot、WAL archive、point-in-time recovery、独立 failure domain、定期 restore drill 和明确 RPO/RTO。

安全的恢复实验

只在 disposable instance 与复制数据上演练:

  1. 写入带序号的 committed transactions;
  2. 保留一项 open/uncommitted transaction;
  3. 用文档支持的 crash simulation 终止专用实例,而不是关闭生产电源;
  4. 重启并记录 recovery log;
  5. 验证 committed rows、uncommitted rows、constraints 与 checksum;
  6. 再从 backup + archived WAL 做一次独立 restore;
  7. 测量实际 recovery time 并与 RTO 比较。

kill -9 测进程 crash,不完整模拟 torn write、device cache 丢失或整机断电。不同 failure mode 要分别验证。

本章小结

page 与 buffer pool 解决在线读写效率,B+ tree 提供 ordered access path,WAL 则解决 dirty pages 延迟写回后的 crash consistency。理解这三者的边界后,才能讨论下一章 LSM-tree 为什么把随机写转换为 memtable、WAL 与 sorted runs,并把代价转移到 compaction。

Built with VitePress | Software Systems Atlas