PostgreSQL 18 预览: 进度视图中基于成本的 VACUUM 延迟时间

John Doe 四月 24, 2025

PostgreSQL 自动清理进程在工作时,会不时地进行休眠,很难判断它的工作饱和度。现在,PostgreSQL 在进度视图中添加了基于成本的 VACUUM 延迟时间。

欢快奔跑的大象

特性提交日志

在进度视图中添加基于成本的 VACUUM 延迟时间。

此提交将基于成本的延迟所花费的休眠时间添加到了pg_stat_progress_vacuumpg_stat_progress_analyze系统视图中。一个名为track_cost_delay_timing的新配置参数(默认处于关闭状态)控制是否收集此信息。对于 VACUUM 操作,报告的值包括了任何相关并行工作进程的休眠时间。不过,并行工作进程每秒仅报告一次其休眠时间,以避免主进程负载过重。

讨论:https://postgr.es/m/ZmaXmWDL829fzAVX@ip-10-97-1-34.eu-west-3.compute.internal

示例

PostgreSQL 很早就有了关于 VACUUM 进度的首个简单视图,这可以在pg_stat_progress_vacuum中查看。从那时起,社区进行了许多改进,提供了更多信息,也能获取更多操作的进度信息。

本次的新改进相当不错,因为它增加了关于到目前为止 VACUUM 运行过程中,因以下一个或多个因素而处于等待状态的时长信息:

  • autovacuum_vacuum_cost_delay
  • autovacuum_vacuum_cost_limit
  • vacuum_cost_delay
  • vacuum_cost_limit
  • vacuum_cost_page_dirty
  • vacuum_cost_page_hit
  • vacuum_cost_page_miss

让我们看看它是如何工作的。我们先修改下设置,以便即使手动运行 VACUUM 也会有一些成本延迟:

select name, setting from pg_settings  where name ~ 'vac.*cost';
             name             | setting
------------------------------+---------
 autovacuum_vacuum_cost_delay | 2
 autovacuum_vacuum_cost_limit | -1
 vacuum_cost_delay            | 20
 vacuum_cost_limit            | 200
 vacuum_cost_page_dirty       | 20
 vacuum_cost_page_hit         | 1
 vacuum_cost_page_miss        | 2
(7 rows)

请注意vacuum_cost_delay的值。通常它是 0,这意味着手动运行 VACUUM 时没有延迟,而通过自动清理(autovacuum)时是有的。

然后,启动另一个 psql 会话,并在其中运行:

select relid::regclass, phase, heap_blks_total, heap_blks_scanned,
       heap_blks_vacuumed, index_vacuum_count,
       max_dead_tuple_bytes, dead_tuple_bytes, num_dead_item_ids,
	   indexes_total, indexes_processed,
	   delay_time
from pg_stat_progress_vacuum where pid = 309635 \watch 1

其中,pid的值是在第一个 psql 会话中通过以下命令获取的:

select pg_backend_pid();

目前,输出内容并没有太大意义:

 relid | phase | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuple_bytes | dead_tuple_bytes | num_dead_item_ids | indexes_total | indexes_processed | delay_time
-------+-------+-----------------+-------------------+--------------------+--------------------+----------------------+------------------+-------------------+---------------+-------------------+------------
(0 rows)

但情况会发生变化。回到第一个 psql 会话,我们来创建一个表,以便有可以进行 VACUUM 操作的对象:

create table test_it as
select i, repeat( 'redrock' || i::text, 200) as payload
from generate_series(1,10000000) i;

alter table test_it add primary key (i);

这会创建出一个约 900MB 的表,和一个 214MB 的索引:

postgres=# \dt+ test_it
                                     List of tables
 Schema |  Name   | Type  |  Owner  | Persistence | Access method |  Size  | Description
--------+---------+-------+---------+-------------+---------------+--------+-------------
 public | test_it | table | redrock | permanent   | heap          | 899 MB | [null]
(1 row)

postgres=# \di+ test_it_pkey
                                            List of indexes
 Schema |     Name     | Type  |  Owner  |  Table  | Persistence | Access method |  Size  | Description
--------+--------------+-------+---------+---------+-------------+---------------+--------+-------------
 public | test_it_pkey | index | redrock | test_it | permanent   | btree         | 214 MB | [null]
(1 row)

现在,让我们给 VACUUM 产生一些清理任务,然后立即运行 VACUUM:

delete from test_it where random() < 0.75;
DELETE 7499475
Time: 5164.567 ms (00:05.165)

vacuum test_it;
Time: 582264.662 ms (09:42.265)

在 VACUUM 运行期间,第二个 psql 会话显示了进度信息。这里不会展示全部内容,只展示几个有意义的时刻。

第一次出现数据行:

  relid  |     phase     | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuple_bytes | dead_tuple_bytes | num_dead_item_ids | indexes_total | indexes_processed | delay_time
---------+---------------+-----------------+-------------------+--------------------+--------------------+----------------------+------------------+-------------------+---------------+-------------------+------------
 test_it | scanning heap |          115008 |               430 |                  0 |                  0 |             67108864 |           204800 |              1283 |             0 |                 0 | 915.843569
(1 row)

两条日志(间隔 1 秒)显示,在状态变化时延迟计数器不会归零:

  relid  |     phase     | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuple_bytes | dead_tuple_bytes | num_dead_item_ids | indexes_total | indexes_processed |  delay_time
---------+---------------+-----------------+-------------------+--------------------+--------------------+----------------------+------------------+-------------------+---------------+-------------------+--------------
 test_it | scanning heap |          115008 |            114907 |                  0 |                  0 |             67108864 |          9502720 |           7494913 |             0 |                 0 | 251967.61019
(1 row)

  relid  |       phase       | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuple_bytes | dead_tuple_bytes | num_dead_item_ids | indexes_total | indexes_processed |  delay_time
---------+-------------------+-----------------+-------------------+--------------------+--------------------+----------------------+------------------+-------------------+---------------+-------------------+--------------
 test_it | vacuuming indexes |          115008 |            115008 |                  0 |                  0 |             67108864 |          9502720 |           7499475 |             1 |                 0 | 252949.01199
(1 row)

这里还有个有趣的现象。上述两次运行中,延迟时间的差值为 252949.01199 - 251967.61019 = 981.40180。由于单位是毫秒,这意味着在 1 秒时间内,VACUUM 因成本原因花费了 981 毫秒的休眠时间。这表明成本延迟的设置导致 VACUUM 仅在约 2% 的时间内处于工作状态。

最后结束时,记录的最后一行内容为:

  relid  |     phase      | heap_blks_total | heap_blks_scanned | heap_blks_vacuumed | index_vacuum_count | max_dead_tuple_bytes | dead_tuple_bytes | num_dead_item_ids | indexes_total | indexes_processed |  delay_time
---------+----------------+-----------------+-------------------+--------------------+--------------------+----------------------+------------------+-------------------+---------------+-------------------+---------------
 test_it | vacuuming heap |          115008 |            115008 |             114828 |                  1 |             67108864 |          9502720 |           7499475 |             0 |                 0 | 575899.343048
(1 row)

所以,VACUUM 总共花费了 582264.662 毫秒(通过 psql 的\timing命令得到),其中有 575899.343 毫秒处于休眠等待状态,占比 98.9%。

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

参考

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