BodySense 数据库第 16 课:WAL、Checkpoint、Crash Recovery 与 Durability
从 PostgreSQL WAL 与 checkpoint 理解事务持久性、write-ahead 原则、crash recovery 与 fsync,并把 ACID 的 Durability 连接到真实存储恢复机制。
[!info] related notes
- 课程 MOC:BodySense 数据库工程深化课程 MOC
- 前置:第 15 课
- 知识:WAL、ACID
BodySense 数据库第 16 课:WAL、Checkpoint、Crash Recovery 与 Durability
[!abstract] 本课目标 理解
COMMIT为什么不是“把每张表的数据页全部立即写回磁盘”,以及数据库如何在 crash 后恢复到一致状态。
从零建立概念:Durability 为什么不能靠“把所有数据页立刻写盘”
先回到 ACID 的 D:
Transaction COMMIT 成功后,即使 PostgreSQL 进程或机器随后 crash,已提交结果也应该能恢复。
最直觉的做法似乎是:
UPDATE 10 tables
↓
把所有修改过的 data pages 立刻 fsync 到 disk
↓
然后告诉客户端 COMMIT success
这当然有机会做到 durable,但性能很差。
原因:
一个 transaction 可能修改很多分散 pages
→ lots of random writes
→ lots of fsync / storage latency
PostgreSQL 使用 WAL 来解耦:
Commit durability
≠
all data pages already written
WAL 是什么
WAL = Write-Ahead Log,预写日志。
名字最重要的词是:Ahead。
核心规则:
描述某个数据页修改的 WAL record,必须在对应修改过的数据页被认为 durable 之前先写到持久存储。
粗略:
UPDATE row
↓
modify page in memory
↓
generate WAL record
↓
flush required WAL
↓
COMMIT can be durable
↓ later
dirty data page can be written to table file
为什么这有用?
因为如果 crash 时:
WAL durable
but data page not yet written
重启后可以:
replay WAL
→ redo change
→ bring data files forward
Log Record 不是业务 Event
WAL 很容易和:
RuntimeEvent
Kafka event
Audit log
混淆。
WAL 的 ownership 是 PostgreSQL storage engine:
physical/logical changes needed for database recovery/replication
它不是给前端阅读的业务 event stream。
例如 BodySense:
runtime_events row
= domain/runtime durable event
WAL record for inserting that row
= PostgreSQL internal recovery data
两个都“像日志”,语义完全不同。
LSN:WAL 中的位置
LSN = Log Sequence Number。
可以先把它想成:
WAL 字节流中的一个位置坐标。
例如:
0/16B6C50
LSN 可用于:
replication progress
backup/recovery position
WAL flush position
diagnostics
不是 transaction ID,也不是业务 sequence。
WAL Buffer → OS → Durable Storage
“写 WAL”也有层次:
PostgreSQL generates WAL
→ WAL buffers / write
→ OS cache
→ fsync / flush
→ storage guarantees persistence
所以真正 Durability 不只是 PostgreSQL 一层。
还依赖:
fsync settings
synchronous_commit
OS
filesystem
virtualization
cloud disk
hardware cache
错误硬件/虚拟化若虚假确认 flush,也会破坏数据库对 durability 的假设。
fsync 是什么
应用 write() 返回,不一定代表 bytes 已经安全到断电后仍存在的介质。
fsync 一类操作要求 OS/存储层把必要写入推进到持久状态。
代价是:
durability stronger
↔
storage flush latency
WAL 的价值之一,是让每次 transaction 不必 fsync 大量随机 data pages,而主要保证必要 WAL 持久化。
Group Commit:多个 Transaction 可以共享一次昂贵 Flush
假设短时间有:
T1 commit
T2 commit
T3 commit
数据库可以把多个 transaction 的 WAL flush 合并。
于是一次 storage flush 可以让多个 transaction 达成 durability。
这叫 group commit 的基本思想。
所以:
WAL 不只是正确性机制,也是高吞吐 transaction 系统的性能基础。
Dirty Page 是什么
Page 从 disk 读进 shared buffer 后被修改:
in-memory page
≠
disk file current content
它就是 dirty page。
Commit 并不要求所有 dirty page 立刻落盘。
后续 background writer/checkpoint 等机制会逐步把它们写回。
Checkpoint 到底是什么
如果 data pages 永远不落盘,只积累 WAL:
crash recovery
→ 可能要从很久以前重放海量 WAL
Checkpoint 周期性推进一个恢复基点:
ensure relevant dirty pages reach disk
record checkpoint information
→ recovery can start from a newer point
Checkpoint 太频繁
lots of forced page writes
I/O spikes
more full-page-write overhead around checkpoints
Checkpoint 太稀疏
more dirty state / WAL to recover
longer crash recovery
所以仍然是 trade-off。
Crash Recovery:REDO 的基本思想
PostgreSQL 意外停止后:
data files
可能停在某个较旧/部分写入状态
WAL
包含已经 durable 的变更记录
启动时恢复过程:
find recovery starting point
→ read WAL forward
→ redo changes that data pages still need
→ reach consistent end
→ accept normal traffic
PostgreSQL 官方将此称为 roll-forward / REDO recovery。
WAL 与 RDB/AOF 的对照
Redis AOF:
Redis dataset persistence mechanism
PostgreSQL WAL:
transaction/storage recovery foundation
共同点:
log before/for recovery
但不要把实现和语义一一等同。
PostgreSQL 的 WAL 还深度参与:
MVCC/transaction system
physical page recovery
replication
PITR
WAL 为什么还能做 Replication 和 PITR
如果:
Primary
→ continuously produces WAL
Replica 可以:
receive WAL
→ replay changes
→ keep physical copy near primary
Backup 也可以:
base backup
+
archived WAL
→ replay to target point
所以:
Crash Recovery
Replication
PITR
共享同一个底层 WAL 思维。
synchronous_commit 为什么是一个业务取舍
在某些配置下可以让 transaction 更早返回,把部分 WAL flush 延后。
这样:
latency ↓
但 crash 时:
最近已向客户端返回 success 的 transaction
可能存在额外丢失窗口
是否可接受取决于数据类型。
例如:
disposable analytics
vs
payment / health durable record
不能用同一个“性能更快”理由决定。
1. Write-Ahead Logging
核心原则:
先把描述修改的 WAL record 持久化
→ 数据 page 可以稍后刷盘
这样即使数据 page 还没落盘,只要必要 WAL 已 durable,重启时就能重放恢复。
2. 为什么不每次 Commit 都刷所有数据页
随机写很多数据 page 成本高。WAL 把大量修改先变成相对顺序的日志写,再由后台逐步写 dirty pages。
3. Checkpoint
Checkpoint 让系统建立一个恢复基点,并推动 dirty page 刷盘。Checkpoint 太频繁会增加 I/O;太稀疏则 crash recovery 可能需要重放更多 WAL。
4. Commit 与 fsync
Durability 依赖存储栈真正把必要数据持久化。synchronous_commit、fsync、磁盘/云盘语义都影响“服务告诉客户端 commit 成功”时的保障。
5. WAL 还有哪些用途
PostgreSQL WAL 同时是:
crash recovery foundation
→ physical replication stream
→ PITR base + WAL archive
因此恢复、复制和备份不是互相独立的知识岛。
Prediction
[!question]
- Transaction commit 后,所有被修改 table page 是否必须已经写回数据文件?
- 为什么 WAL 必须“先于”对应数据页持久化?
- 只保留最新 base backup、不保留后续 WAL,能做到任意时间点恢复吗?
- Checkpoint 越频繁越安全吗、越好吗?
- 应用返回 200 后主机突然断电,Durability 由哪些层共同决定?
[!success]- Answer / 解析 1. 不需要。 关键是事务所需 WAL 已满足 durability 条件,dirty data page 可以之后刷盘。
2. 否则 crash 时可能出现数据页包含数据库无法通过日志解释/恢复的修改,破坏恢复协议。
3. 不能。 PITR 需要 base backup 加目标时间点之前连续可用的 WAL archive。
4. 不一定。 更频繁 checkpoint 可能制造大量 I/O;恢复时间与运行期写压力需要平衡。
5. PostgreSQL commit 配置、WAL flush/fsync、OS、虚拟化/云盘持久化语义和硬件共同构成。
Lab
在隔离开发数据库查看 pg_current_wal_lsn()、checkpoint/WAL 统计;做一批写入前后比较 WAL 增量。阅读 PostgreSQL 日志中的 checkpoint 信息。
L4 Gate
能从 COMMIT → WAL durable → dirty page later → crash → replay 解释 PostgreSQL Durability,不再把 commit 理解成“直接保存表文件”。