由 John Doe 四月 24, 2025
PostgreSQL 自动清理进程在工作时,会不时地进行休眠,很难判断它的工作饱和度。现在,PostgreSQL 在进度视图中添加了基于成本的 VACUUM 延迟时间。
特性提交日志
在进度视图中添加基于成本的 VACUUM 延迟时间。
此提交将基于成本的延迟所花费的休眠时间添加到了pg_stat_progress_vacuum
和pg_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