These three parameters together set the pace of autovacuum: how often it considers running, how hard it works while running, and how long it pauses to keep from monopolizing your I/O. Like the analyze pair, they are best understood as a unit.
autovacuum_naptime
Default 1min (60 seconds), context sighup. The autovacuum launcher wakes up this often, considers what needs doing, and may start a worker. The actual cadence is per-database: with N databases, the launcher tries to start one worker every naptime / N seconds, so each individual database is examined roughly once per naptime.
The default is correct for clusters with one or a handful of databases. It becomes a problem when you have many databases — say, multi-tenant deployments with dozens or hundreds — because each database now waits proportionally longer between checks. With 60 databases and the default, a given database is checked once a minute, but the launcher is firing every second, which is fine. With 600 databases, the math still works in principle but the launcher overhead becomes noticeable. Lower autovacuum_naptime to 15s or 30s in heavy multi-database environments.
How vacuum throttles itself: cost-based delay
Vacuum doesn’t just run flat-out. It accumulates a “cost” as it works — vacuum_cost_page_hit (default 1) per buffer hit, vacuum_cost_page_miss (default 2) per buffer miss, vacuum_cost_page_dirty (default 20) per dirtied page. When the accumulated cost exceeds autovacuum_vacuum_cost_limit, the worker sleeps for autovacuum_vacuum_cost_delay milliseconds, then resets and continues.
This is how PostgreSQL keeps autovacuum from saturating your disks during normal operation. It is also how an inattentively-tuned cluster ends up with vacuum that cannot keep up with its workload.
autovacuum_vacuum_cost_delay
Default 2ms, context sighup. Special value -1 means “inherit from vacuum_cost_delay.”
The 2ms default is recent. Before PostgreSQL 12, it was 20ms, calibrated for spinning disks of the late 2000s and the I/O subsystems built around them. A 10x acceleration of the default in 2019 acknowledged that the world had moved on. On NVMe, even 2ms per cost-cycle is conservative. Setting it to 1ms or 0 (no delay at all) is reasonable on modern storage — at which point the cost system effectively becomes “vacuum at full speed,” gated only by the cost limit’s per-cycle work allowance, which on a fast machine is consumed quickly enough that cycles overlap.
autovacuum_vacuum_cost_limit
Default -1 (inherit from vacuum_cost_limit, which defaults to 200), context sighup.
This is the total budget across all currently running autovacuum workers, as discussed in autovacuum_max_workers. With the inherited 200 and the 2ms delay, a single worker accumulates 200 cost units, sleeps 2ms, and repeats — roughly 100,000 cost units per second. In page terms, that’s about 800MB/sec of buffer-hit reads, 400MB/sec of buffer-miss reads, or 40MB/sec of dirtied pages. The actual throughput is some weighted mix, usually dominated by misses and dirties.
Raise it. The default of 200 is a museum piece. On contemporary hardware, 1000 or 2000 is a sensible starting point — enough to let vacuum make real progress without producing query-time hiccups. Then watch your monitoring and adjust. If you have raised autovacuum_max_workers per the previous post, raise the cost limit proportionally so each worker still has a meaningful budget.
How they interact
The three parameters answer three different questions:
autovacuum_naptime— how often does autovacuum consider running?autovacuum_vacuum_cost_limit— how much work does it do per cycle (across all workers)?autovacuum_vacuum_cost_delay— how long does it pause between cycles?
Throughput is roughly (cost_limit / cost_delay) × cost-per-page and is divided among workers. Naptime is independent and governs the scheduling cadence, not the per-vacuum throughput. Tuning cost limits without considering naptime is fine; tuning naptime down without raising cost limits just gets you to “we’re starting the same too-slow vacuums more often.”
Recommendation: Lower autovacuum_naptime to 15s if you have many databases, otherwise leave it. Set autovacuum_vacuum_cost_delay to 1ms on SSD, 0 on NVMe. Raise autovacuum_vacuum_cost_limit to at least 1000, more if you have raised autovacuum_max_workers. The defaults were calibrated for 2008. You are not running a 2008 database server.