From 10f257503d4276e6b7a4468bdbf32ec91c2750c9 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Fri, 25 Sep 2026 19:01:30 +0000 Subject: [PATCH v4 2/2] Add tests for autovacuum cost parameter refresh while waiting. --- src/backend/commands/vacuumparallel.c | 15 ++ .../t/001_parallel_autovacuum.pl | 166 ++++++++++++++++++ .../t/003_cost_reload_while_waiting.pl | 109 ++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 src/test/modules/test_autovacuum/t/003_cost_reload_while_waiting.pl diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index 2dd127e9118..405f0b5e7b1 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -41,12 +41,14 @@ #include "commands/progress.h" #include "commands/vacuum.h" #include "executor/instrument.h" +#include "miscadmin.h" #include "optimizer/paths.h" #include "pgstat.h" #include "postmaster/interrupt.h" #include "storage/bufmgr.h" #include "storage/proc.h" #include "tcop/tcopprot.h" +#include "utils/injection_point.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -758,6 +760,8 @@ parallel_vacuum_refresh_cost_params(void) } parallel_vacuum_propagate_shared_delay_params(); + + INJECTION_POINT("parallel-autovacuum-leader-cost-updated", NULL); } /* @@ -964,6 +968,9 @@ parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int num_index_scan /* Vacuum the indexes that can be processed by only leader process */ parallel_vacuum_process_unsafe_indexes(pvs); + if (pvs->shared->is_autovacuum) + INJECTION_POINT("parallel-autovacuum-leader-before-index", NULL); + /* * Join as a parallel worker. The leader vacuums alone processes all * parallel-safe indexes in the case where no workers are launched. @@ -979,6 +986,11 @@ parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int num_index_scan /* Wait for all vacuum workers to finish */ WaitForParallelWorkersToFinish(pvs->pcxt); + if (pvs->shared->is_autovacuum) + INJECTION_POINT("parallel-autovacuum-leader-after-worker-wait", + ConfigReloadPending ? "reload pending" : + "reload processed"); + for (int i = 0; i < pvs->pcxt->nworkers_launched; i++) InstrAccumParallelQuery(&pvs->buffer_usage[i], &pvs->wal_usage[i]); } @@ -1045,6 +1057,9 @@ parallel_vacuum_process_safe_indexes(ParallelVacuumState *pvs) if (!indstats->parallel_workers_can_process) continue; + if (IsParallelWorker()) + INJECTION_POINT("parallel-autovacuum-worker-before-index", NULL); + /* Do vacuum or cleanup of the index */ parallel_vacuum_process_one_index(pvs, pvs->indrels[idx], indstats); } diff --git a/src/test/modules/test_autovacuum/t/001_parallel_autovacuum.pl b/src/test/modules/test_autovacuum/t/001_parallel_autovacuum.pl index 33c86bbdc94..bd1d574a4cf 100644 --- a/src/test/modules/test_autovacuum/t/001_parallel_autovacuum.pl +++ b/src/test/modules/test_autovacuum/t/001_parallel_autovacuum.pl @@ -252,6 +252,172 @@ $node->safe_psql('postgres', "SELECT injection_points_wakeup('autovacuum-worker-cost-balanced')"); $node->safe_psql('postgres', "SELECT injection_points_detach('autovacuum-worker-cost-balanced')"); +$node->wait_for_log( + qr/automatic vacuum of table "postgres\.public\.test_autovac"/, + $log_offset); +$node->poll_query_until( + 'postgres', q{ + SELECT count(*) = 0 FROM pg_stat_activity + WHERE backend_type = 'autovacuum worker' AND datname = 'regress_db2' +}) or die "second autovacuum worker did not finish"; + +# Test 4: +# Check whether a config reload is serviced while the autovacuum leader waits +# for a parallel worker to finish an index. Hold the worker after it claims +# an index, so the leader can process the remaining indexes and enter +# ParallelFinish. +my $postgresoid = $node->safe_psql('postgres', + "SELECT oid FROM pg_database WHERE datname = 'postgres'"); +my $testautovacid = + $node->safe_psql('postgres', "SELECT 'test_autovac'::regclass::oid"); + +$node->safe_psql( + 'postgres', qq{ + ALTER SYSTEM SET autovacuum_max_workers = 1; + ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 700; + ALTER SYSTEM SET autovacuum_vacuum_cost_delay = 0; + SELECT pg_reload_conf(); +}); + +prepare_for_next_test($node, 4); +$log_offset = -s $node->logfile; + +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_attach('parallel-autovacuum-worker-before-index', 'wait'); + SELECT injection_points_attach('parallel-autovacuum-leader-before-index', 'wait'); +}); +$node->safe_psql('postgres', + 'ALTER TABLE test_autovac SET (autovacuum_enabled = true)'); +$node->wait_for_event('autovacuum worker', + 'parallel-autovacuum-leader-before-index'); +$node->wait_for_event('parallel worker', + 'parallel-autovacuum-worker-before-index'); +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_wakeup('parallel-autovacuum-leader-before-index'); + SELECT injection_points_detach('parallel-autovacuum-leader-before-index'); +}); +$node->poll_query_until( + 'postgres', q{ + SELECT count(*) > 0 + FROM pg_stat_activity worker + JOIN pg_stat_activity leader ON leader.pid = worker.leader_pid + WHERE worker.backend_type = 'parallel worker' + AND worker.wait_event = 'parallel-autovacuum-worker-before-index' + AND leader.wait_event = 'ParallelFinish' +}) or die "autovacuum leader did not enter ParallelFinish"; + +$node->safe_psql( + 'postgres', qq{ + ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 800; + ALTER SYSTEM SET autovacuum_vacuum_cost_delay = 8; + ALTER SYSTEM SET vacuum_cost_page_miss = 11; + ALTER SYSTEM SET vacuum_cost_page_dirty = 12; + ALTER SYSTEM SET vacuum_cost_page_hit = 13; + SELECT pg_reload_conf(); +}); + +$node->wait_for_log( + qr/Autovacuum VacuumUpdateCosts\(db=$postgresoid, rel=$testautovacid, dobalance=yes, cost_limit=800, cost_delay=8 /, + $log_offset); + +$node->safe_psql('postgres', + "SELECT injection_points_wakeup('parallel-autovacuum-worker-before-index')"); +$node->safe_psql('postgres', + "SELECT injection_points_detach('parallel-autovacuum-worker-before-index')"); +$node->wait_for_log( + qr/parallel autovacuum worker updated cost params: cost_limit=800, cost_delay=8, cost_page_miss=11, cost_page_dirty=12, cost_page_hit=13/, + $log_offset); +$node->wait_for_log( + qr/automatic vacuum of table "postgres\.public\.test_autovac"/, + $log_offset); +ok(1, "config reload is propagated while the leader waits for workers"); + +# Test 5: +# Check the same wait path for an un-signalled cost-limit rebalance. A second +# autovacuum worker joins the balance while the first leader and its parallel +# worker remain held. +$node->safe_psql( + 'postgres', qq{ + ALTER SYSTEM SET autovacuum_max_workers = 2; + ALTER SYSTEM SET autovacuum_vacuum_cost_limit = 600; + SELECT pg_reload_conf(); +}); + +prepare_for_next_test($node, 5); +$node->safe_psql('regress_db2', + 'ALTER TABLE filler SET (autovacuum_enabled = false)'); +$node->safe_psql('regress_db2', 'UPDATE filler SET id = id + 1'); + +$log_offset = -s $node->logfile; +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_attach('parallel-autovacuum-worker-before-index', 'wait'); + SELECT injection_points_attach('parallel-autovacuum-leader-before-index', 'wait'); +}); +$node->safe_psql('postgres', + 'ALTER TABLE test_autovac SET (autovacuum_enabled = true)'); +$node->wait_for_event('autovacuum worker', + 'parallel-autovacuum-leader-before-index'); +$node->wait_for_event('parallel worker', + 'parallel-autovacuum-worker-before-index'); +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_wakeup('parallel-autovacuum-leader-before-index'); + SELECT injection_points_detach('parallel-autovacuum-leader-before-index'); +}); +$node->poll_query_until( + 'postgres', q{ + SELECT count(*) > 0 + FROM pg_stat_activity worker + JOIN pg_stat_activity leader ON leader.pid = worker.leader_pid + WHERE worker.backend_type = 'parallel worker' + AND worker.wait_event = 'parallel-autovacuum-worker-before-index' + AND leader.wait_event = 'ParallelFinish' +}) or die "autovacuum leader did not enter ParallelFinish"; + +$node->safe_psql('postgres', + "SELECT injection_points_attach('autovacuum-worker-cost-balanced', 'wait')" +); + +# Attach before the rebalance, as the rebalance wakeup is the only thing that +# brings the waiting leader to this point. +$node->safe_psql('postgres', + "SELECT injection_points_attach('parallel-autovacuum-leader-cost-updated', 'notice')" +); +$node->safe_psql('regress_db2', + 'ALTER TABLE filler SET (autovacuum_enabled = true)'); +$node->wait_for_log( + qr/VacuumUpdateCosts\(db=$db2oid, rel=$filleroid, dobalance=yes, cost_limit=300,/, + $log_offset); +$node->wait_for_log( + qr/notice triggered for injection point parallel-autovacuum-leader-cost-updated/, + $log_offset); +$node->safe_psql('postgres', + "SELECT injection_points_detach('parallel-autovacuum-leader-cost-updated')"); + +$node->safe_psql('postgres', + "SELECT injection_points_wakeup('parallel-autovacuum-worker-before-index')"); +$node->safe_psql('postgres', + "SELECT injection_points_detach('parallel-autovacuum-worker-before-index')"); +$node->wait_for_log( + qr/parallel autovacuum worker updated cost params: cost_limit=300,/, + $log_offset); + +$node->safe_psql('postgres', + "SELECT injection_points_wakeup('autovacuum-worker-cost-balanced')"); +$node->safe_psql('postgres', + "SELECT injection_points_detach('autovacuum-worker-cost-balanced')"); +$node->wait_for_log( + qr/automatic vacuum of table "postgres\.public\.test_autovac"/, + $log_offset); +$node->poll_query_until( + 'postgres', q{ + SELECT count(*) = 0 FROM pg_stat_activity + WHERE backend_type = 'autovacuum worker' AND datname = 'regress_db2' +}) or die "second autovacuum worker did not finish"; +ok(1, "cost rebalance is propagated while the leader waits for workers"); $node->stop; done_testing(); diff --git a/src/test/modules/test_autovacuum/t/003_cost_reload_while_waiting.pl b/src/test/modules/test_autovacuum/t/003_cost_reload_while_waiting.pl new file mode 100644 index 00000000000..afc3a733670 --- /dev/null +++ b/src/test/modules/test_autovacuum/t/003_cost_reload_while_waiting.pl @@ -0,0 +1,109 @@ +# Copyright (c) 2026, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +if ($ENV{enable_injection_points} ne 'yes') +{ + plan skip_all => 'Injection points not supported by this build'; +} + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->append_conf( + 'postgresql.conf', qq{ +autovacuum_max_workers = 1 +autovacuum_max_parallel_workers = 1 +autovacuum_naptime = '1s' +autovacuum_vacuum_cost_delay = '20ms' +autovacuum_vacuum_cost_limit = 200 +log_min_messages = debug2 +min_parallel_index_scan_size = 0 +}); +$node->start; + +if (!$node->check_extension('injection_points')) +{ + plan skip_all => 'Extension injection_points not installed'; +} + +$node->safe_psql( + 'postgres', q{ + CREATE EXTENSION injection_points; + CREATE TABLE test_autovac (id int, a int) + WITH (autovacuum_enabled = false, + autovacuum_parallel_workers = 1, + autovacuum_vacuum_threshold = 0, + autovacuum_vacuum_scale_factor = 0); + INSERT INTO test_autovac + SELECT g, g FROM generate_series(1, 100) g; + CREATE INDEX test_autovac_id_idx ON test_autovac (id); + CREATE INDEX test_autovac_a_idx ON test_autovac (a); + UPDATE test_autovac SET a = a + 1; + SELECT injection_points_attach( + 'parallel-autovacuum-worker-before-index', 'wait'); + SELECT injection_points_attach( + 'parallel-autovacuum-leader-before-index', 'wait'); + SELECT injection_points_attach( + 'parallel-autovacuum-leader-after-worker-wait', 'notice'); + ALTER TABLE test_autovac SET (autovacuum_enabled = true); +}); + +$node->wait_for_event('autovacuum worker', + 'parallel-autovacuum-leader-before-index'); +$node->wait_for_event('parallel worker', + 'parallel-autovacuum-worker-before-index'); +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_wakeup( + 'parallel-autovacuum-leader-before-index'); + SELECT injection_points_detach( + 'parallel-autovacuum-leader-before-index'); +}); +$node->poll_query_until( + 'postgres', q{ + SELECT EXISTS ( + SELECT 1 + FROM pg_stat_activity + WHERE backend_type = 'autovacuum worker' + AND wait_event = 'ParallelFinish') +}) or die "autovacuum leader did not reach ParallelFinish"; + +my $log_offset = -s $node->logfile; +if (!$ENV{NO_RELOAD_CONTROL}) +{ + $node->safe_psql( + 'postgres', q{ + ALTER SYSTEM SET autovacuum_vacuum_cost_delay = 0; + SELECT pg_reload_conf(); + }); +} +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_wakeup( + 'parallel-autovacuum-worker-before-index'); + SELECT injection_points_detach( + 'parallel-autovacuum-worker-before-index'); +}); + +$node->wait_for_log( + qr/parallel-autovacuum-leader-after-worker-wait \(reload (?:pending|processed)\)/, + $log_offset); + +my $log = slurp_file($node->logfile, $log_offset); +my ($reload_state) = + $log =~ /parallel-autovacuum-leader-after-worker-wait \(reload (pending|processed)\)/; +is($reload_state, 'processed', + 'autovacuum leader processes a configuration reload while waiting'); + +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_detach( + 'parallel-autovacuum-leader-after-worker-wait'); +}); +$node->stop; + +done_testing(); -- 2.47.3