Does VACUUM FULL, CLUSTER or REINDEX lock the table?
VACUUM FULL / CLUSTER / REINDEX rewriting under full lock
Critical: this fails outright or takes production down.
What happens
VACUUM FULL and CLUSTER rewrite the entire table under ACCESS EXCLUSIVE; plain REINDEX locks writes on the table while rebuilding. All three look like harmless maintenance and take the table down for the duration. Plain VACUUM (without FULL) needs no such lock, and REINDEX CONCURRENTLY avoids the write block.
Why it is dangerous on a populated table
On an empty table this finishes before anyone notices. On a populated one the lock is held for the whole operation, every query on the table queues behind it, and the connection pool fills from the back.
Measured
Both forms ran against the same table (20M rows, 1.7 GB) on Postgres 18.6, with one session inserting a row and another reading one row every 50 ms. Worst wait is the slowest of those probe calls while the statement ran. This run is from Sep 16, 2026; the whole benchmark has every scenario and how to run it.
| Statement | Time | Worst write wait | Worst read wait | Lock | |
|---|---|---|---|---|---|
| Fires | VACUUM FULL (rewrite under ACCESS EXCLUSIVE) | 9.0 s | 9.0 s | 9.0 s | AccessExclusiveLock, ShareLock, rewrite |
| Safe | VACUUM (ANALYZE), no rewrite | 2.8 s | 89 ms | 9 ms | ShareUpdateExclusiveLock |
Fires on
VACUUM FULL orders;The safe pattern
Plain VACUUM (ANALYZE) reclaims space for reuse with no exclusive lock, it is almost always what was wanted. Rebuild specific indexes online with REINDEX ... CONCURRENTLY (Postgres 12+). If the table itself is genuinely bloated, pg_repack rebuilds it online; VACUUM FULL and CLUSTER belong in maintenance windows only.
VACUUM (ANALYZE) orders; -- no exclusive lock, frees space for reuse
-- index bloat, own migration outside a transaction (PG12+):
-- REINDEX INDEX CONCURRENTLY idx_orders_region;
-- table bloat: pg_repack rebuilds online without ACCESS EXCLUSIVEFixtures
The rule ships with these files and the test suite runs them on every change: the first set must fire, the second must stay silent.
Fires (3)
CLUSTER orders USING orders_pk;REINDEX TABLE orders;VACUUM FULL orders;Stays silent (2)
VACUUM orders;REINDEX TABLE CONCURRENTLY orders;How to check locally
This rule runs locally in the free CLI, or with the full corpus through the hosted service. No install, nothing leaves your machine:
npx bolvrk check migration.sql