PostgreSQL 19 beta's REPACK CONCURRENTLY kept a live client's worst stall to 0.33 seconds
REPACK CONCURRENTLY stalled a live client 0.33 seconds at worst in PostgreSQL 19 beta 4, against 3-6 seconds under VACUUM FULL, a dev.to post reported. The runs pit it against VACUUM FULL alone, so retiring pg_repack is still an open question.
The Engineer · Build desk

Key figures: with ten clients, REPACK CONCURRENTLY's worst-case stall per client was 0.28 seconds, against 0.33 seconds with one client. The statement took 3 to 6 seconds of wall-clock time under either VACUUM FULL or REPACK CONCURRENTLY, depending on the run.
| Figure | Value | Note | Claim |
|---|---|---|---|
| REPACK CONCURRENTLY worst stall, ten clients | 0.28 seconds | 7 | |
| Worst stall, one client | 0.33 seconds | 7 | |
| Statement time, either command, at least | 3 seconds | up to 6 seconds, depending on the run | 4 |
What happened
- After half the rows of a 2,000,000-row table were deleted, a plain VACUUM left it at 531 MB, while VACUUM FULL and REPACK each brought it down to 266 MB.
- REPACK CONCURRENTLY copies the table in the background, uses logical decoding to capture changes made during the copy, and takes its lock only at the end for the file swap.
- The author drove a background client through one UPDATE and one SELECT on a live row, counted calls over 250ms as blocked, and ran each command three times.
- REPACK CONCURRENTLY wrote more WAL than VACUUM FULL in every measurement, by roughly 4% to 25%.
Why it matters
- exposure Ten clients reaching the swap window together take ten short stalls at once, and the author says that sum belongs in a capacity plan ahead of the single-client worst case.
- cost Teams limited by WAL-shipping bandwidth to a replica pay for the shorter lock in extra bytes on that link, according to the author.
- decision Anyone repacking several tables at once has to size max_repack_replication_slots first, since each concurrent run needs its own slot and the default is 5.
- contradiction The documentation flags an extra temp file during the copy, yet the test saw a 3 MB difference at a light write rate, so it settles nothing for write-heavy tables.
VACUUM FULL takes an ACCESS EXCLUSIVE lock up front and holds it until the swap is done, so the client in the test was blocked for essentially the whole statement [5]. The statement took 3-6 seconds under either command [4]. The post's headline puts the old lock at five seconds, inside that range [16]. Taking the statement time as the VACUUM FULL block, the worst REPACK stall of 0.33 seconds [7] is roughly 9 to 18 times shorter [18].
We'd expect the lock behavior to carry over to other tables, because it follows from where the lock is taken [5][6]. The 0.33 seconds is a different matter. It was measured on one table of 531 MB [2], with a client looping on a live row [3] and a write load of a few thousand single-row updates a second [12]. It transfers to tables and workloads that look like that one.
With ten clients on the table, the per-client worst case barely moved: 0.28s against 0.33s with one client [7]. Both figures sit above the 250ms line the author used to count a call as blocked [3].
The extra WAL comes from the logical-decoding worker. It decodes and replays every change that landed on the old copy during the rewrite, and that replay goes through WAL like any other write [10]. The width of the range depends on how much VACUUM FULL varied between runs [9].
Disk was nearly a tie. Peak data-directory growth was 276 MB for REPACK CONCURRENTLY and 279 MB for VACUUM FULL, a 3 MB gap of about 1% [11][19]. The author expects a heavier write rate to look different and wrote that there was no time to push the load that far [12].
Each concurrent repack runs its own logical decoding session, so CONCURRENTLY needs a free slot, gated by max_repack_replication_slots [13]. The default is 5 [13]. With two tables under default settings, the author could not trigger the limit [14].
The post describes the option as "supposed to do the rewrite that used to need VACUUM FULL or the third-party pg_repack extension" [15]. The timed runs it reports are VACUUM FULL against REPACK, and pg_repack does not appear in them [3].
What to watch
- A head-to-head of REPACK CONCURRENTLY against pg_repack on the same bloat, with the same client loop and WAL counters.
- The same test at a write rate well above a few thousand single-row updates a second, to see whether the temp file the documentation flags grows past noise.
- Whether REPACK behavior and the max_repack_replication_slots default of 5 survive unchanged from beta 4 to the final PostgreSQL 19 release.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence50
- Adoption
- Insufficient
- Hype gap+10
- Incentives
- Insufficient
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The author built a table of 2,000,000 rows of 200-byte text and deleted every even-numbered row, leaving 1,000,000 rows.
- [2]
The table measured 531 MB before and after deleting 1,000,000 of 2,000,000 rows, even after a plain VACUUM; after either VACUUM FULL or REPACK it measured 266 MB.
- [3]
On PostgreSQL 19 beta 4, a background client ran one UPDATE and one SELECT against a live row in a tight loop, with a 250ms threshold for counting a call as blocked; VACUUM FULL and REPACK (CONCURRENTLY) were each run three times against freshly rebuilt bloat.
- [4]
The statement took roughly the same wall-clock time under either command, 3-6 seconds depending on the run.
- [5]
Under VACUUM FULL the client was blocked for essentially the entire statement, because the command takes an ACCESS EXCLUSIVE lock up front and holds it until the swap is done.
- [6]
Under REPACK CONCURRENTLY the client kept running normally while Postgres copied the table in the background and used logical decoding to capture changes made during the copy; the lock appears only at the end, for the file swap.
- [7]
In the rerun with ten clients, the worst-case stall per client for REPACK CONCURRENTLY barely moved, 0.28s against 0.33s with one client.
- [8]
The author wrote that the summed stall is the more honest number for a capacity plan: ten clients all catching the same brief swap window means ten short stalls landing at once, a different production event from one client stalling once.
- [9]
REPACK CONCURRENTLY wrote more WAL than VACUUM FULL every time it was measured, by roughly 4% to 25% depending on how much VACUUM FULL varied between runs.
- [10]
The extra WAL is the logical-decoding worker decoding and replaying every change that landed on the old copy during the rewrite; that replay goes through WAL like any other write, so a setup limited by WAL-shipping bandwidth to a replica sees the CONCURRENTLY version push more bytes down that link.
- [11]
Peak data-directory growth was 276MB for REPACK CONCURRENTLY against 279MB for VACUUM FULL, which the author called close enough to be noise.
- [12]
The author's write load was a few thousand single-row updates a second; the author expects results to look different under a heavier write rate and did not have time to push it that far.
- [13]
CONCURRENTLY needs a free slot, gated by the setting max_repack_replication_slots, because each concurrent repack runs its own logical decoding session; the default is 5.
- [14]
The author could not trigger the slot limit with two tables under default settings, however the runs were timed.
- [15]
The post says the CONCURRENTLY option is supposed to do the rewrite that used to need VACUUM FULL or the third-party pg_repack extension.
- [16]
The post's headline describes the lock REPACK CONCURRENTLY replaces as five seconds.
- [17]
The documentation specifically calls out an extra temp file for rows written to the old table during the copy.
- [18]
Taking the 3-6 second statement time as the VACUUM FULL block, the 0.33s REPACK CONCURRENTLY worst-case stall is roughly 9 to 18 times shorter.
- [19]
The peak data-directory growth gap between VACUUM FULL and REPACK CONCURRENTLY was 3 MB, about 1% of 279 MB.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toPostgreSQL 19's REPACK CONCURRENTLY swaps a five-second table lock for one under a second
1 article · October 14, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Entities
- dev.toFollow
- PostgreSQLFollow
- pg_repackFollow