暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

万万没想到,PostgreSQL备库把主库干翻了!

呆呆的私房菜 2024-11-06
282
    Whoami:5年+金融、政府、医疗领域工作经验的DBA
    Certificate:PGCM、OCP、YCP
    Skill:Oracle、Mysql、PostgreSQL、国产数据库
    Platform:CSDN、墨天轮、公众号(呆呆的私房菜)


    阅读本文可以了解PostgreSQL主备同步存在的“坑“,帮助读者在运维工作中规避生产故障!


    01

    主备概述
    • PostgreSQL的物理备库是基于流复制实现的,意味着备库在物理层面与主库完全一致,每个数据块都一致,但是,主备读写分离集群同时也带来了备库查询冲突的问题,只读操作可能和恢复会发生冲突。例如某个用户正在读取备库某个数据块的数据,与此同时,实时恢复进程读取到wal的记录,发现需要修改这个数据块的数据,此时恢复就与只读发生了冲突。


    02

    主备配置
    • 为了避免备库冲突问题,我们可以通过调整如下数据库配置进行优化,当然我们也要考虑到调整参数带来的代价。

    • 一、主库配置

    参数描述
    vacuum_defer_cleanup_age设置主库垃圾回收的延迟,默认为0。

    🔺调整 vacuum_defer_cleanup_age 参数可能带来的影响:

    • 1. 引发主库膨胀,因为垃圾版本要延迟若干个事务后才能回收;

    • 2. 重复扫描垃圾版本,重复耗费垃圾回收进程的CPU资源;

    • 3. 如果期间大量垃圾版本在事务到达并解禁后,会产生爆炸性回收,产生大量的wal日志,从而造成wal的写IO问题。


    • 二、备库配置:

    参数描述
    hot_standby_feedback

    如果设置为ON,备库在执行QUERY时会通知主库,哪些版本需要被保留,不能被VACUUM

    max_standby_archive_delay备库在放弃查询前允许等待归档的最大延迟时间
    max_standby_streaming_delay备库在放弃查询前允许主库发送wal日志的最大延迟时间

    🔺调整 hot_standby_feedback 参数带来的影响:

    • 1. 如果备库出现了long query,或者repeatable read的长事务,并且主库对备库还需要或正查询的数据执行了更新并产生垃圾时,主库会保留这部分垃圾版本;

    • 2. 代价与 vacuum_defer_cleanup_age 参数的代价一样;

    🔺调整 max_standby_archive_delay 和 max_standby_streaming_delay 参数带来的影响:

    • 1. 如果备库的查询与应用恢复进程冲突,那么备库的应用恢复进程会出现延迟,也许从备库读到的是X秒前的数据。

    03

    案例分享
      备库配置:
      postgres=# show hot_standby_feedback;
      hot_standby_feedback
      ----------------------
      on
      (1 row)


      主库配置:设置较小的唤醒时间以及垃圾回收阈值
      postgres=# show autovacuum_naptime;
      -[ RECORD 1 ]------+---
      autovacuum_naptime | 1s


      postgres=# show autovacuum_vacuum_scale_factor;
      -[ RECORD 1 ]------------------+-------
      autovacuum_vacuum_scale_factor | 0.0002


      1. 创建表并插入数据
      postgres=# create table tt1(id int, name text, current timestamp);
      CREATE TABLE
      postgres=# insert into tt1 select 1, md5(random()::text), now() from generate_series(1,10000000);
      INSERT 0 10000000


      2. 在备库上开启一个repeatable read事务,,执行查询
      postgres=# begin transaction isolation level repeatable read;
      BEGIN
      postgres=# select count(*) from tt1;
      count
      ----------
      10000000
      (1 row)


      3. 主库执行更新tt1表
      postgres=# update tt1 set name = 'name_test';


      4. 查询tt1表当前统计信息,有1000w条dead tuple
      postgres=# select * from pg_stat_all_tables where relname = 'tt1';
      -[ RECORD 1 ]-------+------------------------------
      relid | 41343
      schemaname | public
      relname | tt1
      seq_scan | 1
      seq_tup_read | 10000000
      idx_scan |
      idx_tup_fetch |
      n_tup_ins | 10000000
      n_tup_upd | 10000000
      n_tup_del | 0
      n_tup_hot_upd | 0
      n_live_tup | 10000000
      n_dead_tup | 10000000
      n_mod_since_analyze | 0
      last_vacuum |
      last_autovacuum | 2024-11-05 00:01:52.069311+08
      last_analyze |
      last_autoanalyze | 2024-11-04 23:29:39.84811+08
      vacuum_count | 0
      autovacuum_count | 47
      analyze_count | 0
      autoanalyze_count   | 2
      • 影响:读IO非常大(扫描tt1表,试图回收垃圾,但是回收不成功),同时autovacuum worker的CPU开销很大,极端情况下CPU会占满。

      • 处理方案:设置备库参数 hot_standby_feedback = off

        -- 关闭从库hot_standby_feedback 
        postgres=# alter system set hot_standby_feedback = off;
        ALTER SYSTEM


        pg_ctl reload
        server signaled


        -- 垃圾已经被回收
        postgres=# select * from pg_stat_all_tables where relname = 'tt1';
        -[ RECORD 1 ]-------+------------------------------
        relid | 41343
        schemaname | public
        relname | tt1
        seq_scan | 1
        seq_tup_read | 10000000
        idx_scan |
        idx_tup_fetch |
        n_tup_ins | 10000000
        n_tup_upd | 10000000
        n_tup_del | 0
        n_tup_hot_upd | 0
        n_live_tup          | 10000000
        n_dead_tup | 0
        n_mod_since_analyze | 0
        last_vacuum |
        last_autovacuum | 2024-11-05 00:10:23.788943+08
        last_analyze |
        last_autoanalyze | 2024-11-04 23:29:39.84811+08
        vacuum_count | 1
        autovacuum_count | 59
        analyze_count | 0
        autoanalyze_count | 2


        postgres=# select count(*) from tt1;
        FATAL: terminating connection due to conflict with recovery
        DETAIL: User query might have needed to see row versions that must be removed.
        HINT: In a moment you should be able to reconnect to the database and repeat your command.
        server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
        The connection to the server was lost. Attempting reset: Succeeded.
        • autovacuum worker不会再被唤醒,所以主库的CPU马上下来了,当然还有需要关注的是垃圾回收会带来一次很大的wal写IO。


        04

        优化建议
        • 1. 不建议设置vacuum_defer_cleanup_age > 0;
        • 2. 如果备库有长时间查询,同时需要实时性,可以设置hot_standby_feedback = on,同时建议将主库的autovacuum_naptime,autovacuum_vacuum_scale_factor设置为较大值(例如60秒,0.1),主库的垃圾回收唤醒间隔会长一点,但是如果突然产生很多垃圾,可能会造成一定的膨胀;
        • 3. 如果备库有长时间查询,并且没有很高的实时性要求,建议设置设置hot_standby_feedback = off, 同时设置较大的max_standby_archive_delay和 max_standby_streaming_delay。
        • 4. 关注备库是否使用了slot,有的话也可能导致主库wal堆积,导致主库的dead tuple不清理(对应slot备库需要的不清理)。



        本文内容就到这啦,阅读完本篇,相信你对PostgreSQL的主从同步注意点有了一定的了解了吧!我们下篇再见!


        点击上方公众号,关注我吧!

        文章转载自呆呆的私房菜,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论