"Postgres 19 REPACK CONCURRENTLY: Reclaim Bloat, No Downtime"
Postgres 19 REPACK CONCURRENTLY reclaims table bloat without locking writers — requirements, how the copy-and-swap works, monitoring, and pg_repack migration.
Postgres 19 repack concurrently takes the job the pg_repack extension has done for a decade — rebuilding a bloated table while the application keeps writing — and puts it in Postgres core. Table bloat is the slow rot of write-heavy databases: dead tuples accumulate faster than autovacuum clears them, the table file balloons, sequential scans crawl, and the old nuclear option ( VACUUM FULL ) takes an ACCESS EXCLUSIVE lock that freezes your table for the entire rewrite. REPACK (CONCURRENTLY) rebuilds the table in the background, replays concurrent writes, and swaps the new file in with only a brief lock at the end. It's in PG19 beta4 — with the beta caveats you'd expect. BETA FLAG: PostgreSQL 19 is still in beta (beta4 shipped 2026-09-24; RC1 scheduled 2026-10-15; GA scheduled 2026-10-29). Core REPACK is confirmed still shipping as of beta4 — and beta4 shipped several fixes for it — but don't run it in production until GA. Verify behavior against the PG19 release notes. Last updated: October 4, 2026 What you'll learn Why tables bloat and when VACUUM can't fix it How REPACK (CONCURRENTLY) works: copy, catch-up replay, swap The requirements (wal_level, primary key, disk, MAINTAIN privilege) and hard limitations How to monitor a running repack with pg_stat_progress_repack Whether to keep the pg_repack extension after upgrading The Problem: Bloat VACUUM Can't Reclaim Regular VACUUM marks dead-tuple space as reusable *inside* the table file, but it never gives the space back to the OS — the file only shrinks from the tail, and only if the tail pages are entirely empty. A table with heavy churn and scattered dead tuples stays fat forever. You can see it: SELECT relname, n_live_tup, n_dead_tup, round(100.0 * n_dead_tup / NULLIF(n_live_tup +…