From 8834f65ae1ab05c10ba992223691c8076cf5e64c Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Fri, 25 Sep 2026 19:01:30 +0000 Subject: [PATCH v5 2/2] Add tests for cost parameter refresh in parallel autovacuum wait. The previous commit made an autovacuum worker running a parallel vacuum (leader) refresh the cost-based delay parameters while it waits in WaitForParallelWorkersToFinish(), both after a configuration reload and after a change in the number of autovacuum workers sharing the cost limit. Add tests for both cases. To get the leader into that wait while its parallel worker still has work left, add an injection point that keeps the leader out of parallel-safe index vacuuming, leaving those indexes to the parallel workers, and one that holds a parallel worker before each index. A third injection point fires once the leader has refreshed the parameters, so the rebalance test knows when it is safe to release the parallel worker. Author: Nikolay Samokhvalov Reviewed-by: Daniel Gustafsson Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/CAM527d-GL%3DJp2EJXBSnVBGPK-4XEZwWof5Cv8P0hghS_og6oAg%40mail.gmail.com --- src/backend/commands/vacuumparallel.c | 23 ++- .../t/001_parallel_autovacuum.pl | 152 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index da0ec3cc8ee..affb363cd5f 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -47,6 +47,7 @@ #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" @@ -762,6 +763,8 @@ parallel_vacuum_refresh_cost_params(void) } parallel_vacuum_propagate_shared_delay_params(); + + INJECTION_POINT("parallel-autovacuum-leader-cost-updated", NULL); } /* @@ -852,6 +855,7 @@ parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int num_index_scan { int nworkers; PVIndVacStatus new_status; + bool leader_participates = true; Assert(!IsParallelWorker()); @@ -965,6 +969,17 @@ parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int num_index_scan pvs->pcxt->nworkers_launched, nworkers))); } +#ifdef USE_INJECTION_POINTS + + /* + * Used by tests to leave all parallel-safe indexes to the parallel + * workers, so that the leader waits for them to finish. + */ + if (nworkers > 0 && pvs->pcxt->nworkers_launched > 0 && + IS_INJECTION_POINT_ATTACHED("parallel-vacuum-leader-skip-safe-indexes")) + leader_participates = false; +#endif + /* Vacuum the indexes that can be processed by only leader process */ parallel_vacuum_process_unsafe_indexes(pvs); @@ -972,7 +987,8 @@ parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int num_index_scan * Join as a parallel worker. The leader vacuums alone processes all * parallel-safe indexes in the case where no workers are launched. */ - parallel_vacuum_process_safe_indexes(pvs); + if (leader_participates) + parallel_vacuum_process_safe_indexes(pvs); /* * Next, accumulate buffer and WAL usage. (This must wait for the workers @@ -1049,6 +1065,11 @@ parallel_vacuum_process_safe_indexes(ParallelVacuumState *pvs) if (!indstats->parallel_workers_can_process) continue; +#ifdef USE_INJECTION_POINTS + if (IsParallelWorker()) + INJECTION_POINT("parallel-vacuum-worker-before-index", NULL); +#endif + /* 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..e49544eb0ca 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,158 @@ $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); +ok( $node->poll_query_until( + 'postgres', q{ + SELECT count(*) = 0 FROM pg_stat_activity + WHERE backend_type = 'autovacuum worker' AND datname = 'regress_db2' + }), + 'second autovacuum worker finished'); + +# Start autovacuum on test_autovac with its parallel worker held before its +# first index, and the leader leaving all indexes to the parallel worker. +# Returns once the leader waits for the parallel worker to finish. +sub start_leader_waiting +{ + my ($node) = @_; + + $node->safe_psql( + 'postgres', q{ + SELECT injection_points_attach('parallel-vacuum-worker-before-index', 'wait'); + SELECT injection_points_attach('parallel-vacuum-leader-skip-safe-indexes', 'notice'); + ALTER TABLE test_autovac SET (autovacuum_enabled = true); + }); + $node->wait_for_event('parallel worker', + 'parallel-vacuum-worker-before-index'); + ok( $node->poll_query_until( + 'postgres', q{ + SELECT count(*) > 0 FROM pg_stat_activity + WHERE backend_type = 'autovacuum worker' + AND wait_event = 'ParallelFinish' + }), + 'autovacuum leader waits for its parallel worker'); +} + +# Release the parallel worker held by start_leader_waiting(). +sub release_parallel_worker +{ + my ($node) = @_; + + $node->safe_psql( + 'postgres', q{ + SELECT injection_points_detach('parallel-vacuum-leader-skip-safe-indexes'); + SELECT injection_points_detach('parallel-vacuum-worker-before-index'); + SELECT injection_points_wakeup('parallel-vacuum-worker-before-index'); + }); +} + +# Test 4: +# Check whether a config reload is serviced while the autovacuum leader waits +# for its parallel worker. +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; + +start_leader_waiting($node); + +$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(); +}); + +# The leader must process the reload before the parallel worker is released. +$node->wait_for_log( + qr/Autovacuum VacuumUpdateCosts\(db=$postgresoid, rel=$testautovacid, dobalance=yes, cost_limit=800, cost_delay=8 /, + $log_offset); + +release_parallel_worker($node); +$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 a cost limit rebalance, which is not signaled +# by a config reload. A second autovacuum worker joins the balance while the +# leader waits for its parallel worker. +$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; + +start_leader_waiting($node); + +# Hold the second worker, so that the balance stays at 2. Attach the notice +# before the rebalance, as the rebalance wakeup is the only thing that brings +# the waiting leader to this point. +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_attach('autovacuum-worker-cost-balanced', 'wait'); + 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')" +); + +release_parallel_worker($node); +$node->wait_for_log( + qr/parallel autovacuum worker updated cost params: cost_limit=300,/, + $log_offset); + +$node->safe_psql( + 'postgres', q{ + SELECT injection_points_wakeup('autovacuum-worker-cost-balanced'); + SELECT injection_points_detach('autovacuum-worker-cost-balanced'); +}); +$node->wait_for_log( + qr/automatic vacuum of table "postgres\.public\.test_autovac"/, + $log_offset); +ok( $node->poll_query_until( + 'postgres', q{ + SELECT count(*) = 0 FROM pg_stat_activity + WHERE backend_type = 'autovacuum worker' AND datname = 'regress_db2' + }), + 'second autovacuum worker finished'); +ok(1, "cost rebalance is propagated while the leader waits for workers"); $node->stop; done_testing(); -- 2.55.0