Chapter Seven · Vacuum, Bloat, and Freezing
Vacuum, Bloat, and Freezing
Twelve thousand rows in 900 MB, and nobody deleted anything. Five topics take Cartwheel's inventory table apart: what vacuum reclaims and what it never gives back, why the shipped autovacuum defaults were designed for a different database, the two schema decisions that decide whether an update costs one write or four, and the one Postgres failure mode that stops writes cluster-wide with no warning at all.
Chapter 6 explained why Postgres keeps several versions of a row and what that buys: readers that never wait for writers, and the last box of strawberries sold exactly once. This chapter is the invoice. Every version the engine kept for a snapshot that has since gone away is still physically on disk, and something has to remove it — not as housekeeping, but as the half of the design that makes the other half affordable.
The evidence has been sitting in the schema since Chapter 1. inventory holds 12,000 live rows and occupies 900 MB, in a table that has never had a DELETE run against it. Three separate causes built that number, and each is a topic here: autovacuum running with defaults chosen for a much smaller database, a throttle calibrated for storage nobody buys any more, and a schema that made every single checkout write four times as much as it needed to. By the end of the chapter the table is 616 kB and the settings that got it there are attached to the table itself, where they survive a restart and a restore.
The last topic is different in kind from everything before it. Bloat is expensive and recoverable; freezing debt is a countdown that gives no signal until the cluster refuses writes for the whole database. It is the one number in this book worth waking someone for, and setting the alert takes five minutes. Chapter 8 then picks up the thread this chapter keeps pulling — that the indexes on a table are a permanent tax on every write and every vacuum, and that some of the ones on orders have never been used at all.
Topics in This Chapter
on_hand that served no query and taxed every write.pgstattuple before anything irreversible, and pgstatindex for the leaf density vacuum never improves. What VACUUM FULL and pg_repack each lock and need — and the cases where the right move is to leave the bloat alone.