PostgreSQL 16: 使用 pg_stat_io 观察缓存页的换出

John Doe 六月 10, 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 相关统计信息的新视图。

下面,让我们来研究下 “evictions” 统计数。在深入细节之前,我们先来了解一下什么是换出?假设缓冲区缓存当前已满,PostgreSQL 需要从磁盘(或 OS 缓存)加载另一个块。为此,必须换出当前缓存在缓冲区中的块,以便为要加载的块腾出空间。这就是该统计数的作用:跟踪换出次数。该统计数可以帮助您确定共享缓冲区(shared_buffers)的大小是否合适。

PostgreSQL 共享缓冲区的默认大小为 128MB,这是我们目前的大小:

show shared_buffers;
 shared_buffers 
----------------
 128MB
(1 row)

使用 pg_buffercache 扩展,我们可以在实例启动后立即检查当前使用了多少个块:

select count(*) from pg_buffercache where relfilenode is not null;
 count
-------
   287
(1 row)

这意味着当前使用了 287 * 8192(这是实例中数据块的大小)= 2351104 字节,略小于 2.3MB。

这也意味着,此时在 pg_stat_io 视图中不会看到任何换出:

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

要查看缓存的换出现象,我们需要加载大约 126MB 的数据,因此让我们这样做:

create table t ( a int, b text, c text, d text );

insert into t select i,  md5(i::text), md5(i::text), md5(i::text) 
  from generate_series(1,1000000) i;

select pg_size_pretty(pg_relation_size('t'));
 pg_size_pretty 
----------------
 128 MB
(1 row)

这些数据应该足以让我们看到一些换出行为:

select backend_type,evictions 
  from pg_stat_io 
 where backend_type = 'client backend'
   and context = 'normal'
   and object ='relation';
  backend_type  | evictions 
----------------+-----------
 client backend |       288
(1 row)

让我们看一下我们的表当前缓存了多少内容:

select c.relname, count(*) AS buffers
  from pg_class c
 inner join pg_buffercache b
       on b.relfilenode=c.relfilenode
 inner join pg_database d
       on (b.reldatabase=d.oid AND d.datname=current_database())
 where c.relname = 't'
 group by c.relname;
 
 relname | buffers 
---------+---------
 t       |   16237
(1 row)

这是 (16237 * 8) / 1024,大约 127 MB 的数据。由于表大小为 128MB,而缓冲区缓存中已经使用了大约 2MB,因此需要进行一些换出操作。288 次换出意味着略大于 2MB,因此这似乎是一致的。

如果我们像下面这样创建该表的副本,然后再次查看换出情况,这次我们会看到如下内容:

create table t2 as select * from t;

select backend_type,evictions,context 
  from pg_stat_io 
 where backend_type = 'client backend'
   and object ='relation';
  backend_type  | evictions |  context  
----------------+-----------+-----------
 client backend |        32 | bulkread
 client backend |      2048 | bulkwrite
 client backend |       323 | normal
 client backend |         0 | vacuum

批量读取和批量写入意味着,数据块会从共享缓冲区中取出,并被放置在环形缓冲区中,以供批量 I/O 操作使用。对于 VACUUM 操作也是如此:

update t set d = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

vacuum t;

select backend_type,evictions,context 
  from pg_stat_io 
 where backend_type = 'client backend'
   and object ='relation';
  backend_type  | evictions |  context  
----------------+-----------+-----------
 client backend |     16102 | bulkread
 client backend |      2048 | bulkwrite
 client backend |     18611 | normal
 client backend |        32 | vacuum

总结一下:当你在“正常”环境下看到大量换出时,这可能提示你的共享缓冲区太小。如果几乎没有看到换出,则可能意味着你的共享缓冲区过大。

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

参考

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