Skip to content

    Back to articles

    6 min read

    PostgreSQL 19 parallel autovacuum is a resource-budget decision

    Faster maintenance spends shared capacity

    PostgreSQL 19 Beta 4 is available, and its release notes for PostgreSQL 19 list parallel autovacuum as a feature. I would treat it as a change to the database’s concurrency budget, not as a free reduction in maintenance time.

    The feature allows autovacuum to use parallel workers for index vacuuming and cleanup. It does not turn every part of a vacuum into parallel work. The PostgreSQL commit introducing parallel autovacuum describes those phases and the new controls: autovacuum_max_parallel_workers sets a per-autovacuum-worker ceiling, while the table storage parameter autovacuum_parallel_workers can cap or disable parallelism for a particular table. The commit also says the new global setting defaults to 0, preserving the previous behavior unless an operator enables it.

    That scope matters. More workers may shorten an index-heavy maintenance phase, but they also create more simultaneous work for the CPU, storage, and shared worker pool. Whether that is a win depends on what else the database is doing when maintenance runs. If foreground queries already compete for CPU or storage bandwidth, bringing vacuum work forward can trade maintenance lag for slower requests. That is not automatically a bad trade: it is a choice about which work gets access to scarce capacity, and when.

    I would start with the pressure that prompted the change. Is maintenance falling behind, or is the goal simply to make a vacuum finish sooner? If the system is accumulating dead tuples or struggling to keep up with routine maintenance, parallelism may be worth testing. If maintenance is already completing comfortably and foreground latency is the constraint, increasing concurrency may solve the wrong problem.

    Enable it as a measured experiment

    I would not begin by enabling extra workers across every table. First, I would identify the relations that are responsible for meaningful maintenance work and check whether their index work is a plausible target. Then I would test those relations under a workload that resembles the production mix: foreground reads and writes, plus the maintenance activity that normally overlaps with them.

    The configuration below shows the controls, not a recommended production setting. The 0 on the global setting leaves the feature disabled; the table parameter can be used to limit the scope of a test. The upstream commit introducing the settings documents that positive per-table values cap the worker count, subject to the global setting.

    -- Keep parallel autovacuum disabled globally while preparing a test.
    ALTER SYSTEM SET autovacuum_max_parallel_workers = 0;
    
    -- For a test, set an explicit cap on one selected relation.
    ALTER TABLE app.events
      SET (autovacuum_parallel_workers = 2);

    I would change the global setting only for a controlled test, and verify the effective configuration before interpreting results. A per-table cap is not a guarantee that workers will always launch: the global limit and available worker capacity still constrain execution. Nor does a configured cap prove that parallel work improved the maintenance outcome. The PostgreSQL release notes describe the feature; the useful evidence for my system is what happened under its workload.

    For a comparison, I would hold the workload and observation window steady, and compare the baseline with a deliberately limited parallel setting. I would record maintenance duration and whether maintenance keeps up, alongside foreground query latency, CPU pressure, and storage activity. I would also include the periods when the database is busiest. A test that shows shorter vacuum duration while the system is otherwise idle does not tell me whether the change is safe when application demand peaks.

    I would avoid changing several vacuum settings at once. If I raise worker counts, cost limits, and memory settings together, I lose the ability to tell which change affected the result. Parallel autovacuum is specifically about adding workers to eligible maintenance work; it is not a substitute for understanding why vacuum is behind or for testing the effect of that extra concurrency.

    Put limits where the contention is

    A global maximum is a guardrail, not a complete resource budget. It limits the parallel workers an autovacuum worker may launch, while the table parameter gives a narrower control. The commit description also makes clear that the available parallel-worker pool constrains the actual number. I would therefore assess parallel autovacuum alongside other uses of that shared capacity, rather than treating its setting as an isolated knob.

    My preference is to start with a small, explicit scope: a selected table, a known maintenance problem, and a test that can be stopped or rolled back. I would keep the global ceiling conservative until measurements show that the additional work does not materially harm foreground queries. If the workload is CPU-bound, I would be especially cautious about allowing more simultaneous work. If storage is the limiting factor, more workers can increase contention there even when they reduce the elapsed time of one maintenance operation.

    The right limit depends on the machine and the workload, so I would not copy a worker count from another database. A value that is safe on an underused test instance can be wrong on a saturated production system. I would also avoid treating a faster individual vacuum as proof of success: the operational goal is to keep maintenance healthy without making the service less responsive to the work users are waiting on.

    Monitor the trade, not just the setting

    I would watch the maintenance process and the foreground service together. PostgreSQL’s pg_stat_progress_vacuum documentation describes the view for monitoring vacuum progress. PostgreSQL 19’s release notes also mention additional vacuum initiation details in that view. Progress data can help establish what maintenance is doing, but it does not answer whether the workload is better off. I would pair it with the application’s latency and error signals, and with host or platform measurements for CPU and storage pressure.

    I would keep the change only if the test demonstrates a useful maintenance improvement while foreground behavior stays within the service’s own limits. If maintenance completes sooner but pushes important queries outside their latency budget, the setting is not safe at that scope. If foreground behavior is stable but maintenance remains behind, I would investigate the remaining bottleneck rather than assume another worker will fix it.

    That is the decision I would carry into a PostgreSQL 19 rollout: parallel autovacuum is a way to spend more of the database’s concurrent capacity on maintenance. I would enable it where the evidence shows that trade is worth making, and keep the limits and monitoring close to the workload it is meant to help.

    Drafted with AI assistance, reviewed and edited by Ali.