Recovery in postgres
服务器启动时,第一个启动的进程是 postmaster(新版本为postgres)。postmaster 接着会生成 startup process(启动进程),startup process 负责在发生故障时进行数据恢复。
startup process 是一个短暂的、一次性的进程,它的主要职责是在数据库启动时执行崩溃恢复或归档恢复。它完成它的工作后,就会退出。
1 | postgres@lavm-bar1guved6:/root$ pg_controldata -D /home/postgres/pgdata/ |grep state |
一个正常停止的服务器会处于“已关闭”(shut down)状态;而一个未运行的服务器却显示为“生产中”(in production)状态,则表明发生了故障。在这种情况下,启动进程(startup process)将自动从在同一个 pg_control 文件中找到的最新完成的检查点(checkpoint)**的起始 LSN 处开始进行恢复。
如果 PGDATA 目录中包含与备份相关的 backup_label 文件,则起始 LSN 位置会从该文件中获取。
在启动过程中,系统会从指定位置开始,逐一读取WAL(Write-Ahead Log,预写式日志)条目。如果数据页的 LSN(Log Sequence Number,日志序列号)小于当前读取到的 WAL 条目的 LSN,系统会将该 WAL 条目应用到数据页上。如果数据页的 LSN 已经大于 WAL 条目的 LSN,则不应应用该 WAL 条目;事实上,也绝不能应用,因为 WAL 条目被设计为必须严格按顺序重放。
然而,有些 WAL 条目是Full Page Image(FPI)。这类条目可以应用于页面的任何状态,因为它们会完全覆盖页面内容,无论页面原先是什么状态都不重要。因此,这种修改是幂等的(idempotent)——多次应用不会改变结果另一个幂等操作的例子是注册事务状态的变更:每个事务的状态在 CLOG(事务提交日志)中是通过设置特定位来表示的,这种设置不依赖于原来的位值。因此,不需要在 CLOG 页面中记录最近变更的 LSN(日志序列号),因为日志重放时只要设置一次这些位就够了,重复设置也不会有副作用最后,系统会执行一次 checkpoint(检查点),将恢复后的所有修改持久化到磁盘,此时 启动进程(startup process) 的任务就完成了。
WAL 日志条目会被应用到缓冲池(buffer cache)中的页面上,就像正常运行时对数据页的普通修改一样。
文件的恢复也遵循类似方式:例如,若某条 WAL 记录表明某个文件应该存在,但实际却缺失,系统就会重新创建这个文件。
一旦恢复完成,所有 unlogged relations会被它们对应的 初始化副本(init fork) 覆盖。
最后,系统会执行一次 checkpoint,将恢复后的所有修改持久化到磁盘,此时 启动进程(startup process) 的任务就完成了
在其经典形式中,恢复过程包含两个阶段:
- roll-forward阶段:重放 WAL 日志,重复执行在崩溃时丢失的操作;
- roll-back阶段:服务器中止那些在故障发生时尚未提交的事务。
在 PostgreSQL 中,向后回滚是不需要的。恢复完成后,CLOG(事务状态日志)中对未完成事务既没有提交(commit)标记,也没有中止(abort)标记(这在技术上表示该事务处于活动状态),但因为可以确定该事务已经不再运行,所以系统会将其视为已中止(aborted)。
我们可以通过强制服务器以“立即模式”(immediate mode)停止来模拟故障:
1 | postgres@lavm-bar1guved6:/root$ pg_ctl stop -m immediate |
当我们启动服务器时,启动进程会检测到之前发生了故障,因而进入恢复模式:
1 | 2025-08-04 10:23:31.860 CST [487414] LOG: starting PostgreSQL 19devel on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit |
如果服务器正在正常关闭,postmaster 会先断开所有客户端连接,然后执行最后一次检查点操作,将所有脏页(未写入磁盘的修改页面)刷写到磁盘上。
看当前的WAL位置
1 | test=# SELECT pg_current_wal_insert_lsn(); |
我们正常停止服务
1 | postgres@lavm-bar1guved6:~$ pg_ctl stop |
现在的数据库状态:
1 | postgres@lavm-bar1guved6:~$ pg_controldata -D /home/postgres/pgdata/ |grep state |
在WAL的末尾,我们看到了表示最后一次checkpoint的CHECKPOINT_SHUTDOWN 条目
1 | postgres@lavm-bar1guved6:~$ pg_waldump -p /home/postgres/pgdata/pg_wal -s 0/01B75358 |
最新的 pg_waldump 消息显示该工具已读取 WAL到末尾。
参考书目
- Egor Rogov, PostgreSQL 14 Internals, https://postgrespro.com/community/books/internals