PostgreSQL磁盘占用上涨:WAL与日志排查

PostgreSQL 所在磁盘越来越满,不一定是业务表变大。持续写入、失败的 WAL 归档、停滞的复制任务和重复报错,都可能占用空间。先找出增长来自哪里,再决定处理方法。

本文面向 PostgreSQL 16 的自建 Linux 实例,SQL 由有权检查实例的数据库管理员执行。检查命令不删除数据;容量统计仍会产生文件系统访问,大实例应分步执行,不要每秒循环扫描。已经接近满盘时,先停止非必要导入、批处理和备份写入,评估扩容,不要先跑全库整理。

先找对数据目录和所在磁盘

在 Ubuntu 软件包安装的实例上,可用下面的方法读取实际数据目录。5432 按实际数据库端口替换;连接失败或输出为空时,先修正连接,不继续套用默认目录:

pg_data_path=$(sudo -u postgres psql -X -At -p 5432 -d postgres -c 'SHOW data_directory;')
printf '%s\n' "$pg_data_path"
sudo df -h "$pg_data_path"
sudo df -i "$pg_data_path"
sudo readlink -f "$pg_data_path/pg_wal"

df -h看容量,df -i看 inode。pg_wal可能连接到其他磁盘,需按最后一条返回的真实路径再检查该盘,并统计一次目录大小:

pg_wal_path=$(sudo readlink -f "$pg_data_path/pg_wal")
sudo df -h "$pg_wal_path"
sudo du -sh "$pg_wal_path"

再进入管理员 psql 会话,下面各节 SQL 都在这里执行:

sudo -u postgres psql -X -p 5432 -d postgres
SELECT pg_is_in_recovery();
SELECT spcname, pg_tablespace_location(oid) AS location
FROM pg_tablespace;

表空间有非空路径时,也检查对应文件系统。默认表空间返回空路径是正常的。pg_is_in_recovery()为 true 表示备库或恢复中实例;下面使用当前 WAL 写入位置的复制槽查询只在返回 false 的主库执行。

pg_wal很大:检查谁还需要这些文件

WAL 是事务恢复和复制所需的预写日志,不能当普通文本日志删除。即使文件很旧,也不能手工清空 pg_wal。先在主库检查保留设置与复制槽:

SHOW max_wal_size;
SHOW wal_keep_size;
SHOW max_slot_wal_keep_size;
SELECT slot_name, slot_type, active, wal_status,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn))
         AS retained_lsn_span
FROM pg_replication_slots
ORDER BY restart_lsn NULLS LAST;

max_wal_size不是硬性占用上限。复制槽、归档失败或较大的保留设置,都可能让 WAL 超过这个值。retained_lsn_span是当前位置到该槽最早需求位置的跨度,多个槽可能重叠,不能相加当成磁盘用量;空值也不能直接当成零。

active=false只说明此刻没有消费者使用该槽,不代表它已经废弃。先确认它属于哪个备库、订阅或备份工具,再恢复下游连接。只有确定下游永久停用并评估重建影响后,才另行安排移除。调小 max_slot_wal_keep_size也可能导致下游缺失所需 WAL,不能当作立即腾盘的按钮。

启用了归档,就看最近是否仍在失败

SHOW archive_mode;
SELECT archived_count, last_archived_wal, last_archived_time,
       failed_count, last_failed_wal, last_failed_time, stats_reset
FROM pg_stat_archiver;

结合当前时间、日志和前后两次统计判断:失败次数是否继续增加,最近成功归档时间是否推进。累计失败数不等于故障仍在发生,最近成功文件之前的所有文件也不能据此推定都已归档。

常见原因是归档目标空间不足、权限改变、网络断开或归档程序退出异常。先修复具体原因,再观察归档继续推进。不要把归档命令改成“什么都不做但返回成功”,这会破坏以后按时间恢复需要的 WAL 链。

普通日志与业务表分别检查

查看日志由谁接管,以及采集器当前使用的文件:

SHOW logging_collector;
SHOW log_destination;
SHOW log_directory;
SELECT pg_current_logfile();

pg_current_logfile()返回空值,不代表没有日志;日志可能由启动程序、systemd 或 syslog 接管。相对日志路径按数据目录解析。若日志持续高速增长,先处理反复出现的报错,再检查实际日志轮转和保留策略。只处理已确认过期且不再写入的普通日志,不能据文件名相似去清理 WAL;systemd 日志查询可参考journalctl 教程。

WAL 和普通日志解释不了增长时,按数据库容量继续定位:

SELECT datname, pg_size_pretty(pg_database_size(oid)) AS database_size
FROM pg_database
ORDER BY pg_database_size(oid) DESC;

在 psql 中用 \connect appdb连接实际业务库,再查占用较大的表。这里的 appdb是示例名称:

SELECT schemaname, relname,
       pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
       pg_size_pretty(pg_indexes_size(relid)) AS index_size,
       n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;

total_size已包含索引和 TOAST 等占用,不再与索引大小相加。行数统计是估算值;分区表要看各子表,不能只看父表。大量删除后空间未下降,可能是旧行版本仍待回收或空闲页留在表内复用,不要仅凭文件大就认定数据库损坏。

按增长原因安排处理

确认的问题 处理方向
归档或复制停滞 恢复归档目标、网络或消费者,再观察保留量与可用空间变化。
错误日志持续增长 修复重复报错,检查真实日志轮转与保留周期。
业务数据正常增加 按增长速度安排容量,或制定经过业务确认的数据保留方案。
表内旧版本堆积 检查长事务和 autovacuum 进展,在有余量时安排维护。

普通 VACUUM 通常主要让空间供表内复用,不保证操作系统空闲容量马上增加。VACUUM FULL 会重写表,需要额外空间并持有排他锁,满盘时不应拿它急救,也不要在高峰期批量重建索引。

处理后记录可用容量、WAL 大小、归档或复制进度,并在相近负载下再次检查。需要迁移或重建前,先确认PostgreSQL 备份及恢复验证已完成。