最近我一直在练习 PostgreSQL 连续归档的内部原理以及我当前开发工作所需的时间点恢复功能。今天我想用最近在 Ubuntu 18.04 上发布的 PostgreSQL 14 来演示这些重要功能。
在详细介绍连续归档(continuous archiving)之前,了解预写日志 (WAL) 的概念非常重要。 WAL 文件由 PG 生成,其中包含从一开始就对数据库进行的所有操作。 INSERT 、 UPDATE 、 DELETE 、 VACUUM 等操作被捕获在 WAL 文件中。有了这些WAL文件,就可以简单地通过 replaying 它们来重新创建数据库,从而允许用户在发生故障时将数据库恢复到某种状态。这是连续归档和时间点恢复的基础。
生成的WAL文件通常存储在PG数据库集群内的 pg_wal 目录中,但它们不会永远增长。配置参数 max_wal_size 和 min_wal_size 控制 pg_wal 目录中可以保存多少个WAL文件。检查点进程将定期清除旧的 WAL 文件,只留下最近的文件。
因此,设置连续归档非常重要,以便所有这些 WAL 文件都可以 archived 到 PG 集群之外的其他地方。因此,当您需要恢复所有旧的 WAL 文件时,PG 可以从存档中恢复它们。
要启用 WAL 归档和恢复,请在 postgresql.conf 中设置以下参数:
archive_mode = on
archive_command = 'cp %p /path/to/archive/%f'
restore_command = 'cp /path/to/archive/%f %p'
您应该将 /path/to/archive 替换为您自己的系统上的存档路径。执行时pg会用 WAL segment 路径 和 WAL segment 名称 替换命令中的占位符 %p 和 %f 。
当一个 WAL 段准备好归档时,PG 将在 pg_wal/archive_status 中创建一个信号文件来指示特定的 WAL 段已准备好归档。
在下面的示例中,段 00000001000000000000000E 已准备好存档,由后缀 .ready
表示,而之前的所有段均已成功存档,因此由后缀 .done
表示
$ ls pgtest/pg_wal/archive_status/
000000010000000000000002.done 000000010000000000000005.done 00000001000000000000000A.done 00000001000000000000000E.ready
000000010000000000000003.done 000000010000000000000007.done 00000001000000000000000B.done
000000010000000000000004.done 000000010000000000000008.done 00000001000000000000000D.done
然后,PG 的归档进程将被唤醒,通过运行配置的 archive_command 来执行归档。
$ps -ef | grep postgres
caryh 1487 1 0 11:10 ? 00:00:00 postgres -D /home/caryh/pgtest
caryh 1510 1487 0 11:10 ? 00:00:00 postgres: checkpointer
caryh 1511 1487 0 11:10 ? 00:00:00 postgres: background writer
caryh 1512 1487 0 11:10 ? 00:00:00 postgres: walwriter
caryh 1516 1487 0 11:10 ? 00:00:00 postgres: autovacuum launcher
caryh 1520 1487 0 11:10 ? 00:00:00 postgres: archiver archiving 00000001000000000000000E
caryh 1521 1487 0 11:10 ? 00:00:00 postgres: stats collector
caryh 1522 1487 0 11:10 ? 00:00:00 postgres: logical replication launcher
请注意,归档程序 (PID=1520) 还会在 ps 显示屏上显示其进度。
成功完成后, pg_wal/archive_status 中的信号文件将更新为后缀 .done
。
$ ls pgtest/pg_wal/archive_status/
000000010000000000000002.done 000000010000000000000005.done 00000001000000000000000A.done 00000001000000000000000E.done
000000010000000000000003.done 000000010000000000000007.done 00000001000000000000000B.done
000000010000000000000004.done 000000010000000000000008.done 00000001000000000000000D.done
在下一个检查点中,这些 .done
文件将被删除,因此这些状态文件也不会持续增长。
将所有 WAL 段备份在单独的存档中,我们就能够将数据库恢复到过去的某个时间点或完全恢复整个数据库。这取决于您的需求,如果您犯了一个重大错误,需要从过去的某个时间点重新开始,您可以让 PG 在恢复模式下恢复到该特定时间,并从该时间点继续数据库操作。这也称为 switching to a new time line ID ,我们将在下一篇博客中对此进行更多讨论。
我们继续上面的例子(已经有100万行数据),做一个时间点恢复。
$ pg_basebackup -U caryh -h 127.0.0.1 --progress -D pgtest-back
获得LSN后,我们再次插入更多行数据。
insert into test values(generate_series(1,1000000), 'asdas');
insert into test values(generate_series(1,1000000), 'asdas');
pg_switch_wal();
pg_switch_wal
---------------
0/13DAC308
insert into test values(generate_series(1,1000000), 'asdas');
insert into test values(generate_series(1,1000000), 'asdas');
因此,这个表 test 总共应该有 500 万行数据,因为它以 100 万行开始,而我们在上面的示例中刚刚插入了 400 万行。
LSN 0/13DAC308 指示的 WAL 位置表示数据库仅包含 300 万行的时间,这就是我们要在示例中恢复到的 point of time 。
Stop the database server
pg_ctl -D pgtest stop
$ rm -rf pgtest/
这样做很疯狂,但请记住,我们在 步骤4.1 中 存档中的所有 WAL 段中进行了基础备份,因此从技术上讲,我们仍然拥有一切。
cp -r pgtest-back/* pgtest/
由于我们使用 LSN 作为目标,因此我们可以简单地将捕获的 LSN 放入 recovery_target_lsn 配置中
recovery_target_lsn = '0/13DAC308'
PG还支持其他方式来定义恢复目标,基于时间戳、名称或xid。有关其他选项,请参阅此文档。
$touch pgtest/recovery.signal
$pg_ctl -D pgtest start
服务器现在将以恢复模式启动,并且将从存档中恢复 WAL 文件并执行恢复。您可以使用 psql 登录并检查数据库是否还应包含 300 万行而不是 5 行。
您可能会注意到,即使数据库已恢复到过去的某个时间点,如果您打算插入其他数据,您也会遇到 database in recovery 或 read only database 错误。这是因为我们仍在 recovery mode 中,但当前处于 paused 中。
这是由 recovery_target_action 选项配置的,默认为 pause 。这实际上是为了让您有时间检查数据库并确认它确实是您想要恢复到的数据库状态。如果这是错误的,您只需关闭数据库并重新配置 recovery_target_lsn ,直到达到所需的数据库状态。
一旦确认数据库已正确恢复,您可以通过以下 psql 命令退出恢复模式:
select pg_wal_replay_resume();
此命令将结束恢复模式,您应该能够将其他数据插入数据库。 recovery.signal 文件将被删除,并且未来的 WAL 片段将具有新的时间线 ID。