PostgreSQL 16: 使用 pg_stat_io 观察表扩展

John Doe 六月 9, 2025

你需要经常批量加载表数据吗?那么,你知道加载过程中表页面的扩展性能如何吗?

草原上的一头大象

特性提交日志

添加 pg_stat_io 视图,提供更详细的 IO 统计信息。

该视图中的行会展示出特定的后端类型、IO 目标对象、IO 上下文组合,例如客户端后端对共享缓冲区中持久化关系的操作,视图中的每列表示已完成的 IO 操作总数(例如写入次数)。例如,视图中的一个值可能会表示自上次统计重置以来,客户端后端从共享缓冲区写入关系数据的块数。

为了便于跟踪 WAL 日志的 IO 和非数据块的 IO(如临时文件 IO),“op_bytes” 列指定了给定行中 “reads”、“writes” 和 “extends” 列的单位。

对于从未出现的 IO 操作、后端类型、目标对象和上下文的组合,视图将完全省略相关行。例如,检查点进程(checkpointer)永远不会对临时关系执行操作。

类似地,如果某个组合从未发生过某个 IO 操作,则该 IO 操作的相关值将显示为 null,以区分于观察到 0 次 IO 操作的情况。例如,后台写入进程(bgwriter)不会执行读取操作。

请注意,视图中的某些值与 pg_stat_bgwriter 中的字段存在冗余(例如 buffers_backend)。目前为了向后兼容,这些字段将保留。

讨论:https://postgr.es/m/20200124195226.lth52iydq2n2uilq@alap3.anarazel.de

示例

PostgreSQL 新版本引入了一个用于跟踪 I/O 相关统计信息的新视图。

我们要查看的第一个统计数据是 “extends”。在此之前,我们需要理解 “extends” 的含义。其背后的原理很简单。考虑如下的一张空表:

create table t ( a int );

这个表目前在磁盘上分配了多少空间?让我们来查询 PostgreSQL 获取此信息:

select pg_relation_size('t');
 pg_relation_size 
------------------
                0
(1 row)

该结果表示,目前该表不占用任何磁盘空间。这可以很容易地通过下面方式验证:

select pg_relation_filepath('t');
 pg_relation_filepath 
----------------------
 base/5/16438
(1 row)
$ ls -l $PGDATA/base/5/16438
-rw------- 1 postgres postgres 0 Jul 27 15:55 /db/pgsql/data/base/5/16438

PostgreSQL 和文件系统都报告了相同的大小。如果我们添加一行会发生什么?

insert into t values (1);

select pg_relation_size('t');
 pg_relation_size 
------------------
             8192
(1 row)
$ ls -l $PGDATA/base/5/16438
-rw------- 1 postgres postgres 8192 Jul 27 15:59 /db/pgsql/data/base/5/16438

我们看到了表大小是 8k,这是实例的数据块大小:

show block_size;
 block_size 
------------
 8192
(1 row)

那么,什么是 “extends”?当 PostgreSQL 需要向关系中添加更多行,而现有块中没有剩余空间时,就需要添加一个新块。上面的例子中有一次 “extends”,对应 8k,也就是一个块。

现在,新的 pg_stat_io 视图会跟踪这些发生的 “extends”。让我们通过一些简单的例子,来理解这些数字。首先,我们从头开始,截断一个已有的表:

truncate t;
$ ls -l $PGDATA/base/5/16438
-rw------- 1 postgres postgres 0 Jul 27 16:09 /db/pgsql/data/base/5/16438

这使我们回到磁盘大小为零的状态。pg_stat_io 视图包含以下列:

postgres=# \d pg_stat_io
                        View "pg_catalog.pg_stat_io"
     Column     |           Type           | Collation | Nullable | Default
----------------+--------------------------+-----------+----------+---------
 backend_type   | text                     |           |          | 
 object         | text                     |           |          | 
 context        | text                     |           |          | 
 reads          | bigint                   |           |          | 
 read_time      | double precision         |           |          | 
 writes         | bigint                   |           |          | 
 write_time     | double precision         |           |          | 
 writebacks     | bigint                   |           |          | 
 writeback_time | double precision         |           |          | 
 extends        | bigint                   |           |          | 
 extend_time    | double precision         |           |          | 
 op_bytes       | bigint                   |           |          | 
 hits           | bigint                   |           |          | 
 evictions      | bigint                   |           |          | 
 reuses         | bigint                   |           |          | 
 fsyncs         | bigint                   |           |          | 
 fsync_time     | double precision         |           |          | 
 stats_reset    | timestamp with time zone |           |          | 

因为我们只对我们的会话感兴趣(没有其他客户端会话连接到此实例),并且我们只想查看有关 “extends” 的统计信息,并且我们只对关系类型的对象的统计信息感兴趣,所以我们可以使用下面命令来查看我们触发的表扩展:

select backend_type,extends,op_bytes 
  from pg_stat_io 
 where backend_type = 'client backend'
   and context = 'normal'
   and object ='relation';
  backend_type  | extends | op_bytes 
----------------+---------+----------
 client backend |       9 |     8192
(1 row)

由于我们已经有了一些统计数据,让我们来重置下统计中的计数值:

select pg_stat_reset_shared('io');
 pg_stat_reset_shared 
----------------------
  
(1 row)

select backend_type,extends,op_bytes 
  from pg_stat_io 
 where backend_type = 'client backend'
   and context = 'normal'
   and object ='relation';
  backend_type  | extends | op_bytes 
----------------+---------+----------
 client backend |       0 |     8192
(1 row)

“op_bytes” 表示每个 I/O 单元的字节数,其对应 “read”、“write” 或 “extend”。这也是 8k 的块大小。

如果我们在小表 “t” 中生成 1000 行,会发生什么?

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

select backend_type,extends,op_bytes 
  from pg_stat_io 
 where backend_type = 'client backend'
   and context = 'normal'
   and object ='relation';
  backend_type  | extends | op_bytes 
----------------+---------+----------
 client backend |       8 |     8192
(1 row)

这样出现了 8 次扩展,8 * 8192 = 65536 字节。如果我们查询关系表的大小,PostgreSQL 应该会返回相同的值:

select pg_relation_size('t');
 pg_relation_size 
------------------
            40960
(1 row)

select pg_relation_filepath('t');
 pg_relation_filepath 
----------------------
 base/5/16441
(1 row)
$ ls -l $PGDATA/base/5/16441*
-rw------- 1 postgres postgres 40960 Jul 28 07:11 /db/pgsql/data/base/5/16441
-rw------- 1 postgres postgres 24576 Jul 28 07:11 /db/pgsql/data/base/5/16441_fsm

40960 + 24576 = 65536 字节。不仅表文件需要扩展,空闲空间映射也需要扩展,扩展空间的总数刚好是两者之和。

最后,关于扩展,还有一个额外的统计数据:“extend_time”。这是扩展关系文件所花费的时间(以毫秒为单位)。默认情况下,它始终为零:

select backend_type,extends,extend_time,op_bytes 
  from pg_stat_io 
 where backend_type = 'client backend'
   and context = 'normal'
   and object ='relation';
  backend_type  | extends | extend_time | op_bytes 
----------------+---------+-------------+----------
 client backend |       8 |           0 |     8192
(1 row)

要获取这些统计数据,您需要启用 track_io_timing

非常不错的体验。感谢社区的所有相关人员。

参考

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