PostgreSQL 时间点恢复(PITR)在异步流复制主从模式下,如何避免主备切换后PITR恢复(备库、容灾节点、只读节点)走错时间线(timeline , history , partial , restore_command , recovery.conf)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
性能测试 PTS,5000VUM额度
简介: 标签 PostgreSQL , 恢复 , 时间点恢复 , PITR , restore_command , recovery.conf , partial , history , 任意时间点恢复 , timeline , 时间线 背景 政治正确非常重要,对于数据库来说亦如此,一个基于流复制的HA架构的集群,如果还有一堆只读节点,当HA集群发生了主备切换后,这些只读节点能否与新的主节点保持

标签

PostgreSQL , 恢复 , 时间点恢复 , PITR , restore_command , recovery.conf , partial , history , 任意时间点恢复 , timeline , 时间线


背景

政治正确非常重要,对于数据库来说亦如此,一个基于流复制的HA架构的集群,如果还有一堆只读节点,当HA集群发生了主备切换后,这些只读节点能否与新的主节点保持同步关系(类似与政治正确),很重要。

基于流复制的主从库模式,主库和从库都可以归档WAL日志。如果使用的是异步复制,主库和从库可能存在一定的WAL差异,如果从库激活,会启用新的时间线。

意味着老的时间线上的WAL并没有全部同步到新的主库新的时间线上。归档也会存在差异。

如何配置PITR恢复,才不会走老路,一直紧跟新的时间线呢?

取决于PITR时,在走错WAL前,是否先发现新时间线的HISTORY文件。

例子

pic

pic

测试环境

单一主机,不同实例使用不同监听端口。

1、主库端口8001(TL=1)

/data01/digoal/pg_root8001

2、从库1端口8000(激活后,TL=2)

/data01/digoal/pg_root8000

3、PITR实例端口8002(使用restore_command,一直恢复到最新TL)

/data01/digoal/pg_root8002

4、从库1激活后,基于它创建的从库2端口8003(激活后,TL=3)

/data01/digoal/pg_root8003

5、归档目录

/data01/digoal/wal

环境变量

.bash_profile  
  
export PS1="$USER@`/bin/hostname -s`-> "      
export PGPORT=8001      
export PGDATA=/data01/digoal/pg_root$PGPORT      
export LANG=en_US.utf8      
export PGHOME=/home/digoal/pgsql11.1  
export LD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib:$LD_LIBRARY_PATH      
export DATE=`date +"%Y%m%d%H%M"`    
export PATH=$PGHOME/bin:$PATH:.      
export MANPATH=$PGHOME/share/man:$MANPATH      
export PGHOST=$PGDATA      
export PGUSER=postgres      
export PGDATABASE=postgres      
alias rm='rm -i'      
alias ll='ls -lh'      
unalias vi  

建主库

initdb -D $PGDATA -U postgres -E UTF8 --lc-collate=C --lc-ctype=en_US.utf8  

配置参数

postgresql.conf  
  
本例测试场景的关键配置  
  
listen_addresses = '0.0.0.0'  
port = 8001  
max_connections = 1000  
unix_socket_directories = '.,/tmp'  
max_worker_processes = 128  
wal_level = replica    
archive_mode = on     # 如果配置为always 则从库也归档日志   
archive_command = 'cp -n %p /data01/digoal/wal/%f'    # 拷贝WAL到指定目录, 包括激活时产生的 partial,history 文件,-n 避免重复拷贝  
max_wal_senders = 8  
hot_standby = on  
wal_receiver_status_interval = 1s  

配置HBA

pg_hba.conf  
  
确保流复制可用  
  
# "local" is for Unix domain socket connections only  
local   all             all                                     trust  
# IPv4 local connections:  
host    all             all             127.0.0.1/32            trust  
# IPv6 local connections:  
host    all             all             ::1/128                 trust  
# Allow replication connections from localhost, by a user with the  
# replication privilege.  
local   replication     all                                     trust  
host    replication     all             127.0.0.1/32            trust  
host    replication     all             ::1/128                 trust  

配置recovery.done

recovery.done  
  
  
restore_command = 'cp /data01/digoal/wal/%f %p'  
recovery_target_timeline = 'latest'  
standby_mode = on  
primary_conninfo = 'host=localhost port=8001 user=postgres'  

创建从库1

pg_basebackup -D /data01/digoal/pg_root8000 -F p -c fast -h 127.0.0.1 -p 8001 -U postgres  

配置参数

postgresql.conf  
  
port = 8000  

改recovery.done文件名

mv recovery.done recovery.conf  

启动从库1

pg_ctl start -D /data01/digoal/pg_root8000  

确保主从开始正常同步

查询主库  
  
postgres=# select * from pg_stat_replication ;  

所有实例(主,从)将wal归档到同一个ftp目录或NAS目录,方便PITR恢复时取文件。

备份主库

pg_basebackup -D /data01/digoal/pg_root8002 -F p -c fast -h 127.0.0.1 -p 8001 -U postgres  

压测主库

pgbench -i -s 100  
  
pgbench -M prepared -n -r -P 1 -c 16 -j 16 -T 30  

确保主从同步

查询主库,确保主从WAL完全已同步  
  
postgres=# select * from pg_stat_replication ;  

激活从库1

激活从库,从库变可读可写,脱离于主库的同步关系。

pg_ctl promote -D /data01/digoal/pg_root8000  

从库采用新时间线2

digoal@pg11-test-> pg_controldata -D /data01/digoal/pg_root8000|grep -i timeline  
Latest checkpoint's TimeLineID:       2  
Latest checkpoint's PrevTimeLineID:   2  
Min recovery ending loc's timeline:   0  

老的主库依旧在使用TL 1

digoal@pg11-test-> pg_controldata -D /data01/digoal/pg_root8001|grep -i timeline  
Latest checkpoint's TimeLineID:       1  
Latest checkpoint's PrevTimeLineID:   1  
Min recovery ending loc's timeline:   0  

激活时,产生的文件

pic

cd /data01/digoal/pg_root8000/pg_wal  
  
三个文件:  
  
patial  
  
history  
  
新时间线WAL文件  
  
如下:  
  
-rw------- 1 digoal digoal  42 Jan 20 18:11 00000002.history  
-rw------- 1 digoal digoal 16M Jan 20 18:11 000000010000000200000058.partial  
-rw------- 1 digoal digoal 16M Jan 20 18:11 000000020000000200000058  

主库上,有一个文件000000010000000200000058,是完整的。这里激活从库时,000000010000000200000058文件还没有写完,所以会生成一个000000010000000200000058.partial文件,与主库上的文件名不冲突,同时生成一个新时间线文件00000002.history,这个文件中记录了这个时间线是从哪个WAL位置开始生成的。

另外,新时间线下的WAL会从000000010000000200000058.partial中拷贝已写内容,并以新时间线重新命名,前8个字符以新时间线开始,由于时间线不一样,文件名与老的主库也不会冲突。

时间线文件内容

digoal@pg11-test-> cat 00000002.history  
1       2/58FE0B80      no recovery target specified  

老时间线下的partial wal,以及新时间线下的WAL文件

digoal@pg11-test-> md5sum 000000010000000200000058.partial  
163531112a802bb152e7a57c9b06d62d  000000010000000200000058.partial  
  
  
digoal@pg11-test-> md5sum 000000020000000200000058  
1b4e44ffa9e3f5025ddb260a1d117f5e  000000020000000200000058  

老主库的000000010000000200000058文件,由于后面还继续在写老主库,所以时间线1上,还有其他WAL文件产生,与TL2产生了WAL分歧。

digoal@pg11-test-> md5sum 000000010000000200000058  
e0193410858e5f0440d1a3f9404edcd4  000000010000000200000058  

注意,激活从库时,这个激活时的WAL文件,partial内容(部分内容,即在TL1上产生的WAL内容)在三个文件中是一致的。

从库1:

digoal@pg11-test-> pg_waldump 000000010000000200000058.partial|head -n 5  
rmgr: Heap2       len (rec/tot):     58/    58, tx:          0, lsn: 2/58000030, prev 2/57FFFFB8, desc: CLEAN remxid 130039429, blkref #0: rel 1663/13285/16515 blk 26035  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184523, lsn: 2/58000070, prev 2/58000030, desc: HOT_UPDATE off 114 xmax 130184523 ; new off 115 xmax 130184523, blkref #0: rel 1663/13285/16516 blk 48  
rmgr: Heap        len (rec/tot):     74/    74, tx:  130184538, lsn: 2/580000B8, prev 2/58000070, desc: HOT_UPDATE off 9 xmax 130184538 ; new off 88 xmax 0, blkref #0: rel 1663/13285/16515 blk 26035  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184530, lsn: 2/58000108, prev 2/580000B8, desc: HOT_UPDATE off 107 xmax 130184530 ; new off 116 xmax 0, blkref #0: rel 1663/13285/16516 blk 48  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184531, lsn: 2/58000150, prev 2/58000108, desc: HOT_UPDATE off 113 xmax 130184531 ; new off 117 xmax 0, blkref #0: rel 1663/13285/16516 blk 48  
digoal@pg11-test-> pg_waldump 000000020000000200000058|head -n 5  
rmgr: Heap2       len (rec/tot):     58/    58, tx:          0, lsn: 2/58000030, prev 2/57FFFFB8, desc: CLEAN remxid 130039429, blkref #0: rel 1663/13285/16515 blk 26035  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184523, lsn: 2/58000070, prev 2/58000030, desc: HOT_UPDATE off 114 xmax 130184523 ; new off 115 xmax 130184523, blkref #0: rel 1663/13285/16516 blk 48  
rmgr: Heap        len (rec/tot):     74/    74, tx:  130184538, lsn: 2/580000B8, prev 2/58000070, desc: HOT_UPDATE off 9 xmax 130184538 ; new off 88 xmax 0, blkref #0: rel 1663/13285/16515 blk 26035  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184530, lsn: 2/58000108, prev 2/580000B8, desc: HOT_UPDATE off 107 xmax 130184530 ; new off 116 xmax 0, blkref #0: rel 1663/13285/16516 blk 48  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184531, lsn: 2/58000150, prev 2/58000108, desc: HOT_UPDATE off 113 xmax 130184531 ; new off 117 xmax 0, blkref #0: rel 1663/13285/16516 blk 48  

老主库:

digoal@pg11-test-> pg_waldump 000000010000000200000058|head -n 5  
rmgr: Heap2       len (rec/tot):     58/    58, tx:          0, lsn: 2/58000030, prev 2/57FFFFB8, desc: CLEAN remxid 130039429, blkref #0: rel 1663/13285/16515 blk 26035  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184523, lsn: 2/58000070, prev 2/58000030, desc: HOT_UPDATE off 114 xmax 130184523 ; new off 115 xmax 130184523, blkref #0: rel 1663/13285/16516 blk 48  
rmgr: Heap        len (rec/tot):     74/    74, tx:  130184538, lsn: 2/580000B8, prev 2/58000070, desc: HOT_UPDATE off 9 xmax 130184538 ; new off 88 xmax 0, blkref #0: rel 1663/13285/16515 blk 26035  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184530, lsn: 2/58000108, prev 2/580000B8, desc: HOT_UPDATE off 107 xmax 130184530 ; new off 116 xmax 0, blkref #0: rel 1663/13285/16516 blk 48  
rmgr: Heap        len (rec/tot):     72/    72, tx:  130184531, lsn: 2/58000150, prev 2/58000108, desc: HOT_UPDATE off 113 xmax 130184531 ; new off 117 xmax 0, blkref #0: rel 1663/13285/16516 blk 48  

继续压测老主库,新主库

让老主库在TL1上,新主库在TL2时间线上,都继续产生WAL文件,在两个时间线上继续产生WAL文件。

PITR恢复验证,从备份恢复到新时间线

从TL1的备份集恢复,持续恢复到TL2。

机制:restore_command 如果发现history 文件,则优先拷贝partial文件,走新时间线分支。因此会采用000000010000000200000058.partial 而不会采用 000000010000000200000058

配置 recovery.conf 时,必须禁止primary_conninfo连接到老主库,否则会走TL1的时间线,并出现分歧后,走不到TL2这个时间线。只能使用restore_command来进行恢复,才能走到TL2的时间线。

原因很简单,因为使用流的方式,会把整个WAL文件都接收完,从而走上老路。而实际上应该使用TL2时间线开端(即激活时刻)的WAL文件(这个WAL文件只有部分内容是TL1的,剩余的内容是激活后产生的)。

recovery.conf  
  
  
restore_command = 'cp /data01/digoal/wal/%f %p'  
recovery_target_timeline = 'latest'  # 这一条一定要配置,才会走新时间线  
standby_mode = on  
# primary_conninfo = 'host=localhost port=8001 user=postgres'   # 注释这一条即可,或者改成指向新的主库,但是建议不要改,直接注释最好,因为主备可能经常切换。    

如果就只想恢复到老时间线怎么做?配置recovery_target_timeline即可。

什么情况下,会走错时间线?

1、流式恢复,并连接了老的主库

2、将老的主库的切换时的wal文件拷贝到了目标集的pg_wal目录(例如本例的000000010000000200000058文件)

配置PITR

cd /data01/digoal/pg_root8002  

配置参数

postgresql.conf  
  
port = 8002  

改recovery.done文件名

mv recovery.done recovery.conf  

修改内容

recovery.conf  
  
  
restore_command = 'cp /data01/digoal/wal/%f %p'  
recovery_target_timeline = 'latest'  # 这一条一定要配置,才会走新时间线  
standby_mode = on  
# primary_conninfo = 'host=localhost port=8001 user=postgres'   # 注释这一条即可,或者改成指向新的主库,但是建议不要改,直接注释最好,因为主备可能经常切换。    

启动PITR恢复实例

pg_ctl start -D /data01/digoal/pg_root8002  

可以看到PITR恢复实例,正确的恢复到了TL2时间线上。

创建新主库(之前的从库1)的从库2

创建从库2

pg_basebackup -D /data01/digoal/pg_root8003 -F p -c fast -h 127.0.0.1 -p 8000 -U postgres  

配置参数

postgresql.conf  
  
port = 8003  

改recovery.done文件名

mv recovery.done recovery.conf  
  
vi recovery.conf  
  
  
restore_command = 'cp /data01/digoal/wal/%f %p'  
recovery_target_timeline = 'latest'     # 这一条一定要配置,才会走新时间线  
standby_mode = on  
primary_conninfo = 'host=localhost port=8000 user=postgres'     

启动从库2

pg_ctl start -D /data01/digoal/pg_root8003  

确保主从开始正常同步

查询主库8000  
  
postgres=# select * from pg_stat_replication ;  

压测新主库(原从库1)

pgbench -M prepared -n -r -P 1 -c 16 -j 16 -T 120 -h 127.0.0.1 -p 8000  

确保新主库的从库同步正常

查询主库8000  
  
postgres=# select * from pg_stat_replication ;  

激活新主库的从库(即从库2)

pg_ctl promote -D /data01/digoal/pg_root8003  
  
时间线现在是TL3  

压测新主库的从库

pgbench -M prepared -n -r -P 1 -c 16 -j 16 -T 120 -h 127.0.0.1 -p 8003  

压测结束后,切换WAL文件,确保PITR使用restore_command可以恢复到最后的WAL

psql -h 127.0.0.1 -p 8003 -U postgres  
  
checkpoint;  
select pg_switch_wal();  
checkpoint;  
select pg_switch_wal();  

检查PITR恢复实例,是否恢复到了新新主库(即老的从库2)

digoal@pg11-test-> psql -h 127.0.0.1 -p 8003  
psql (11.1)  
Type "help" for help.  
  
postgres=# select count(*) from pgbench_history ;  
  count    
---------  
 9319998  
(1 row)  
  
postgres=# \q  
digoal@pg11-test-> psql -h 127.0.0.1 -p 8002  
psql (11.1)  
Type "help" for help.  
  
postgres=# select count(*) from pgbench_history ;  
  count    
---------  
 9319998  
(1 row)  

完全一致

查看TL,也已经到3了。

digoal@pg11-test-> pg_controldata -D /data01/digoal/pg_root8002|grep -i timeline  
Latest checkpoint's TimeLineID:       2  
Latest checkpoint's PrevTimeLineID:   2  
Min recovery ending loc's timeline:   3  
  
digoal@pg11-test-> pg_controldata -D /data01/digoal/pg_root8003|grep -i timeline  
Latest checkpoint's TimeLineID:       3  
Latest checkpoint's PrevTimeLineID:   3  
Min recovery ending loc's timeline:   0  
  
  
PITR实例,做完检查点就打通了  
  
digoal@pg11-test-> psql -h 127.0.0.1 -p 8002  
psql (11.1)  
Type "help" for help.  
  
postgres=# checkpoint;  
CHECKPOINT  
postgres=# \q  
digoal@pg11-test-> pg_controldata -D /data01/digoal/pg_root8002|grep -i timeline  
Latest checkpoint's TimeLineID:       3  
Latest checkpoint's PrevTimeLineID:   3  
Min recovery ending loc's timeline:   3  

小结

如何让PITR实例可以一直跟到最新时间线

1、recovery.conf 使用restore command , 不要使用 stream

2、激活后,尽快归档history文件。

什么情况下,会走错时间线?

1、流式恢复,并连接了老的主库

2、将老的主库的切换时的wal文件拷贝到了目标集的pg_wal目录(例如本例的000000010000000200000058文件)

如果就只想恢复到老时间线怎么做?

配置recovery_target_timeline即可。 

参考

《PostgreSQL standby recover的源码分析 (walreceiver唤醒时机? 为什么standby crash后walreceiver不会立即被唤醒?)》

《PostgreSQL recovery target introduce》

《PostgreSQL 时间点恢复(PITR)时查找wal record的顺序 - loop(pg_wal, restore_command, stream)》

 

免费领取阿里云RDS PostgreSQL实例、ECS虚拟机

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
弹性计算 关系型数据库 数据库
搭建PostgreSQL主从架构
PostgreSQL是一个关系型数据库管理系统(RDBMS),支持NoSQL数据类型(JSON/XML/hstore)。本教程介绍如何在两台CentOS 7操作系统的ECS实例上搭建PostgreSQL主从架构。
|
关系型数据库 数据库 PostgreSQL
PostgreSQL环境搭建和主备构建 2
PostgreSQL环境搭建和主备构建
371 0
|
26天前
|
Oracle 关系型数据库 数据库
【赵渝强老师】PostgreSQL中的模式
在PostgreSQL中,所有数据库对象均隶属于模式,包括表、索引、视图等,每个对象有唯一的oid标识。创建数据库时,默认生成名为“public”的Schema。用户可自定义模式,如通过SQL语句创建名为demo的模式及其下的表。与Oracle不同,PostgreSQL中用户和模式不是一一对应关系。
58 12
【赵渝强老师】PostgreSQL中的模式
|
5月前
|
分布式计算 DataWorks 关系型数据库
DataWorks操作报错合集之使用连接串模式新增PostgreSQL数据源时遇到了报错"not support data sync channel, error code: 0001",该怎么办
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
7月前
|
Oracle 关系型数据库 分布式数据库
PolarDB for PostgreSQL报错问题之跨节点执行报错如何解决
PolarDB for PostgreSQL是基于PostgreSQL开发的一款云原生关系型数据库服务,它提供了高性能、高可用性和弹性扩展的特性;本合集将围绕PolarDB(pg)的部署、管理和优化提供指导,以及常见问题的排查和解决办法。
|
SQL 存储 Oracle
19 PostgreSQL 锁类型,锁模式,锁冲突,死锁检测的介绍|学习笔记
快速学习19 PostgreSQL 锁类型,锁模式,锁冲突,死锁检测的介绍
19 PostgreSQL 锁类型,锁模式,锁冲突,死锁检测的介绍|学习笔记
|
SQL 关系型数据库 Shell
PostgreSQL环境搭建和主备构建 1
PostgreSQL环境搭建和主备构建
100 0
|
SQL XML 弹性计算
【ECS生长万物之开源】搭建PostgreSQL主从架构
PostgreSQL被业界誉为最先进的开源数据库,支持NoSQL数据类型(JSON/XML/hstore)。本文档介绍在CentOS 7操作系统的ECS实例上搭建PostgreSQL主从架构的操作步骤。
|
SQL 存储 关系型数据库
PostgreSQL 流复制搭建主从环境,同步和异步的解释,压力测试,主从角色切换|学习笔记
快速学习PostgreSQL 流复制搭建主从环境,同步和异步的解释,压力测试,主从角色切换
PostgreSQL 流复制搭建主从环境,同步和异步的解释,压力测试,主从角色切换|学习笔记
|
SQL 存储 关系型数据库
9 PostgreSQL 点对点多主表级复制-触发器篇|学习笔记
快速学习9 PostgreSQL 点对点多主表级复制-触发器篇
9 PostgreSQL 点对点多主表级复制-触发器篇|学习笔记