Repository navigation
fix(migrations): make 0305 archive_batch indexes safe to re-run after a partial failure - #16673
Conversation
An interrupted CREATE INDEX CONCURRENTLY leaves an INVALID index behind, and every retry of 0305 then failed with "relation ... already exists". Each operation now keeps a valid index of the same name, drops an invalid leftover, and then builds. Migration state is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Killing the migration process does not stop its CREATE INDEX CONCURRENTLY on the server. A retry saw that index as INVALID, waited for it to finish inside DROP INDEX CONCURRENTLY, then dropped and rebuilt it, scanning the table twice. The operation now waits for a build of the same index listed in pg_stat_progress_create_index before deciding, and keeps its result. Adds tests for the four catalog states a retry can meet: an INVALID leftover, a valid index, no index, and a build still in progress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adversarial review: full tier, OpusTier: CI: every check on the head commit is green. That includes ruff, Migration Graph Check, both Rest Framework Unit Tests jobs (8499 tests, Regression test: I ran it locally on PostgreSQL 16 in a throwaway
The tests exercise the real catalog states rather than mocks. Break attempts
Static only / not checked
Findings
Recommendationchanges required: 1 |
A long wait on a large table logged the same warning every 5 seconds. It now logs when the wait starts and when it ends, and the in-flight test asserts exactly those two records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Description
Migration
0305_archive_batch_indexes(new in 3.5.0) builds 11 partial indexes withAddIndexConcurrentlyin a non-atomic migration. If the process dies partway through, which we observed during a production rollout when the migration container was OOM-killed, the interruptedCREATE INDEX CONCURRENTLYleaves its index in the catalog marked INVALID. Because the migration was never recorded as applied, the next attempt runs it again and fails on the first index that already exists:That repeats on every retry, so the upgrade cannot finish without someone cleaning up by hand.
This PR edits 0305 in place. Each operation is now an
AddIndexConcurrentlysubclass defined in the migration file that checkspg_indexfor an index of the same name before building:DROP INDEX CONCURRENTLY IF EXISTS, then rebuilt,Killing the migration process does not stop its build on the server, which runs on to completion. So before deciding, the operation checks
pg_stat_progress_create_indexfor a build of the same index from another backend and waits for it to finish (polling every 5 seconds, with one log line when the wait starts and one when it ends). Without that wait, the retry would see the in-flight index as INVALID, block insideDROP INDEX CONCURRENTLYuntil the old build finished, then drop the finished index and build it again, scanning a large table such asdojo_findingtwice.Migration state is inherited from
AddIndexConcurrently, somakemigrations --checkis unaffected. The reverse path was already idempotent (DROP INDEX CONCURRENTLY IF EXISTS). Editing in place is safe: an install that recorded 0305 as applied never runs it again, and one stuck on it has not recorded it.One side effect:
sqlmigrate dojo 0305now reads the catalog, so against a database that already has all 11 valid indexes it prints(no-op)for each operation.Test results
New tests in
unittests/test_migration_0305_archive_batch_indexes.pyrun the 0305 operation fordojo_testagainst the real catalog. They areSimpleTestCasetests on autocommit connections, becauseCREATE INDEX CONCURRENTLYcannot run inside aTestCasetransaction, and they restore the index afterwards. They cover four states:CREATE UNIQUE INDEX CONCURRENTLYover duplicate rows): dropped and rebuilt ondojo_test,All 4 pass with
PYTHONWARNINGS=error. Against the original 0305 they fail withrelation "dojo_test_archive_batch" already exists.Manual checks on PostgreSQL 16, starting from a fresh database migrated to
0304:django_migrationsrow, marked two indexes INVALID (one withindisready = falseas well) and dropped two. Re-running the original migration failed with the error above.pg_get_indexdefoutput for all 11 matches a fresh run.0304drops all 11, forward again recreates them.makemigrations dojo --check --dry-run: no changes detected.ruff check --config ruff.tomlon both files: clean.Not tested: a production-sized table, PostgreSQL versions other than 16, and two migration processes started at the same moment.
Documentation
None needed.
Checklist
dev.devbranch.🤖 Generated with Claude Code