Plan and review safe database migrations: expand/contract, backward-compatible deploys, locking/index risks, backfills, and zero-downtime notes. Use when DB migration, 数据库迁移, schema change, online DDL, expand/contract, dual-write, backfill, or shipping migrations without downtime. Complements SQL style; not a substitute for SQL injection testing.
Instrucciones de origen · Vista previa de solo lectura
name
database-migration-safety
description
Plan and review safe database migrations: expand/contract, backward-compatible deploys, locking/index risks, backfills, and zero-downtime notes. Use when DB migration, 数据库迁移, schema change, online DDL, expand/contract, dual-write, backfill, or shipping migrations without downtime. Complements SQL style; not a substitute for SQL injection testing.
Database Migration Safety
Ship schema and data changes that respect deploy order, old and new
application versions, lock behavior, and rollback reality. Prefer expand/contract
over rewrite-in-place. Repository migration tooling and production runbooks
outrank generic preferences.
Use When
Writing or reviewing Flyway, Liquibase, Alembic, Django/Rails, Prisma, Knex,
golang-migrate, Sqitch, Atlas, or raw ordered SQL migrations
Changing columns, indexes, constraints, enums, FKs, or large data backfills
Targeting zero or low downtime: online DDL, concurrent indexes, multiphase
expand → migrate → contract
Dual-running app versions during rolling deploys or blue/green
User mentions: DB migration, 数据库迁移, expand/contract, zero-downtime
migration, CREATE INDEX CONCURRENTLY, backfill, drop column safely
Repo tooling, dialect, and ops policy outrank this skill’s defaults.
Migration runner: Flyway/Liquibase/Alembic/Django/Prisma/etc.; whether
migrations wrap each file in a transaction; support for CONCURRENTLY /
offline DDL; down migrations allowed or forward-only
Dialect and version: PostgreSQL, MySQL/MariaDB, SQL Server, SQLite —
online DDL and lock semantics differ; use only features the project runs
Deploy model: rolling pods, blue/green, maintenance window, single
instance — dual-version compatibility length is defined by this model
Naming and layout: existing migrations/ patterns, expand/contract
PR habit, separate backfill jobs vs in-migration SQL
Safety linters: Squawk, pg-osc, pt-online-schema-change, gh-ost,
strong_migrations, custom CI checks — honor their rules
Data classes: multi-tenant keys, RLS, encryption-at-rest, PII columns —
follow existing handling; never log row contents with secrets/PII
Neighboring migrations: copy recent safe multiphase examples in-repo
before inventing a new pattern
Precedence: If repo rules conflict with defaults below, follow the repo.
Surface conflicts that risk data loss, prolonged locks, or break old app
binaries still serving traffic.
Workflow
State the change and blast radius.
Additive vs destructive; table size; hot paths; replicas; SLA
Whether downtime is approved or online migration is required
Map application compatibility.
Which app versions will run against DB during the change
Rule: DB expands first, contracts last; app may dual-read/dual-write
in the middle
Pick a migration shape.
Shape
When
Notes
Single additive migration
Small table, nullable column, new table/index with online path
Still avoid long locks
Expand / migrate / contract
Rename, type change, NOT NULL without safe default, drop column
Multiple deploys
Maintenance window
Approved downtime; simpler DDL
Document freeze and rollback
Online schema tool
Very large MySQL/Postgres tables
gh-ost, pt-osc, pg-osc per ops
Design expand phase (backward compatible).
Add nullable columns, new tables, new indexes (online when needed)
Do not drop/rename/change types yet
Defaults: prefer application-backfill or explicit DB default that old
writers satisfy
Design data move.
Backfill in batches; idempotent; rate-limited; observable progress
Prefer job/worker over multi-hour transaction on large tables
Verify row counts / checksums / spot queries before contract
Design contract phase (after app no longer depends on old shape).
Remove old columns/triggers/dual-write only when metrics show idle
Enforce NOT NULL / FKs / CHECKs only after backfill complete
Assess locks and performance.
Long ACCESS EXCLUSIVE (Postgres) or table rebuilds (MySQL variants)
Index build strategy; autovacuum; replica lag; statement timeouts
Plan rollback and order.
Expand should be roll-forward friendly; avoid irreversible DROP early
Expand / Contract Pattern
Typical rename (zero-downtime sketch)
Expand:ADD COLUMN new_name (nullable or with default); keep old_name
App deploy A: dual-write both; read prefer new_name with fallback to old_name
Backfill: copy old_name → new_name where null; batched
App deploy B: read/write only new_name
Contract: drop old_name (and dual-write code) after soak
Typical NOT NULL add
Add nullable column (or with temporary default)
Deploy writers that always set the column
Backfill existing rows
Validate no nulls; SET NOT NULL / drop temporary default in a controlled step
Avoid ADD COLUMN … NOT NULL without default on large busy tables
Typical index on large Postgres table
Prefer CREATE INDEX CONCURRENTLY when the runner allows non-transactional
statements (often cannot sit inside a transaction block)
If concurrent not available, schedule off-peak or use approved online tool
UNIQUE indexes: handle duplicates before enforcing uniqueness
Typical column drop
Deploy app that stops reading/writing the column
Soak; confirm no queries reference it (logs, pg_stat_statements, code search)
Drop column in a later migration
Do not drop in the same release that still might roll back to old code
Zero-Downtime Notes
Rolling deploy: every migration must leave DB compatible with both
old and new app binaries for the full roll
Blue/green: cut traffic only after expand+backfill verified; contract after
Locks: treat multi-second exclusive locks on hot tables as outages unless
approved; test on production-sized data
FKs: adding FKs can scan/lock; create NOT VALID then VALIDATE when
dialect supports phased validation (Postgres)
Enums: prefer additive new values; removing enum values is hard — plan
multiphase or check constraints on text instead when churn is high
Views/materialized views/triggers: update in order that keeps old app working
Replicas: long transactions and heavy backfills cause lag — throttle
SQLite / small apps: simpler migrations OK; still avoid data-loss DROP
without backup and explicit intent
Good / Bad Examples
Expand before break
Good — additive first:
-- V202607111200__orders_status_expand.sqlALTER TABLE orders
ADDCOLUMN status text;
-- App dual-writes status; backfill job fills nulls next.-- Later migration: SET NOT NULL + CHECK after verification.
Bad — rewrite in place on live system:
-- Drops data path old app still uses during rollALTER TABLE orders DROPCOLUMN state;
ALTER TABLE orders ADDCOLUMN status text NOT NULL;
Concurrent index (Postgres)
Good
-- Run outside a transaction if required by the migration toolCREATE INDEX CONCURRENTLY IF NOTEXISTS idx_orders_customer_id
ON orders (customer_id);
Bad
-- Blocks writes for a long time on large tablesCREATE INDEX idx_orders_customer_id ON orders (customer_id);
Backfill batching
Good
-- Idempotent batch; repeat until 0 rows updatedUPDATE orders
SET status ='open'WHERE id IN (
SELECT id FROM orders
WHERE status ISNULLORDERBY id
LIMIT 1000
);
Bad
-- One huge transaction; long locks; hard to resumeUPDATE orders SET status ='open'WHERE status ISNULL;
Drop column safety
Good (sequence across releases)
R1: App ignores column deprecated_at (stop writes)
R2: Confirm no reads; migration DROP COLUMN deprecated_at
Bad
-- Same deploy as app that might roll back to code selecting the columnALTER TABLE users DROPCOLUMN legacy_flag;
Constraint after clean data
Good
-- After backfill verifiedALTER TABLE orders
ALTERCOLUMN status SETNOT NULL;
ALTER TABLE orders
ADD CONSTRAINT ck_orders_status
CHECK (status IN ('draft', 'open', 'paid', 'canceled'));
Bad
-- Fails mid-deploy or locks while checking dirty dataALTER TABLE orders
ADDCOLUMN status text NOT NULL;
Anti-Patterns
Editing applied migrations instead of adding corrective migrations
Combining irreversible DROP with expand in one shot on shared environments
Multi-hour exclusive locks on peak traffic without approval
Non-idempotent backfills that corrupt on retry
Enforcing UNIQUE/NOT NULL before cleaning duplicates/nulls
Assuming MySQL and Postgres lock behavior are the same
Silent data type changes that truncate values (text → varchar(10))
Migrating production without a backup/PITR story appropriate to the org
Putting heavy backfills only in a blocking migration transaction “to keep it simple”
Forgetting ORM models, generated types, and API schemas must track the multiphase reality
Tests for old+new schema compatibility where feasible
Parameterized SQL; transactions scoped correctly
api-documentation-writing / json-schema-design: when HTTP/event
contracts change with columns, update schemas and docs in the matching phase
(additive API fields with expand; removals with contract)
This skill specializes operationally safe schema evolution. It does not
replace SQL style preferences, JSON contract design, or full app quality gates.
Checklist
Migration tool, dialect, transaction/CONCURRENTLY constraints, and deploy model identified
Change classified: additive, multiphase expand/contract, or approved downtime
Old and new app versions remain compatible for the full roll / dual-run window
Expand ships before rename/type/NOT NULL/drop contract steps
Backfills batched, idempotent, observable; verified before enforce/drop
Lock and index strategy safe for table size and peak traffic
FKs/CHECKs/UNIQUE enforced only on clean data; phased validate when available
No in-place edits to already-applied migrations
Rollback story: app can roll back onto expanded schema; irreversible steps deferred
ORM/models, json-schema-design / OpenAPI, and jobs updated per phase
Staging or clone dry-run done; safety linter (Squawk/strong_migrations/etc.) clean when configured
sql-style-conventions followed for new SQL artifacts
code-quality-standards applied for dual-write/backfill code, errors, and tests
Prod runbook notes: order of deploy, monitoring (locks, lag, error rate), abort criteria
App rollback must still work against expanded schema
Never edit already-applied migrations in shared environments; add new ones
Ship with verification.
Migrate on disposable DB or staging clone; run app dual-version smoke
CI migration lint when available; document runbook for prod