Enforce Zero-Downtime Migrations in Django
Flags Django migrations that lock or rewrite large PostgreSQL tables, or break the release that is still serving traffic during a deploy.
What it flags
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("orders", "0042_order_external_ref")]
operations = [
# Plain CREATE INDEX blocks writes to orders until the build finishes
migrations.AddIndex(
model_name="order",
index=models.Index(fields=["external_ref"], name="order_external_ref_idx"),
),
]from django.contrib.postgres.operations import AddIndexConcurrently
from django.db import migrations, models
class Migration(migrations.Migration):
# CREATE INDEX CONCURRENTLY cannot run inside a transaction
atomic = False
dependencies = [("orders", "0042_order_external_ref")]
operations = [
AddIndexConcurrently(
model_name="order",
index=models.Index(fields=["external_ref"], name="order_external_ref_idx"),
),
]Why it matters
Django runs each migration in a transaction on PostgreSQL, so a plain CREATE INDEX or SET NOT NULL on a large table holds a lock that blocks writes until the migration commits. During a rolling deploy the previous release keeps serving traffic against the new schema: a dropped column breaks its queries, and a non-nullable column without a database default breaks its inserts. makemigrations generates these operations by default and they finish instantly on a small development database, so the risk only shows up in production.
Agent instructions
cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.
AGENTS.md
Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.
## Enforce Zero-Downtime Migrations in Django Applies to `**/migrations/*.py`. Review Django migrations for operations that lock or rewrite large tables or break the release still serving traffic during a deploy. Flag changes that: - Add a non-nullable field to an existing table without `db_default`, including `AddField` with a one-off default and `preserve_default=False`; Django drops the column default afterwards, so inserts from the previous release fail. - Make an existing column non-nullable or change its type with `AlterField` (for example `IntegerField` to `BigIntegerField`) on a large table, which scans or rewrites it under an ACCESS EXCLUSIVE lock. - Add an index to an existing PostgreSQL table through `AddIndex`, `db_index=True`, or a new `ForeignKey` (indexed by default) instead of `AddIndexConcurrently` in a migration with `atomic = False`. - Remove or rename a field or model (`RemoveField`, `RenameField`, `DeleteModel`, `RenameModel`) in the same release as the code change, instead of making the column nullable, removing it from model state with `SeparateDatabaseAndState`, and dropping it in a later release. - Import models directly in `RunPython` instead of `apps.get_model()`, or backfill a large table row by row with `save()` inside the migration's transaction. Do not flag: - Migrations that create new tables, or alter tables created earlier in the same pull request. - New unindexed columns that are nullable or have a constant `db_default` (PostgreSQL 11 or later). - Increasing a `CharField`'s `max_length`, which PostgreSQL applies without rewriting the table. - The `AddIndexConcurrently` requirement in projects that do not use PostgreSQL.
Checks files matching **/migrations/*.py.
More Django agents
See all Django code review standards
Run this agent on every pull request
cubic reviews every pull request against the agents you turn on and comments on the lines that break them.