From b6f63c59dcf0a2d00ffa2a60099fd1381192f93d Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy Date: Tue, 20 Oct 2026 16:06:32 +0000 Subject: [PATCH v2] 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 makes the leader and its workers share the cost limit. For each process, the delay is now calculated as vacuum_cost_delay * accumulated_balance * active_processes / vacuum_cost_limit, with a maximum of vacuum_cost_delay * 4 * active_processes. 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 Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/CALj2ACWDhGH_ACJYHvxHVbL9GZ8AgMoJFnf-u5%3DbWx7NZtYBRQ%40mail.gmail.com Backpatch-through: 19 --- doc/src/sgml/config.sgml | 10 +++++++++- doc/src/sgml/ref/vacuum.sgml | 6 ++++-- src/backend/commands/vacuum.c | 30 +++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index f7db2995c3e..6f2a5f1e0ce 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -10012,7 +10012,15 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; delay is calculated as vacuum_cost_delay * accumulated_balance / vacuum_cost_limit with a maximum of - vacuum_cost_delay * 4. + vacuum_cost_delay * 4 for a non-parallel vacuum. + For , each process + uses a share of the limit, so the delay is calculated as + vacuum_cost_delay * + accumulated_balance * + active_processes / + vacuum_cost_limit with a maximum of + vacuum_cost_delay * 4 * + active_processes. 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 c7595d59bb2..a9f0d8a6a16 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2545,16 +2545,22 @@ vacuum_delay_point(bool is_analyze) if (VacuumSharedCostBalance != NULL) msec = compute_parallel_delay(); else if (VacuumCostBalance >= vacuum_cost_limit) + { msec = vacuum_cost_delay * VacuumCostBalance / vacuum_cost_limit; + /* + * Avoid uselessly long delays. The cost can accumulate far higher + * than the limit, as delays are not applied while critical locks are + * held or interrupts are pending. + */ + msec = Min(msec, vacuum_cost_delay * 4); + } + /* Nap if appropriate */ if (msec > 0) { instr_time delay_start; - if (msec > vacuum_cost_delay * 4) - msec = vacuum_cost_delay * 4; - if (track_cost_delay_timing) INSTR_TIME_SET_CURRENT(delay_start); @@ -2649,10 +2655,11 @@ vacuum_delay_point(bool is_analyze) * We allow each worker to update it as and when it has incurred any cost and * then based on that decide whether it needs to sleep. We compute the time * to sleep for a worker based on the cost it has incurred - * (VacuumCostBalanceLocal) and then reduce the VacuumSharedCostBalance by - * that amount. This avoids putting to sleep those workers which have done less - * I/O than other workers and therefore ensure that workers - * which are doing more I/O got throttled more. + * (VacuumCostBalanceLocal) and the number of active workers, as the cost + * limit is for the whole vacuum and not for each worker, and then reduce the + * VacuumSharedCostBalance by that amount. This avoids putting to sleep those + * workers which have done less I/O than other workers and therefore ensure + * that workers which are doing more I/O got throttled more. * * We allow a worker to sleep only if it has performed I/O above a certain * threshold, which is calculated based on the number of active workers @@ -2685,8 +2692,13 @@ 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 nworkers */ + msec = vacuum_cost_delay * VacuumCostBalanceLocal * nworkers / + vacuum_cost_limit; + + /* Avoid uselessly long delays, as in a non-parallel vacuum */ + msec = Min(msec, vacuum_cost_delay * 4 * nworkers); + pg_atomic_sub_fetch_u32(VacuumSharedCostBalance, VacuumCostBalanceLocal); VacuumCostBalanceLocal = 0; } -- 2.47.3