What VACUUM taught me about batching writes
Every Postgres table I've ever inherited had the same invisible reviewer: autovacuum, running behind me, grading my write patterns. It took writing pgstat — an exporter for WAL rates, slot lag and bloat — before I started reading the feedback properly.
Here's the version of the lesson I wish I'd skipped to.
Dead tuples are an invoice
Postgres doesn't overwrite rows in place. Every UPDATE writes a new tuple, and the old one hangs around as garbage until VACUUM collects it. A workload that updates the same row 50 times a minute is, structurally, a workload that writes 50 rows a minute and asks the database to clean up quietly in the background.
If vacuum can't keep up, you don't get an error. You get:
- tables that grow on disk while
pg_stat_user_tablesshows stable row counts, - index bloat that makes every lookup pay a tax,
- and eventually
wraparound, the maintenance window you can no longer postpone.
The pattern that made it stop
The fix wasn't tuning autovacuum. It was changing what we asked of it — batching:
-- before: 40k single-row updates per minute
UPDATE counters SET value = value + 1 WHERE key = $1;
-- after: one statement per partition batch
UPDATE counters AS c
SET value = c.value + b.value
FROM unnest($1::counter_batch[]) AS b(key, value)
WHERE c.key = b.key;
Same business logic, 1/50th the dead tuples, and an easier time for autovacuum to stay ahead. The dashboards stopped looking like sawtooths.
What I watch now
Write amplification is the number I check first on any new service:
n_tup_updvsn_tup_hot_upd— HOT updates skip index maintenance; a low HOT ratio means your indexes are getting rewritten constantly.pg_stat_all_tables.n_dead_tuptrended over a week, not a point in time.- WAL bytes generated per business event. This is the honest cost of your write path, and it doesn't lie the way query latencies do.
None of this is exotic. It is just the database telling you exactly what you're doing to it, in tables designed for that purpose, and me finally being bothered to listen.