Skill: migrations-data-model-changes
You are performing data model and migration work for a Django + Postgres application.
This skill standardizes how to change schemas safely, how to backfill data, and how to avoid outages or irreversible migrations.
When to use this skill
Use this skill whenever you:
- •Add/change/remove a Django model field
- •Add/change constraints (unique, not-null, foreign keys)
- •Add/change indexes
- •Backfill/transform data
- •Change auth/user-related schema
- •Need to evaluate migration risk, downtime, or rollback strategy
Inputs
- •The approved design:
docs/features/<feature-slug>/02-design.md - •Current models + migrations in the repo
- •Current database assumptions (table sizes, traffic, downtime tolerance), if known
If traffic/size is unknown, assume moderate traffic and design for low-risk / minimal locking changes.
Outputs
Design phase outputs
In 02-design.md, include a Data model & migrations section that covers:
- •schema changes (tables/fields/indexes/constraints)
- •migration strategy (expand/contract if needed)
- •backfill approach (if any)
- •rollback strategy
- •operational risk notes (locks, long-running migrations)
Implementation phase outputs
- •Django migration files that implement the plan
- •Updated
docs/features/<feature-slug>/03-implementation-notes.mdwith:- •migration status (created/applied/deferred)
- •exact commands to run
- •any special operational notes
Hard rules
- •Do NOT bypass migrations (no manual DB edits as a substitute).
- •Do NOT introduce irreversible or high-downtime migrations without explicitly documenting the risk and proposing safer alternatives.
- •Do NOT ship schema changes that break backward compatibility unless the design explicitly calls for it and the deployment plan addresses it.
- •Any custom
Usermodel is authoritative and must not be replaced.
Safety model: prefer Expand/Contract
When changes could break existing code or require long operations, use the expand/contract pattern:
- •Expand (backward compatible)
- •Add new nullable columns / tables
- •Add new code paths that write both old+new (dual write) or write new while reading old
- •Add feature flags if needed
- •Backfill (safe + resumable)
- •Populate new columns incrementally
- •Avoid one giant transaction for large tables
- •Switch
- •Update reads to use new column/table
- •Keep dual-write temporarily if needed
- •Contract
- •Remove old columns/constraints only after the switch is proven
Common safe patterns (Django/Postgres)
Adding a new field
Prefer:
- •add field as
null=True(or with safe default behavior in code) - •deploy code that handles nulls
- •backfill
- •then make it
null=Falseand add constraints
Avoid:
- •adding a
NOT NULLfield with a default on a large table in one step (can lock/rewrite)
Adding a unique constraint
Prefer:
- •backfill + deduplicate first
- •add a unique index/constraint after data is clean
- •ensure application validation matches DB constraint
Renaming a field
Prefer expand/contract:
- •add new field
- •dual write / copy data
- •switch reads
- •drop old field
Direct rename may be okay on small tables, but can break older code during rolling deploys.
Adding an index
- •Consider index build time and locking.
- •For large tables, prefer concurrent index creation (requires special handling in Django).
- •If using
CREATE INDEX CONCURRENTLY, the migration must typically setatomic = False.
- •If using
Foreign keys
- •If adding an FK to existing data:
- •add column nullable
- •backfill valid references
- •validate data
- •then enforce
NOT NULL/ constraints
Data migrations & backfills
Principles
- •Backfills must be idempotent and ideally resumable.
- •Avoid loading entire tables into memory.
- •Prefer chunked updates.
Recommended approaches
- •Use a Django
RunPythondata migration for small/medium datasets. - •For large datasets, prefer a management command or background job (documented in deploy notes), then do a later “contract” migration.
Don’t do this
- •Don’t run long-running backfills inside a single transaction if it risks timeouts or lock contention.
- •Don’t assume production data is clean.
Transactionality & locking
- •Django migrations are atomic by default (transactional). That’s safer for small changes.
- •For operations that cannot run in a transaction (e.g., some concurrent index operations), explicitly set
atomic = Falseand document why. - •Always call out potential locks in the design and deployment notes.
Backward compatibility checklist
Before finishing design/implementation, confirm:
- •Old code can run against the new schema (during rolling deploy)
- •New code can run against the old schema (if deployments are staged)
- •API contracts remain valid (or versioned)
Rollback strategy
Every non-trivial schema change must include a rollback plan:
- •Can we roll back app code without rolling back DB?
- •If DB rollback is required, is it safe and tested?
- •If irreversible, document explicitly and add mitigation (feature flag, staged rollout)
Verification expectations
Provide (at minimum):
- •migration generation commands (e.g.,
python manage.py makemigrations) - •migration apply commands (e.g.,
python manage.py migrate) - •smoke checks that touch the modified models/endpoints
Do not claim migrations were applied unless you ran them.