From 256f1de1b9c6db0eccb2ebd8e2256ea6ac52e082 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy Date: Tue, 13 Oct 2026 22:28:29 +0000 Subject: [PATCH v1] Parallel vacuum: workers together with leader could overrun cost budget Each process taking part in a parallel vacuum sleeps for every vacuum_cost_limit cost units it incurs on its own, so the leader and its workers each run at the full budget and together overrun it. This commit scales the sleep by the number of active workers, so that each process gets vacuum_cost_limit divided by the number of processes working at the same time. The documentation is updated to say that the limit is shared. Although this exists for parallel vacuum too, back-patch only through 19, where parallel autovacuum was introduced, since parallel autovacuum runs with the default cost delay and cost limit. Author: Bharath Rupireddy Discussion: https://postgr.es/m/ Backpatch-through: 19 --- doc/src/sgml/ref/vacuum.sgml | 6 ++++-- src/backend/commands/vacuum.c | 13 +++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index 3c85846df26..bb4015348f5 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -481,8 +481,10 @@ VACUUM [ ( option [, ...] ) ] [ VACUUM causes a substantial increase in I/O traffic, which might cause poor performance for other active sessions. Therefore, it is sometimes advisable to use the cost-based vacuum delay feature. For - parallel vacuum, each worker sleeps in proportion to the work done by that - worker. See for + parallel vacuum, the leader and its parallel workers share the cost limit. + Each of them sleeps in proportion to the work it has done, and sleeps + longer when more of them are working at the same time. + See for details. diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index a257dd8d21e..bbcbf315961 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2657,6 +2657,11 @@ vacuum_delay_point(bool is_analyze) * VacuumCostLimit set by the system. Testing reveals that we achieve * the required throttling if we force a worker that has done more than 50% * of its share of work to sleep. + * + * The cost limit applies to the whole vacuum, not to each process taking + * part in it. So each process gets a share of the limit, that is + * vacuum_cost_limit divided by the number of active workers, and sleeps + * that many times longer than it would if it were vacuuming alone. */ static double compute_parallel_delay(void) @@ -2682,8 +2687,12 @@ compute_parallel_delay(void) if ((shared_balance >= vacuum_cost_limit) && (VacuumCostBalanceLocal > 0.5 * ((double) vacuum_cost_limit / nworkers))) { - /* Compute sleep time based on the local cost balance */ - msec = vacuum_cost_delay * VacuumCostBalanceLocal / vacuum_cost_limit; + /* + * Compute sleep time based on the local cost balance and the number + * of active workers. + */ + msec = vacuum_cost_delay * VacuumCostBalanceLocal * nworkers / + vacuum_cost_limit; pg_atomic_sub_fetch_u32(VacuumSharedCostBalance, VacuumCostBalanceLocal); VacuumCostBalanceLocal = 0; } -- 2.47.3