PostgreSQL复制槽实战

1 引言

看下官网的解释,复制槽提供了一种办法确保主库不会“删除”还未发送到备库的WAL日志,也不会删除备库需要的多版本,即使备库掉线。

2 相关参数

wal_keep_segments:设置为较大值,保证pg_wal目录下保留较多的wal日志,主库上wal日志留存越多,允许备库宕机的时间越长,设置此参数需要注意不要将pg_wal目录撑满。或者在主库上开启归档,如果没有足够的硬盘空间保留wal归档,至少在备库停机维护时临时开启主库归档。如果备库落后主库wal_keep_segments数量的wal,则主库可能会删除备用服务器仍需要的wal,这种情况下,流复制就会中断。

hot_standby_feedback:备库定时将最小活跃事务ID(xmin)告诉master,使得 master在执行vacuum 时对备库还需要的tuple手下留情,但这样可能会导致主库膨胀,在每个wal_receiver_status_interval定义的周期内发送的频率不超过一次,并且此设置不会覆盖在主数据库上的old_snapshot_threshold行为。

max_standby_streaming_delay:通常会将一些执行时间较长的分析任务、统计SQL跑在备库上。在备库上执行长时间的查询,由于涉及的记录有可能在主库上被更新或删除,主库上对更新或删除数据的老版本进行vacuum后,从库上也会执行这个操作,从而与从库上的当前查询产生冲突。此参数默认为30s,当备库执行SQL时,有可能与正在应用的WAL发生冲突,此查询如果30s没有执行完就被中止,注意30s不是备库上单个查询允许的最大执行时间,是指当备库上应用WAL时允许的最大WAL延迟应用时间,因此备库上查询的执行时间有可能不到这个值就被中止了,此参数可以设置为-1,表示当从库上的WAL应用进程与从库上执行的查询冲突时,WAL应用进程一直等待直到从库查询执行完成。

old_snapshot_threshold:单位为min,最大可以设置为60天,当vacuum回收垃圾时,遇到垃圾记录的xmax大于数据库中现存的最早未提交事务xmin时,不会对其进行回收。因此当数据库中存在很久为结束的事务时,可能会导致数据库膨胀。此参数代表强制删除为过老的事务快照保留的死元组。这会导致长事务读取已被删除的tuple时出错。

vacuum_defer_cleanup_age:指定vacuum延迟清理死亡元组的事务数,vacuum会延迟清除无效的记录,延迟的事务个数通过vacuum_defer_cleanup_age进行设置。默认为0,在主库上设置一个稍大的值也可以减少冲突的发生,但是并不太好计量。

max_standby_archive_delay:备机因为处理归档的wal日志产生查询冲突而取消查询之前的等待时间,和上面的参数类似。

recovery_min_apply_delay:延迟备库设置备库延迟重做WAL的时间,而备库依然及时接收主库发送的WAL日志流,只是不是一接收到WAL后就立即应用,而是等待此参数设置的值再应用。使用此功能将延迟hot_standby_feedback,当synchronous_commit设置为remote_apply时,同步复制也会受此设置的影响,每个commit都需要等待。

3 为啥要设复制槽

前面所说的那么多参数,只有在主备关系正常时才能起到作用,而replication slot能够确保在主备断连后主库的wal仍不被清理,因为replication slot的状态信息是持久化保存的,即便从库断掉或主库重启,这些信息仍然不会丢掉或失效。

replication slots 是从postgresql 9.4引入的,主要是提供了一种自动化的方法来确保主库在所有的备库收到wal之前不会移除它们,并且主库也不会移除可能导致恢复冲突的行(需要配合hot_standby_feedback),即使备库断开也是如此。

在没有启用replication slots的环境中,如果碰到 ERROR: requested WAL segment xxxx has already been removed 的错误,解决办法是要么提前开启了归档,要么重做slave,另外还可以在主库上设置wal_keep_segments 为更大的值。当然,如果备库停机时间太长,可能主库的WAL日志目录会被撑满,如果设置了复制槽,建议将WAL日志目录放在大容量硬盘上。

4 实战演示

4.1 hot_standby_feedback

流复制搭建就不演示了,此处基于PostgreSQL12搭建的,在此就不再赘述,只是要注意原有的recovery.conf没有了,合到了postgresql.conf里面。

postgres=# select usename,client_addr,backend_xmin,sync_state from pg_stat_replication ;

我把hot_standby_feedback主从都设为了off,查询pg_replication_slots可以看到xmin为空

postgres=# select * from pg_replication_slots ;

模拟一下冲突的发生,先创建一张测试表test,插入4千万数据:

postgres=# insert into test values(generate_series(1,40000000));

看下vacuum里的逻辑,

/*

该函数计算当前tuple的xmax是否大于或等于OldestXmin。xmax是删除这个tuple的事务ID,而OldestXmin由GetOldestXmin函数计算,是所有活跃事务的ID,以及所有事务的xmin 组成的集合中最小的事务ID。所有ID大于这个OldestXmin的事务,都是“新近”开启的事务,其他事务可能需要读取这个旧版本用于查询,所以不能物理删除,则返回HEAPTUPLE_RECENTLY_DEAD,保留此tuple。换句话说,就是产生垃圾tuple的事务号,通常在为垃圾tuple的头信息中的xmax版本号大于或等于vacuum开启时数据库中最小的(backend_xmin, backend_xid),这条垃圾tuple就不能被回收,我们可以在pg_stat_activity视图中看到这两列,

postgres=# \d pg_stat_activity

对于repeatable read与serializable隔离级别的事务来说,每一个查询开始时需要获取一个快照,对于read committed隔离级别的事务来说,事务中每条查询开始都会重新获取一个快照,快照中将包含一个xmin值,表示当前数据库中最小的正在运行的事务号,如果没有,则为最小未分配事务号。快照xmin与事务申请的事务号有别,xid表示该事务申请的事务号。PostgreSQL9.6之后可以通过old_snapshot_threshold来强制删除为过老的事务快照保留的死元组。

了解了vacuum的逻辑后,我们知道假如session 1查询某行数据,session 2删除该数据,然后commit,执行一次vacuum,我们知道这次vacuum并不会删除该行数据,因为session 1的事务还需要使用该元组,所以不会清理该元组。那么如果是主从呢?主库在准备进行vacuum时怎么知道从库还在进行查询,这就是hot_standby_feedback的意义,设置hot_standby_feedback参数之后备库会定期向主库通知最小活跃事务id(xmin),这样使得主库vacuum进程不会清理大于xmin值的事务。但是假如主备之间的网络突然中断,备库就无法向主库正常发送xmin值,如果时间够长,主库在这段时间内还是会清理无用元组,这样网络恢复后就可能发生上面的冲突ERROR:canceling statement due to confilct with recovery。

把主从的hot_standby_feedback都设为on,再来模拟一下,我们可以看到pg_replication_slots的xmin一列有值了:

postgres=# select * from pg_replication_slots ;

恢复一下环境:

postgres=# drop table test;

4.2 restart_lsn****恢复一下环境:

postgres=# \d pg_replication_slots

对于restart_lsn的官方解释是The address (LSN) of oldest WAL which still might be required by the consumer of this slot and thus won’t be automatically removed during checkpoints. NULL if the LSN of this slot has never been reserved,意思就是主库checkpoint的时候不会删除这之后的wal日志,以及过早的归档出去,为备库保留着。

postgres=# \d pg_replication_slots

模拟一下,没有复制槽的情况下:

postgres=# select pg_drop_replication_slot('slot1');

再启动备机,可以看到流复制关系断了,看下日志:

postgres=# select client_addr,sync_state from pg_stat_replication ;

恢复下环境,使用复制槽,看下情况:

postgres=# select client_addr,sync_state from pg_stat_replication ;

在备库上查一下是否有这些表,可以看到,流复制正常:

postgres=# \d

5 结论

1.复制槽防止备库需要的wal日志在主库被删除,主库会根据备库返回的信息确认哪些wal日志已不再需要,才能进行清理。

2.当允许应用连接从库做只读查询时,复制槽可以与参数hot_standby_feedback配合使用,使主库的vacuum操作不会过早的清掉从库查询需要的记录,而出现如下错误:ERROR: canceling statement due to conflict with recovery

但是也要注意几点:

1.如果收不到从库的reply,复制槽的状态restart lsn会保持不变,造成主库会一直保留本地日志,可能导致日志磁盘满。所以应该实时监控日志磁盘使用情况,并设置较小的wal_sender_timeout,默认为60s,及早发现从库断掉的情况。

2.将hot_standby_feedback设为on时,如果从库长时间有慢查询发生,可能导致发回到主库的xmin变化较慢,主库的vaccum操作停滞,造成主库被频繁更新的表大小暴增,导致严重的表膨胀。

针对措施:

1.可以增加wal日志个数的监控,当wal日志数量超过正常值告警。

2.做好对每个复制槽同步状态的监控,出现某个槽同步状态异常要及时处理,同步异常会造成lsn不向前推进,导致wal堆积。

3.对于业务很空闲但是数据需要同步的库,可以自定义脚本,定期更新无用表,手工推进lsn。

4.如果wal日志已经堆积很多磁盘马上要爆炸的情况下,在考虑应急删掉复制槽之前要评估剩余空间是否还有足够富余,因为即使删掉复制槽,wal日志也不是马上就会清理,删掉后主库vacuum也会产生较多xlog日志,一定要做好评估。

5.增加pg_replication_slot()视图中restart_lsn的监控,对于落后较大和长期不推进的lsn进行告警。

最后引用一下微信群分享的PostgreSQL流复制图片:


免责声明:

1、本站资源由自动抓取工具收集整理于网络。

2、本站不承担由于内容的合法性及真实性所引起的一切争议和法律责任。

3、电子书、小说等仅供网友预览使用,书籍版权归作者或出版社所有。

4、如作者、出版社认为资源涉及侵权,请联系本站,本站将在收到通知书后尽快删除您认为侵权的作品。

5、如果您喜欢本资源,请您支持作者,购买正版内容。

6、资源失效,请下方留言,欢迎分享资源链接

文章评论

0条评论