Question: Vacuum, bloat and wraparound
How do I make VACUUM run faster?
Answered in the first paragraph. Last updated .
Give it more memory, remove the pacing that is deliberately slowing it down, and let the index phase use more than one core. Those three cover nearly every vacuum that is genuinely slow. First check that it is slow rather than blocked, because a vacuum that scans an entire table and is permitted to delete nothing finishes at exactly the same speed no matter what you tune.
The three that move the number
Memory first. The dead row pointers a vacuum collects live in a workspace sized by the maintenance memory setting, and when that fills the vacuum must stop, walk every index, and start again. Two passes over the indexes of a large table is usually most of the runtime. On PostgreSQL 17 and later the workspace grows instead of being capped, which removed the ceiling that made this the dominant cost for a decade. Autovacuum workers take their budget from a separate setting so that raising it for a manual run does not multiply across every worker at once.
Pacing second. Autovacuum sleeps on purpose after doing a fixed amount of work, and on a server with fast storage that delay is most of the elapsed time. The delay and the budget are both per table as well as global, so one enormous table can be allowed to run flat out while the rest of the schema stays polite.
Parallelism third, and with a caveat: the option that spreads index cleanup across workers belongs to the command you type. The background daemon does not use it. That makes it a tool for a planned catch-up rather than for everyday maintenance.
When speed is not the problem
If something old is still entitled to see the dead rows, vacuum will read every page and remove none of them. That is the xmin horizon, and tuning throughput against it just burns more I/O for the same zero rows reclaimed. The log line each run prints names the transaction that held it back, which is the fastest way to tell this case apart.
The other structural cost is index count. Every index is another full pass, so a table carrying a dozen of them pays a dozen times, and dropping one that nothing uses is a larger win than any setting here.
Measure before and after rather than trusting the change. PostgreSQL 18 records the cumulative time spent vacuuming each table, which turns this from an argument about settings into a ranked list. Autovacuum and table bloat has the thresholds that decide how often the work is triggered in the first place.