All rules Rule BV015 · LK
PostgreSQL
critical
LK · Locks & blocking
free in the CLI

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.

StatementTimeWorst write waitWorst read waitLock
FiresVACUUM FULL (rewrite under ACCESS EXCLUSIVE)9.0 s9.0 s9.0 sAccessExclusiveLock, ShareLock, rewrite
SafeVACUUM (ANALYZE), no rewrite2.8 s89 ms9 msShareUpdateExclusiveLock

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 EXCLUSIVE

Fixtures

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
CLUSTER orders USING orders_pk;
reindex
REINDEX TABLE orders;
vacuum full
VACUUM FULL orders;

Stays silent (2)

plain vacuum
VACUUM orders;
reindex concurrently
REINDEX TABLE CONCURRENTLY orders;

How to check locally

Catch this before it ships

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