From cb70ca048b95a0ca41f07d6ff2b220002c2ed826 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Fri, 24 Jul 2026 08:18:54 +0000 Subject: [PATCH v4] Propagate rebalanced cost limit to parallel vacuum workers AutoVacuumUpdateCostLimit() runs after each nap in vacuum_delay_point() and follows av_nworkersForBalance, but the new limit never reached the shared cost params in the vacuum DSM: propagation only happened on config reload. Parallel workers computed their delays from the stale limit, so a parallel autovacuum could run at up to twice the configured budget (or half of it) until the next SIGHUP, contradicting the propagation promise in maintenance.sgml. Call parallel_vacuum_propagate_shared_delay_params() after rebalancing. Gated on the leader: parallel workers take the same nap path and must not overwrite the shared parameters. Add a test: pause the leader at the existing injection point, start a second autovacuum worker and hold it at a new injection point placed after it joined the balance, then check the first parameter load of the parallel workers reports the balanced limit. The hold is needed because a second worker left running can finish its own vacuum before the leader resumes, which puts the balance back where it started. Autovacuum is disabled for everything but the two test tables via thresholds, as a worker spawned by catalog churn would get trapped at the hold point and starve the test of its second worker slot. --- src/backend/commands/vacuum.c | 3 + src/backend/postmaster/autovacuum.c | 1 + .../t/001_parallel_autovacuum.pl | 88 ++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 38539a6fd3d..c17aecce7ad 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2577,6 +2577,9 @@ vacuum_delay_point(bool is_analyze) */ AutoVacuumUpdateCostLimit(); + if (AmAutoVacuumWorkerProcess()) + parallel_vacuum_propagate_shared_delay_params(); + /* Might have gotten an interrupt while sleeping */ CHECK_FOR_INTERRUPTS(); } diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 45abf48768a..b06da6bce1d 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2487,6 +2487,7 @@ do_autovacuum(void) */ VacuumUpdateCosts(); + INJECTION_POINT("autovacuum-worker-cost-balanced", NULL); /* clean up memory before each iteration */ MemoryContextReset(PortalContext); 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 22f40cb1d50..33c86bbdc94 100644 --- a/src/test/modules/test_autovacuum/t/001_parallel_autovacuum.pl +++ b/src/test/modules/test_autovacuum/t/001_parallel_autovacuum.pl @@ -33,10 +33,15 @@ $node->init; # Limit to one autovacuum worker and disable autovacuum logging globally # (enabled only on the test table) so that log checks below match only # activity on the expected table. +# +# Effectively disable autovacuum for all tables except the ones the test +# re-enables via reloptions. A worker spawned by catalog churn would skew +# the cost balance, and an injection point attached below would trap it, +# eating the only free worker slot. $node->append_conf( 'postgresql.conf', qq{ autovacuum_max_workers = 1 -autovacuum_worker_slots = 1 +autovacuum_worker_slots = 2 autovacuum_max_parallel_workers = 2 max_worker_processes = 10 max_parallel_workers = 10 @@ -44,6 +49,9 @@ log_min_messages = debug2 autovacuum_naptime = '1s' min_parallel_index_scan_size = 0 log_autovacuum_min_duration = -1 +autovacuum_vacuum_threshold = 100000 +autovacuum_analyze_threshold = 100000 +autovacuum_vacuum_insert_threshold = -1 }); $node->start; @@ -72,6 +80,7 @@ $node->safe_psql( id SERIAL PRIMARY KEY, col_1 INTEGER, col_2 INTEGER, col_3 INTEGER, col_4 INTEGER ) WITH (autovacuum_parallel_workers = $autovacuum_parallel_workers, + autovacuum_vacuum_threshold = 50, log_autovacuum_min_duration = 0); INSERT INTO test_autovac @@ -167,5 +176,82 @@ ok(1, "vacuum delay parameter changes are propagated to parallel vacuum workers" ); +# Test 3: +# Check whether a cost limit rebalance reaches the parallel workers. The +# leader pauses right after taking the shared cost param snapshot +# (balance = 1, limit 500), then a second autovacuum worker joins the +# balance (balance = 2, limit 250) and is held there for the rest of the +# test. After resume, the parallel workers' first parameter load must show +# the rebalanced 250, not the snapshotted 500. + +# Second worker's table lives in another database: no cost reloptions, so it +# participates in balancing. +$node->safe_psql('postgres', 'CREATE DATABASE regress_db2'); +$node->safe_psql( + 'regress_db2', qq{ + CREATE TABLE filler (id int) + WITH (autovacuum_enabled = false, autovacuum_vacuum_threshold = 50); + INSERT INTO filler SELECT g FROM generate_series(1, 1000) g; +}); + +# Allow a second autovacuum worker. +$node->safe_psql( + 'postgres', qq{ + ALTER SYSTEM SET autovacuum_max_workers = 2; + SELECT pg_reload_conf(); +}); + +prepare_for_next_test($node, 3); +$node->safe_psql('regress_db2', 'UPDATE filler SET id = id + 1'); + +my $db2oid = $node->safe_psql('postgres', + "SELECT oid FROM pg_database WHERE datname = 'regress_db2'"); +my $filleroid = + $node->safe_psql('regress_db2', "SELECT 'filler'::regclass::oid"); + +$log_offset = -s $node->logfile; + +# Pause the leader after the shared cost param snapshot. The leader is past +# the hold point below by then, so that one only catches the second worker. +$node->safe_psql('postgres', + "SELECT injection_points_attach('autovacuum-start-parallel-vacuum', 'wait')" +); +$node->safe_psql('postgres', + 'ALTER TABLE test_autovac SET (autovacuum_enabled = true)'); +$node->wait_for_event('autovacuum worker', + 'autovacuum-start-parallel-vacuum'); + +# Second worker -> balance = 2. Hold it there: if it were allowed to finish, +# the balance would drop back to 1 before the leader resumes. +$node->safe_psql('postgres', + "SELECT injection_points_attach('autovacuum-worker-cost-balanced', 'wait')" +); +$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=250,/, + $log_offset); + +$node->safe_psql('postgres', + "SELECT injection_points_wakeup('autovacuum-start-parallel-vacuum')"); +$node->safe_psql('postgres', + "SELECT injection_points_detach('autovacuum-start-parallel-vacuum')"); + +# First param load must show the rebalanced limit. +$node->wait_for_log( + qr/parallel autovacuum worker updated cost params: cost_limit=\d+,/, + $log_offset); +my $log = slurp_file($node->logfile, $log_offset); +my @limits = + $log =~ /parallel autovacuum worker updated cost params: cost_limit=(\d+),/g; +note("parallel worker cost_limit sequence: @limits"); +is($limits[0], '250', 'parallel workers see the rebalanced cost limit'); + +# Release the second worker. +$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->stop; done_testing(); -- 2.55.0