Dangerous schema changes on large PostgreSQL tables and how to make them safely

Django Interview Q&A November 13, 2026 python

Every ALTER TABLE takes an ACCESS EXCLUSIVE lock.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# ======================================================================
# Q: Which schema changes are dangerous on a large table, and how do you perform
# them safely?
#
# Every ALTER TABLE takes an ACCESS EXCLUSIVE lock. If it waits behind a slow
# query, it blocks all the queries that follow. Rules:
# - Always SET lock_timeout = '5s' in the migration, so it fails fast instead of
#   "freezing" the site. Then retry.
# - ADD COLUMN with a constant default is fast since PostgreSQL 11 (it does not
#   rewrite the table).
# - Index: AddIndexConcurrently with atomic = False.
# - NOT NULL or FK on an existing table: first a NOT VALID constraint
#   (AddConstraintNotValid), then ValidateConstraint in a separate migration,
#   which does not block writes.
# - Column type change or rename: expand/contract (new column, dual write,
#   backfill, switch, drop).
# - Backfill in batches of 1,000-10,000 rows, with a short pause, never a single
#   UPDATE over the whole table.
# - Check the SQL before deploying with manage.py sqlmigrate. A CI tool: django-
#   migration-linter.
# ======================================================================

from django.contrib.postgres.operations import AddIndexConcurrently

class Migration(migrations.Migration):
    atomic = False
    operations = [
        migrations.RunSQL("SET lock_timeout = '5s'", migrations.RunSQL.noop),
        AddIndexConcurrently('order', models.Index(
            fields=['company', '-created_at'], name='order_company_created')),
    ]