一则 PITR 的有趣案例
1前言
今天晚上的时候,一位公众号粉丝找到我提了这样一个问题:"pg_receivewal 发回来的wal有办法做增备么。我的操作步骤是这样的"
(node-2) 复制wal日志 pg_receivewal -h node-1 -D xxx_rec (node-2) pg_basebackup -h node-1 -D xxx_full (node-1) 生成增量数据 pgbench -i -s 10 (node-2)修改配置文件 restore_command = 'cp xxx_rec/%f %p' recovery_target = 'immediate' (node-2)pg_ctl start -o '-c config路径'
库启动之后读取到中间 xxxxAD 就不继续读取了。我理解这个开启就是把已有的 backlabel 里面的读完就停止?
以上便是基本的现象以及这位粉丝的疑惑,让我们简单分析一下这个案例。
2分析
pg_receivewal 这个工具是 9.2 引入的,以 replication protocal 实时获取 WAL,有点像实时归档的意思。
PostgreSQL 9.2还支持虚拟备库,即就是只有WAL,没有数据文件的备库。pg_receivexlog,它以类似流式复制(streaming replication)的方式,获取主库的 WAL 文件。
先准备一个基础环境,同时使用 pg_basebackup 制作一个基础备份,这些就不做演示了。然后模拟一点业务,生成一些 WAL,同时新开一个窗口使用 pg_receivewal 实时流式接受 WAL:pg_receivewal -h localhost -p 5432 -D wal_archive
不一会儿,wal_archive 目录下就有 WAL 日志了
[postgres@xiongcc ~]$ ll wal_archive/
total 245760
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000077
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000078
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000079
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 00000001000000000000007A
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 00000001000000000000007B
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 00000001000000000000007C
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 00000001000000000000007D
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 00000001000000000000007E
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 00000001000000000000007F
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000080
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000081
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000082
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000083
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000084
-rw------- 1 postgres postgres 16777216 Feb 7 20:53 000000010000000000000085.partial
最后那个以 partial 结尾的文件表示当前正在写入的 WAL
postgres=# select pg_walfile_name(pg_current_wal_lsn());
pg_walfile_name
--------------------------
000000010000000000000085
(1 row)
那么既然有了变更日志(WAL),又有了基础备份(pg_basebackup),按理说做 PITR 应该没问题,试试看
postgres=# begin;
BEGIN
postgres=*# select txid_current();
txid_current
--------------
246387
(1 row)postgres=*# insert into t1 values(9999);
INSERT 0 1
postgres=*# commit ;
COMMIT
postgres=# select pg_switch_wal();
pg_switch_wal
---------------
0/85000560
(1 row)
postgres=# select pg_walfile_name(pg_current_wal_insert_lsn());
pg_walfile_name
--------------------------
000000010000000000000086
(1 row)
为了确保 WAL 顺利接受,做了个切换,其实也可以不用做。然后老样子,配置一下 PITR
[postgres@xiongcc ~]$ cat 14data_bak/postgresql.conf | grep recovery_target
#recovery_target = '' # 'immediate' to end recovery as soon as a
#recovery_target_name = '' # the named restore point to which recovery will proceed
#recovery_target_time = '' # the time stamp up to which recovery will proceed
recovery_target_xid = '246387' # the transaction ID up to which recovery will proceed
#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
#recovery_target_inclusive = on # Specifies whether to stop:
#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID
#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
此处我仅配置了 recovery_target_xid,恢复到插入 9999 这一行的事务 ID,其中 recovery_target_inclusive 表示是否恢复到这个事务之前还是之后,on 就表示恢复完插入这个事务,因此 9999 这条数据是可见的。
日志里正常显式从归档中或许日志的操作
2023-02-07 21:15:04.517 CST [20567] LOG: starting point-in-time recovery to XID 246387
cp: cannot stat ‘/home/postgres/wal_archive/000000010000000000000076’: No such file or directory
2023-02-07 21:15:04.527 CST [20567] LOG: redo starts at 0/76000028
2023-02-07 21:15:04.528 CST [20567] LOG: consistent recovery state reached at 0/76000100
2023-02-07 21:15:04.529 CST [20565] LOG: database system is ready to accept read-only connections
2023-02-07 21:15:04.554 CST [20567] LOG: restored log file "000000010000000000000077" from archive
2023-02-07 21:15:04.617 CST [20567] LOG: restored log file "000000010000000000000078" from archive
2023-02-07 21:15:04.668 CST [20567] LOG: restored log file "000000010000000000000079" from archive
2023-02-07 21:15:04.719 CST [20567] LOG: restored log file "00000001000000000000007A" from archive
2023-02-07 21:15:04.764 CST [20567] LOG: restored log file "00000001000000000000007B" from archive
2023-02-07 21:15:04.814 CST [20567] LOG: restored log file "00000001000000000000007C" from archive
2023-02-07 21:15:04.873 CST [20567] LOG: restored log file "00000001000000000000007D" from archive
2023-02-07 21:15:04.919 CST [20567] LOG: restored log file "00000001000000000000007E" from archive
2023-02-07 21:15:04.973 CST [20567] LOG: restored log file "00000001000000000000007F" from archive
2023-02-07 21:15:05.013 CST [20567] LOG: restored log file "000000010000000000000080" from archive
2023-02-07 21:15:05.067 CST [20567] LOG: restored log file "000000010000000000000081" from archive
2023-02-07 21:15:05.113 CST [20567] LOG: restored log file "000000010000000000000082" from archive
2023-02-07 21:15:05.178 CST [20567] LOG: restored log file "000000010000000000000083" from archive
2023-02-07 21:15:05.231 CST [20567] LOG: restored log file "000000010000000000000084" from archive
2023-02-07 21:15:05.299 CST [20567] LOG: restored log file "000000010000000000000085" from archive
2023-02-07 21:15:05.834 CST [20567] LOG: recovery stopping after commit of transaction 246387, time 2023-02-07 21:08:44.330518+08
2023-02-07 21:15:05.834 CST [20567] LOG: pausing at the end of recovery
2023-02-07 21:15:05.834 CST [20567] HINT: Execute pg_wal_replay_resume() to promote.
查询一下 9999 这条数据,没问题,数据正常恢复出来了。
postgres=# select * from t1 where id = 9999;
id
------
9999
(1 row)
那么回到这位童鞋的例子,说明采用 pg_receivewal 和基础备份做时间点恢复是没有问题的,那么问题出在了哪里?
首先这个例子中是恢复到中间的某一个日志,那让我们继续再生成一些
[postgres@xiongcc ~]$ ll wal_archive/
total 540672
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000077
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000078
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000079
...
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000080
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000081
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000082
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000083
-rw------- 1 postgres postgres 16777216 Feb 7 20:50 000000010000000000000084
-rw------- 1 postgres postgres 16777216 Feb 7 21:08 000000010000000000000085
-rw------- 1 postgres postgres 16777216 Feb 7 21:22 000000010000000000000086 ---👈🏻上一次在这
-rw------- 1 postgres postgres 16777216 Feb 7 21:22 000000010000000000000087
-rw------- 1 postgres postgres 16777216 Feb 7 21:22 000000010000000000000088
...
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 00000001000000000000008E
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 00000001000000000000008F
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000090
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000091
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000092
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000093
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000094
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000095
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000096
-rw------- 1 postgres postgres 16777216 Feb 7 21:23 000000010000000000000097.partial
这一次按照这位童鞋操作的例子,配置 recovery_target = 'immediate',回顾一下我之前写过的关于 PITR 的文章
现在不妨回过头来思考一下,为什么 recovery_target_action 是 pause 呢?假如我们设为了 promote 的话,PostgreSQL 便会自动帮你执行pg_wal_replay_resume(),时间线加1,但是一旦后之后居发现恢复错了,不是想要的时间点,也无法再继续基于当前这个实例继续恢复了,假设还只有一份全备的话,那就只能干瞪眼了。而是 pause 的话,PostgreSQL 会假设你还没有恢复完成,或者说是更严谨,需要我们自行去校验是否恢复到了我们想要的时间点,就算不是也可以再恢复一次,因为回放机制的原因,多次回放是可以的,一旦确认无误之后,便可以调用 pg_wal_replay_resume(),让实例变成可读写的状态,至此 PITR 也就正式完成了。
因为我还没有手动 promote,因此还是可以继续恢复的,这次我们改成 recovery_target = 'immediate',再启动一次,这一次的日志如下
2023-02-07 21:15:05.834 CST [20567] LOG: pausing at the end of recovery
2023-02-07 21:15:05.834 CST [20567] HINT: Execute pg_wal_replay_resume() to promote. ---👈🏻上一次操作的日志在这
2023-02-07 21:30:21.237 CST [20565] LOG: received fast shutdown request
2023-02-07 21:30:21.240 CST [20565] LOG: aborting any active transactions
2023-02-07 21:30:21.789 CST [20570] LOG: shutting down
2023-02-07 21:30:21.793 CST [20565] LOG: database system is shut down
2023-02-07 21:30:21.860 CST [20644] LOG: starting PostgreSQL 14.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44), 64-bit
2023-02-07 21:30:21.860 CST [20644] LOG: listening on IPv6 address "::1", port 5433
2023-02-07 21:30:21.861 CST [20644] LOG: listening on IPv4 address "127.0.0.1", port 5433
2023-02-07 21:30:21.865 CST [20644] LOG: listening on Unix socket "/tmp/.s.PGSQL.5433"
2023-02-07 21:30:21.871 CST [20646] LOG: database system was shut down in recovery at 2023-02-07 21:30:21 CST
cp: cannot stat ‘/home/postgres/wal_archive/00000002.history’: No such file or directory
2023-02-07 21:30:21.874 CST [20646] LOG: starting point-in-time recovery to earliest consistent point
2023-02-07 21:30:21.890 CST [20646] LOG: restored log file "000000010000000000000085" from archive
2023-02-07 21:30:21.929 CST [20646] LOG: redo starts at 0/85000060
2023-02-07 21:30:21.929 CST [20646] LOG: consistent recovery state reached at 0/85000548
2023-02-07 21:30:21.929 CST [20646] LOG: recovery stopping after reaching consistency
2023-02-07 21:30:21.929 CST [20646] LOG: pausing at the end of recovery
2023-02-07 21:30:21.929 CST [20646] HINT: Execute pg_wal_replay_resume() to promote.
2023-02-07 21:30:21.930 CST [20644] LOG: database system is ready to accept read-only connections
可以看到恢复到了 000000010000000000000085 就停了,没有继续往后应用 WAL,所以按照这位童鞋的想法,应该是想要恢复到最新的日志,即 000000010000000000000097.partial。那么就是 recovery_target 这个参数的影响了,看看这个参数吧
This parameter specifies that recovery should end as soon as a consistent state is reached, i.e., as early as possible. When restoring from an online backup, this means the point where taking the backup ended.
此参数指定恢复应在达到一致状态后立即结束,即尽可能早地结束。从在线备份恢复时,这意味着备份结束的点。
达到一致性状态后就立马结束,因为上一次一致性恢复到了 000000010000000000000085,所以就如这个参数所说,停止,没有继续往后恢复了。简而言之,就是当数据库只要发现了一个一致性的状态,就立马停止结束回放。比如前面这个例子,上一次一致性是到了 000000010000000000000085,所以这次就恢复到了 000000010000000000000085。
2023-02-07 21:30:21.929 CST [20646] LOG: redo starts at 0/85000060
2023-02-07 21:30:21.929 CST [20646] LOG: consistent recovery state reached at 0/85000548 ---此时已经达到一致性状态了
2023-02-07 21:30:21.929 CST [20646] LOG: recovery stopping after reaching consistency
而假如不像我这么操作,做完基础备份之后,然后配置 recovery_target = 'immediate' 进行恢复的话,便会恢复到备份结束的点,为什么?因为备份结束数据库认为达到一致性状态了。如这句话所说,When restoring from an online backup, this means the point where taking the backup ended. 备份结束的点
日志里也是如此 👇🏻
[postgres@xiongcc ~]$ ll wal_archive/
total 229376
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 000000010000000000000099
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009A
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009B
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009C
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009D
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009E
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009F
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A0
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A1
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A2
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A3
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A4
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A5
-rw------- 1 postgres postgres 16777216 Feb 7 21:45 0000000100000000000000A6.partial ---👈🏻上一次正在写的日志在这里
[postgres@xiongcc ~]$ ll wal_archive/
total 262144
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 000000010000000000000099
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009A
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009B
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009C
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009D
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009E
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 00000001000000000000009F
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A0
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A1
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A2
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A3
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A4
-rw------- 1 postgres postgres 16777216 Feb 7 21:40 0000000100000000000000A5
-rw------- 1 postgres postgres 16777216 Feb 7 21:47 0000000100000000000000A6
-rw------- 1 postgres postgres 16777216 Feb 7 21:47 0000000100000000000000A7 ---👈🏻做pg_basebackup全备的时候会切换日志
-rw------- 1 postgres postgres 16777216 Feb 7 21:47 0000000100000000000000A8.partial
然后启动,你便会发现恢复到了备份结束的点,0000000100000000000000A7
[postgres@xiongcc ~]$ tail -f 14data_bak/log/postgresql-07.log
2023-02-07 21:50:13.739 CST [20788] LOG: database system was interrupted; last known up at 2023-02-07 21:47:38 CST
cp: cannot stat ‘/home/postgres/wal_archive/00000002.history’: No such file or directory
2023-02-07 21:50:13.796 CST [20788] LOG: starting point-in-time recovery to earliest consistent point
2023-02-07 21:50:13.812 CST [20788] LOG: restored log file "0000000100000000000000A7" from archive
2023-02-07 21:50:13.839 CST [20788] LOG: redo starts at 0/A7000028
2023-02-07 21:50:13.841 CST [20788] LOG: consistent recovery state reached at 0/A7000100
2023-02-07 21:50:13.841 CST [20788] LOG: recovery stopping after reaching consistency
2023-02-07 21:50:13.841 CST [20788] LOG: pausing at the end of recovery
2023-02-07 21:50:13.841 CST [20788] HINT: Execute pg_wal_replay_resume() to promote.
2023-02-07 21:50:13.842 CST [20786] LOG: database system is ready to accept read-only connections
3小结
其实这位童鞋的问题还挺"刁钻",因为按照我们一般的习惯,是习惯于配置那些 xid/time 之类的,不会配置这个 recovery_target。总之还是挺不错,我也温习了一下 pg_receivewal 以及 PITR 的基础原理,还搞懂了这个生疏的参数。
另外,明天晚上 8 点,和各位唠唠嗑,不见不散