PostgreSQL 19: 监控全页写镜像对 WAL 的影响

John Doe 三月 20, 2026

长期以来,PostgreSQL 中的全页镜像占据了大部分的 WAL 日志流量,系统缺少有效的手段进行运维管理。

image

WAL 预写日志是 PostgreSQL 保障数据一致性、实现崩溃恢复与主备复制的核心机制,而全页镜像(Full Page Image, FPI)是 WAL 中的关键组成部分:在 checkpoint 完成后,数据页第一次被修改时,PostgreSQL 会将整个数据页写入 WAL,避免因操作系统 “部分写” 导致的数据页损坏。

特性提交日志

向 pg_stat_wal 视图和 pg_stat_get_backend_wal() 函数中新增 wal_fpi_bytes 统计项。

新增的wal_fpi_bytes计数器,用于统计预写式日志(WAL)中生成的全页镜像(FPI)的总字节量。该数据可通过pg_stat_wal视图获取全局统计结果,也可通过pg_stat_get_backend_wal()函数获取单个后端进程的统计数据。

在此之前,这类信息只能通过pg_waldump工具或pg_walinspect插件获取,而这两种方式会受运行环境限制无法使用,且执行成本较高。wal_fpi_bytes计数器能够直观反映全页镜像对 WAL 生成量的影响程度(在部分业务负载下,其占比可能极高),也可用于评估 WAL 压缩、数据页空洞优化的实际效果。

讨论:https://postgr.es/m/CAOzEurQtZEAfg6P0kU3Wa-f9BWQOi0RzJEMPN56wNTOmJLmfaQ@mail.gmail.com

特性示例

当实例出现 WAL 生成量突增、归档存储成本上涨、主备复制带宽打满等问题时,首要任务是判断 FPI 是否为 WAL 膨胀的核心来源。

通过查询系统视图pg_stat_wal,秒级获取 FPI 的空间占比,直接定位优化方向:

SELECT
  wal_records,
  wal_fpi,
  pg_size_pretty(wal_bytes) AS wal_total_size,
  pg_size_pretty(wal_fpi_bytes) AS wal_fpi_total_size,
  round(wal_fpi_bytes * 100.0 / wal_bytes, 2) AS fpi_size_ratio
FROM pg_stat_wal;
 wal_records | wal_fpi | wal_total_size | wal_fpi_total_size | fpi_size_ratio
-------------+---------+----------------+--------------------+----------------
     9842578 |   16000 | 724 MB         | 99 MB              |             13
(1 row)

fpi_size_ratio超过 50%,说明 FPI 是 WAL 流量的核心来源,优化方向可聚焦于调整checkpoint_timeout/max_wal_size拉长 checkpoint 间隔、设置 WAL 日志压缩参数wal_compression、优化大事务批量写入逻辑。

在业务上线批量写入、数据归档、表结构变更等操作前,DBA 需要精准评估该操作的 WAL 开销,尤其是 FPI 带来的额外流量,避免上线后打满带宽、影响主备同步。

通过调用系统函数pg_stat_get_backend_wal(pg_backend_pid()),统计当前会话操作前后的差值,精准获取单操作的 FPI 字节开销。

-- 操作前记录基线统计值
SELECT wal_bytes, wal_fpi_bytes
FROM pg_stat_get_backend_wal(pg_backend_pid()) \gset prev_

-- 执行目标业务操作
CREATE TABLE bookings_copy AS SELECT * FROM bookings;

-- 计算操作的实际 WAL 与 FPI 开销
SELECT
  pg_size_pretty(wal_bytes - :prev_wal_bytes) AS op_wal_total,
  pg_size_pretty(wal_fpi_bytes - :prev_wal_fpi_bytes) AS op_fpi_total,
  round((wal_fpi_bytes - :prev_wal_fpi_bytes) * 100.0 /
        (wal_bytes - :prev_wal_bytes), 2) AS op_fpi_ratio
FROM pg_stat_get_backend_wal(pg_backend_pid());

非常不错的体验,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/f9a09aa2952039a9956b44d929b9df74d62a4cd4