PostgreSQL 17: 增量备份相关的系统信息函数

John Doe 七月 10, 2025

你在使用 PostgreSQL 的增量备份吗?那你想知道增量备份相关的运行状态吗?

欢快奔跑的大象

特性提交日志

新增函数 pg_get_wal_summarizer_state()。

这使得从 SQL 中获取 WAL 汇总进度信息成为可能。此前新增的函数pg_available_wal_summaries()pg_wal_summary_contents()仅检查磁盘状态,而本函数则公开了来自服务器共享内存的信息。

讨论:http://postgr.es/m/CA+Tgmobvqqj-DW9F7uUzT-cQqs6wcVb-Xhs=w=hzJnXSE-kRGw@mail.gmail.com

示例

下面,我们将介绍随着增量备份特性新增的系统信息函数。

我们先从一个启用了"summarize_wal"的全新集群开始:

postgres=# \dconfig *summa*
List of configuration parameters
       Parameter       | Value 
-----------------------+-------
 summarize_wal         | on
 wal_summary_keep_time | 10d
(2 rows)

WAL 摘要会写入pg_wal/summaries目录,当然,由于我们尚未对该实例进行任何操作,目前这里看不到任何内容:

$ ls -la $PGDATA/pg_wal/
total 16400
drwx------  4 postgres postgres     4096 Jun 18 10:51 .
drwx------ 20 postgres postgres     4096 Jun 18 10:51 ..
-rw-------  1 postgres postgres 16777216 Jun 18 10:53 000000010000000000000001
drwx------  2 postgres postgres     4096 Jun 18 10:51 archive_status
drwx------  2 postgres postgres     4096 Jun 18 10:51 summaries

$ ls -la $PGDATA/pg_wal/summaries
total 44
drwx------ 2 postgres postgres 4096 Jun 18 10:51 .
drwx------ 4 postgres postgres 4096 Jun 18 10:51 ..
-rw------- 1 postgres postgres 4808 Jun 18 10:51 00000001000000000100002800000000010B3098.summary
-rw------- 1 postgres postgres 4944 Jun 18 10:51 0000000100000000010B309800000000014E8838.summary
-rw------- 1 postgres postgres   32 Jun 18 10:51 0000000100000000014E883800000000014E8938.summary
-rw------- 1 postgres postgres  230 Jun 18 10:51 0000000100000000014E893800000000014EF360.summary
-rw------- 1 postgres postgres   32 Jun 18 10:51 0000000100000000014EF36000000000014EF460.summary
-rw------- 1 postgres postgres   88 Jun 18 10:51 0000000100000000014EF46000000000014EF798.summary
-rw------- 1 postgres postgres   32 Jun 18 10:51 0000000100000000014EF79800000000014EF810.summary

事实并非如此,尽管从用户角度来看没有进行任何操作,但我们确实看到这里有不少文件。这正是介绍随着增量备份特性新增的三个系统信息函数的绝佳场景。我们先来看pg_available_wal_summaries(),它能为我们呈现与文件系统相同的情况,还附带额外信息:

select * from pg_available_wal_summaries();
 tli | start_lsn |  end_lsn  
-----+-----------+-----------
   1 | 0/14EF360 | 0/14EF460
   1 | 0/14E8938 | 0/14EF360
   1 | 0/1000028 | 0/10B3098
   1 | 0/14EF460 | 0/14EF798
   1 | 0/14E8838 | 0/14E8938
   1 | 0/10B3098 | 0/14E8838
   1 | 0/14EF798 | 0/14EF810
(7 rows)

这看起来有点奇怪,因为我们看不到各行之间有任何关联。换句话说:按常理,这不应该是一个链状结构吗?一行的 end_lsn 应该是下一行的 start_lsn 才对?由于我们处于同一时间线(tli),按 start_lsn 排序后,我们得到了预期的结果:

select * from pg_available_wal_summaries() order by start_lsn;
 tli | start_lsn |  end_lsn
-----+-----------+-----------
   1 | 0/1000028 | 0/10B3098
   1 | 0/10B3098 | 0/14E8838
   1 | 0/14E8838 | 0/14E8938
   1 | 0/14E8938 | 0/14EF360
   1 | 0/14EF360 | 0/14EF460
   1 | 0/14EF460 | 0/14EF798
   1 | 0/14EF798 | 0/14EF810
(7 rows)

现在我们看到的情况与文件系统一致,例如,第一行的 end_lsn 是下一行的 start_lsn,并以此类推,直至摘要的末尾:

$ ls -la $PGDATA/pg_wal/summaries
total 44
drwx------ 2 postgres postgres 4096 Jun 18 10:51 .
drwx------ 4 postgres postgres 4096 Jun 18 10:51 ..
-rw------- 1 postgres postgres 4808 Jun 18 10:51 00000001000000000100002800000000010B3098.summary
-rw------- 1 postgres postgres 4944 Jun 18 10:51 0000000100000000010B309800000000014E8838.summary
-rw------- 1 postgres postgres   32 Jun 18 10:51 0000000100000000014E883800000000014E8938.summary
-rw------- 1 postgres postgres  230 Jun 18 10:51 0000000100000000014E893800000000014EF360.summary
-rw------- 1 postgres postgres   32 Jun 18 10:51 0000000100000000014EF36000000000014EF460.summary
-rw------- 1 postgres postgres   88 Jun 18 10:51 0000000100000000014EF46000000000014EF798.summary
-rw------- 1 postgres postgres   32 Jun 18 10:51 0000000100000000014EF79800000000014EF810.summary

磁盘上的文件名也是这样构成的:先是起始和结束时间线,接着是起始和结束的 lsn(日志序列号)。

接下来我们可以看看pg_wal_summary_contents( tli bigint, start_lsn pg_lsn, end_lsn pg_lsn )函数。这个函数能让我们可以了解摘要中包含的内容,例如,使用上面的第一个条目:

select * from pg_wal_summary_contents( 1, '0/1000028', '0/10B3098' );
 relfilenode | reltablespace | reldatabase | relforknumber | relblocknumber | is_limit_block
-------------+---------------+-------------+---------------+----------------+----------------
         112 |          1663 |           1 |             0 |              0 | f
         113 |          1663 |           1 |             0 |              0 | f
         174 |          1663 |           1 |             0 |              0 | f
         175 |          1663 |           1 |             0 |              0 | f
         548 |          1663 |           1 |             0 |              0 | f
         549 |          1663 |           1 |             0 |              0 | f
...

这意味着什么呢?首先,这与 template1 数据库有关:

select oid, datname from pg_database;
 oid |  datname  
-----+-----------
   5 | postgres
   1 | template1
   4 | template0
(3 rows)

表空间是 pg_default:

select oid, spcname from pg_tablespace ;
 oid  |  spcname   
------+------------
 1663 | pg_default
 1664 | pg_global
(2 rows)

relfilenode 列描述的是磁盘上的文件,例如:

$ ls -la $PGDATA/base/1/112*
-rw------- 1 postgres postgres 8192 Jun 18 10:51 /db/pgsql/data/base/1/112

relblocknumber 表示已更改的块。is_limit_block 的含义是:每个 is_limit_block 为 false 的行表示,由其余输出列标识的块在本文件所总结的记录范围内被至少一个 WAL 记录修改过。每个 is_limit_block 为 true 的行表示,要么(a)在相关的 WAL 记录范围内,关系分支被截断到 relblocknumber 所给出的长度;要么(b)在相关的 WAL 记录范围内,关系分支被创建或删除;在这种情况下,relblocknumber 将为零。

让我们做一个简单的测试,看看这到底意味着什么。我们创建一个包含一行数据的简单表:

create table t ( a int );

insert into t values (1);

select pg_relation_filepath('t');
 pg_relation_filepath 
----------------------
 base/5/16388
(1 row)

如果我们查询最后一个摘要文件,应该会在pg_wal_summary_contents的输出中看到一个新条目:

select * from pg_wal_summary_contents( 1, '0/14EF810', '0/1557C00' ) where relfilenode = 16388;
 relfilenode | reltablespace | reldatabase | relforknumber | relblocknumber | is_limit_block 
-------------+---------------+-------------+---------------+----------------+----------------
(0 rows)

我们什么也没看到,原因是新的摘要文件尚未写入,让我们强制执行检查点,并再次检查:

checkpoint;

select * from pg_available_wal_summaries() order by start_lsn;
 tli | start_lsn |  end_lsn
-----+-----------+-----------
   1 | 0/1000028 | 0/10B3098
   1 | 0/10B3098 | 0/14E8838
   1 | 0/14E8838 | 0/14E8938
   1 | 0/14E8938 | 0/14EF360
   1 | 0/14EF360 | 0/14EF460
   1 | 0/14EF460 | 0/14EF798
   1 | 0/14EF798 | 0/14EF810
   1 | 0/14EF810 | 0/1557C00
   1 | 0/1557C00 | 0/15598C8
(9 rows)

select * from pg_wal_summary_contents( 1, '0/1557C00', '0/15598C8' ) where relfilenode = 16388;
 relfilenode | reltablespace | reldatabase | relforknumber | relblocknumber | is_limit_block
-------------+---------------+-------------+---------------+----------------+----------------
       16388 |          1663 |           5 |             0 |              0 | f
(1 row)

让我们添加更多数据,看看这在 WAL 摘要中是如何记录的:

insert into t select * from generate_series(1, 1000000);

checkpoint;

select * from pg_available_wal_summaries() order by start_lsn;
 tli | start_lsn |  end_lsn
-----+-----------+-----------
   1 | 0/1000028 | 0/10B3098
   1 | 0/10B3098 | 0/14E8838
   1 | 0/14E8838 | 0/14E8938
   1 | 0/14E8938 | 0/14EF360
   1 | 0/14EF360 | 0/14EF460
   1 | 0/14EF460 | 0/14EF798
   1 | 0/14EF798 | 0/14EF810
   1 | 0/14EF810 | 0/1557C00
   1 | 0/1557C00 | 0/15598C8
   1 | 0/15598C8 | 0/5290968
(10 rows)

select * from pg_wal_summary_contents(1, '0/15598C8', '0/5290968') where relfilenode = 16388;
 relfilenode | reltablespace | reldatabase | relforknumber | relblocknumber | is_limit_block
-------------+---------------+-------------+---------------+----------------+----------------
       16388 |          1663 |           5 |             0 |              0 | f
       16388 |          1663 |           5 |             0 |              1 | f
       16388 |          1663 |           5 |             0 |              2 | f
       16388 |          1663 |           5 |             0 |              3 | f
       16388 |          1663 |           5 |             0 |              4 | f
       16388 |          1663 |           5 |             0 |              5 | f
       16388 |          1663 |           5 |             0 |              6 | f
...
       16388 |          1663 |           5 |             0 |           4420 | f
       16388 |          1663 |           5 |             0 |           4421 | f
       16388 |          1663 |           5 |             0 |           4422 | f
       16388 |          1663 |           5 |             0 |           4423 | f
       16388 |          1663 |           5 |             0 |           4424 | f
(4425 rows)

这是否意味着我们修改了该表中的 4424 个块?是的,确实如此:

select pg_relation_filepath('t');
 pg_relation_filepath 
----------------------
 base/5/16388
(1 row)
$ ls -la $PGDATA/base/5/16388*
-rw------- 1 postgres postgres 36249600 Jun 18 11:23 /db/pgsql/data/base/5/16388
-rw------- 1 postgres postgres    32768 Jun 18 11:23 /db/pgsql/data/base/5/16388_fsm
-rw------- 1 postgres postgres     8192 Jun 18 11:23 /db/pgsql/data/base/5/16388_vm
select 36249600/8192;
 ?column? 
----------
     4425
(1 row)

增量备份需要包含自上次完整备份以来发生更改的所有这些块。

最后一个函数给出了 WAL 摘要进程的状态:

select * from pg_get_wal_summarizer_state();
 summarized_tli | summarized_lsn | pending_lsn | summarizer_pid
----------------+----------------+-------------+----------------
              1 | 0/77DC3C0      | 0/77DE570   |           1638
(1 row)

非常简单:时间线、已写入摘要的最后一个 LSN,和 pending_lsn 表示 WAL 摘要进程已使用的最后一个 lsn。

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

参考

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