require-concurrent-reindex
REINDEX without CONCURRENTLY acquires ACCESS EXCLUSIVE, blocking all operations.
- operation
- Indexes
- lock taken
- ACCESS EXCLUSIVE
- remediation
- Fixed by --fix
- category
- Lock safety
- Blocks reads
- Blocks writes
- Can stall the lock queue
What triggers it
Fires on any ReindexStmt (REINDEX TABLE/INDEX/SCHEMA/DATABASE) once ctx.pgVersion is 12 or higher, as long as it is not REINDEX_OBJECT_SYSTEM and does not already carry the concurrently DefElem in its params.
What does not
Non-ReindexStmt nodes are skipped outright. It also returns null when ctx.pgVersion is below 12 (CONCURRENTLY did not exist yet), when the target is REINDEX SYSTEM (which PostgreSQL will not run CONCURRENTLY), and when the statement already sets concurrently.
Where it applies
Applies to PostgreSQL 12 and later. It works on the SQL text alone — no database connection needed.
REINDEX CONCURRENTLY requires PostgreSQL 12 or later; on older versions the rule does not fire at all since there is no safe CONCURRENTLY form to suggest.
The lock, and what it blocks
REINDEX TABLE holds ACCESS EXCLUSIVE on the whole table for the rebuild; REINDEX INDEX holds SHARE, which still blocks writes. Either way the block lasts as long as the index build takes, scaling with table size.
Why it matters
REINDEX rebuilds the index while blocking all reads and writes. On PG 12+, REINDEX CONCURRENTLY rebuilds the index without blocking concurrent operations.
Unsafe, and safe
Flagged
REINDEX INDEX idx_users_email;
Safe alternative
REINDEX INDEX CONCURRENTLY idx_users_email;
Deploy and transaction boundaries
REINDEX CONCURRENTLY carries the same restriction as CREATE INDEX CONCURRENTLY (see MP025): it cannot run inside a transaction block, so it needs to be the only statement in its deploy step rather than batched with other DDL.
What it assumes
The rule fires on syntax and PostgreSQL version alone, with no size or traffic signal, so a REINDEX on a tiny or idle table is flagged the same as one on a huge, hot one.
What backs this rule
Every rule is a claim about PostgreSQL, so it carries what the claim rests on: a handbook chapter that cites the manual, the incidents that put it there, and the version it was last checked against.
When CREATE INDEX CONCURRENTLY fails, it does not clean up after itself.
- verified against
- PostgreSQL 17.10
- last checked
- 2026-08-11
- confidence
- High
PostgreSQL manual
Public incidents and write-ups
What the CLI prints
⚠ [MP021] WARNING (line 1) REINDEX INDEX "idx_users_email" without CONCURRENTLY blocks all writes (or reads for tables). On PostgreSQL 17, use REINDEX CONCURRENTLY instead. Safe alternative: REINDEX INDEX CONCURRENTLY idx_users_email Why: REINDEX without CONCURRENTLY acquires ACCESS EXCLUSIVE (table) or SHARE (index) locks, blocking all queries for the duration. REINDEX CONCURRENTLY (PG 12+) builds the new index without blocking reads or writes. Docs: https://migrationpilot.dev/rules/mp021
Generated by running the CLI's own formatter over the flagged example above, so it is the text the tool actually produces. A real run also reports the other rules that fire on the same statement; those blocks are left out here.
Turning it off
For one statement, put a comment on the line before it:
-- migrationpilot-disable MP021 REINDEX INDEX idx_users_email;
For the whole project, in .migrationpilotrc.yml — by name or by id:
rules:
MP021: false
# or keep it, and downgrade it
rules:
MP021:
severity: warningTry it
Open this rule's flagged example in the playground. It runs in your browser — edit it and watch the finding appear and disappear.
Run MP021 in the playground