PostgreSQL WAL 文件数量长期持续增加问题排查

1. 提出问题

最近遇到一个自己运维 DB 的开发同学反馈:"我们的业务量较小,但现在数据库积累了大量的 WAL, 一个月的时间 pg_wal 目录和归档的目录均超过了80GB, 且 WAL 增长的速度较快,由于虚拟机的磁盘不大,几乎把磁盘撑爆了,我应该怎么处理呢?可以先人工删一下吗?”

针对这个现象,我们需要分析如下几个问题:

  • 几乎没有业务为什么 WAL 还会增加且增加的还很快?
  • WAL 会一直增加直至把磁盘撑爆吗?DBMS 有参数控制上限么?
  • WAL 文件为什么会保留那么多, DBMS 难道不会自动清理, 需要人工介入么?

2. 分析问题

因为 PostgreSQL 不同版本之间参数有差异,所以确认运行环境是排查问题的第一步。

经确认, 出现这一现象的 DB 版本是 PostgreSQL 11,那么针对 pg_wal 目录下文件数量和尺寸的问题,我们首先明确如下几个相关参数的含义:

  • wal_segment_size:单个 WAL 文件的大小,默认为16MB,一般不做更改,且在 pg11 之前,只能在编译 pg 时指定,很明显这个参数的影响可以排除。

  • wal_keep_segments:这个参数配置的是 standby 复制所需的在 pg_wal 目录中最少保留的 WAL 文件的数目,一般情况下,这大概就是 wal 尺寸范围,如果这个参数配置为0,那么此时 WAL 文件的数量还取决于如下其他参数。

  • min_wal_size:这个是 WAL 文件保留的最小尺寸,当 WAL 的使用低于这个配置,那么 WAL 将会在 checkpoint 时候对 WAL 以 recycle 的形式处理,而不是直接 remove。当然这个参数也是有配置范围限制的:min_wal_size 至少是 wal_segment_size 的2倍。

  • max_wal_size:这个参数指定是 checkpoint 和 checkpoint 之间产生的 WAL 最大量。当然这只是一个软限制,在集群负载高或者一些其他异常情况下可能会超过这个值。结合 checkpoint_completion_target 参数, 当新产生的 wal 文件到达特定的数据量后便会触发 checkpoint。

  • archive_timeout:如果 DB 实例写入量很少,到达 archive_timeout 会触发 WAL 文件的强制切换。因此如果 archive_timeout 如果太短就会产生很多新的 WAL,从而产生大量归档。

除了这些参数配置外,还有一些异常情况会造成 WAL 堆积:

  • 如果开启了归档,那么归档命令成功了,WAL 才会被 remove/recycle,所以这里需要注意,如果归档命令是失效的,那么 pg_wal 目录会一直增长,不会自动删除 WAL,此目录会持续增长,进而有撑爆磁盘的风险。

  • 在创建了复制槽后,如果通过复制槽不能正常消费掉,则会造成 WAL 堆积,导致主库的 WAL 文件数量一直增加,pg_wal 目录也会一直增长。

3. 解决问题

根据上述参数描述和分析,我们首先检查 DB 实例的上述参数。

postgres=# select version();
                                                               version
-------------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 10.12 (Ubuntu 10.12-2.pgdg18.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0, 64-bit
(1 row)
postgres=# show wal_keep_segments ;
 wal_keep_segments
-------------------
 8
(1 row)
postgres=# show min_wal_size ;
 min_wal_size
--------------
 80MB
(1 row)
postgres=# show max_wal_size ;
 max_wal_size
--------------
 1GB
(1 row)
postgres=# show archive_timeout;
 archive_timeout
-----------------
 30min
(1 row)
postgres=# select * from pg_stat_get_archiver();
 archived_count |    last_archived_wal     |      last_archived_time       | failed_count | last_failed_wal | last_failed_time |          stats_reset
----------------+--------------------------+-------------------------------+--------------+-----------------+------------------+-------------------------------
            164 | 000000990000000300000058 | 2022-04-11 07:47:24.490616+00 |            0 |                 |                  | 2022-04-09 17:09:57.660584+00
(1 row)

可见,wal_keep_segments 值为128,但 archive_timeout 设置较小,这就回答了“几乎没有业务,为什么 WAL 还会增加且增加的很快”这个问题。建议调大 archive_timeout 参数来降低 WAL 文件的强制切换频率,WAL 产生过快的问题得以优化。

pg_wal 目录的文件轻易不要人工处理,但归档目录需要定期将历史 WAL 备份走然后删掉,在磁盘紧张的情况下可以先将归档这部分处理掉。

继续观察,由于 failed_count 是0, 且同时检查 DB log 没有任何异常信息,可以排除归档失败导致的 pg_wal 目录一直增加。

通过上述信息及分析可断定 WAL 堆积不是由于 wal_keep_segments 参数配置过大或归档失败导致,接下来我们重点分析下 pg_replication_slots。


postgres=# select * from pg_replication_slots ;
         slot_name         | plugin | slot_type | datoid | database | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn
---------------------------+--------+-----------+--------+----------+-----------+--------+------------+------+--------------+-------------+---------------------
 harbor_database_cluster_0 |        | physical  |        |          | f         | f      |      10586 |      |              | 0/D7000000  |
 harbor_database_cluster_2 |        | physical  |        |          | f         | f      |      10585 |      |              | 0/D7000000  |
(2 rows)

首先这个复制槽的激活状态"active = f",可以确定其不是被某些进程正在使用的此复制槽。

接下来我们查看 restart_lsn 对应的 WAL 文件名。

postgres=# select pg_walfile_name('0/D7000000');
     pg_walfile_name
--------------------------
 0000001200000000000000D6
(1 row)

所以从这里我们基本上可以判断 WAL 保留这么多是物理槽占用导致的。

此外,我们继续确认下当前 wal_lsn 和上面开始堆积的 lsn 差异是不是和现在的 pg_wal 尺寸吻合呢?

postgres=# select pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),'0/D7000000'));
 pg_size_pretty
----------------
 0 bytes
(1 row)

这里的总尺寸也对上了!

问题原因找到了,那么接下来就可以对症下药。首先确认下这个复制槽是干什么用的,如果这个复制槽有用,那么把这些堆积的 WAL 消费掉就可以了,如果这个复制槽没啥用,那么把可以直接 drop 掉这个物理复制槽。

popostgres=# select pg_drop_replication_slot('harbor_database_cluster_0');

 pg_drop_replication_slot

--------------------------


(1 row)
popostgres=# select pg_drop_replication_slot('harbor_database_cluster_2');

 pg_drop_replication_slot

--------------------------


(1 row)

正常情况下,数据库在做 checkpoint 时或在数据库重启时会随着 recovery 的 checkpoint 而自动 remove/recycle。那么接下来,我们选择手动执行一个 checkpoint 或等待下一个系统自动的 checkpoint 后 pg_wal 目录的尺寸就会降下来。

postgres=# checkpoint;

CHECKPOINT

postgres=# \q

4. 结论

综述,本案例情形已经明朗:

  • 几乎没有业务的数据库 WAL 量也会增加是因为周期性强制切换 WAL,如果想要 WAL 切换速度降低,我们可以适当调大 archive_timeout,比如从1min 增大为10min 甚至更长时间

  • DBMS 有参数控制 WAL 的增长幅度和总尺寸,但是很多都是软限制,而非强制。出现异常的情况下,pg_wal 确实是会无限膨胀下去将磁盘撑爆,DBMS 无法自动处理,需要人工介入。

  • 产生 WAL 堆积在排除参数配置且归档成功的情况下,是因为物理复制槽占用导致。可见在此极端情况下,WAL 文件确实会一直堆积,进而出现占满磁盘的情况,导致 DB 实例异常 shutdown 而产生故障。

DBMS 正常情况下是可以对 WAL 进行自动 remove/recycle,如果出现 WAL 清理不掉的情况,且参数无可疑之处,多半是其他原因引起的,需要仔细分析原因,切忌“头痛医头脚痛医脚”。比如此例中是因为废弃的物理复制槽导致,其实废弃的逻辑复制槽亦可导致此问题。

你可能感兴趣的:(postgresql)