plugins/backend-toolkit/skills/migration-strategy/SKILL.md
Ship schema changes with zero downtime using the expand-contract pattern — never rename/drop in one step, backfill safely, keep old and new code coexisting during deploy. Use before any schema change on a live database. Not for designing the schema (use schema-design) or wiring migrations into CI (use cicd-pipeline).
npx skillsauth add jaykim88/claude-ai-engineering migration-strategyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Change a live database schema without downtime or data loss by never doing a destructive change in a single step — expand first, migrate, then contract — so old and new application versions coexist safely during rollout.
Universal — the expand-contract (parallel-change) pattern and "additive-first, destructive-later" rule apply to any relational DB and migration tool.
Never rename, drop, or retype in one deploy
EXPAND — additive only (deploy 1)
MIGRATE — backfill (between deploys)
transaction-management)Switch reads (deploy 2)
NOT VALID then VALIDATE CONSTRAINT to avoid long locks)CONTRACT — remove old (deploy 3)
Safe DDL on Postgres
CREATE INDEX CONCURRENTLY (no table lock)ADD COLUMN with no default = fast; with volatile default on large table = rewrite (avoid)ALTER ... SET NOT NULL via CHECK ... NOT VALID → VALIDATE to avoid full-table lockValidate (validation loop)
| ❌ Anti-pattern | ✅ Correct |
|---|---|
| ALTER TABLE RENAME COLUMN in one deploy | Expand (add new) → backfill → contract (drop old) |
| ADD COLUMN NOT NULL DEFAULT <volatile> on big table | Add nullable → backfill → set NOT NULL via NOT VALID/VALIDATE |
| CREATE INDEX (locks table) | CREATE INDEX CONCURRENTLY |
| Backfill in one transaction | Chunked, throttled backfill |
| Drop old column same release as adding new | Drop only after reads migrated + verified |
| Tier | Examples | Action SLA |
|---|---|---|
| Critical | Destructive single-step change on a live table (rename/drop/retype in one deploy); dropping a column still being read; ADD COLUMN NOT NULL DEFAULT <volatile> rewriting a large table under an exclusive lock → outage or data loss | Block release; fix immediately |
| Major | Backfill in one giant transaction (lock/replication lag); CREATE INDEX without CONCURRENTLY on a large table; lock duration never measured on a prod-sized copy | Fix this sprint |
| Minor | Backfill not throttled on a small table; missing per-step rollback note; expand/contract split into more deploys than necessary | Schedule within 2 sprints |
feat(migration): expand <table> for <change> / chore(migration): contract — drop old <column>prisma migrate dev for dev; prisma migrate deploy in CICREATE INDEX CONCURRENTLY (Prisma can't run it inside a transaction — use --create-only then edit)background-jobs task, chunkedop.create_index(postgresql_concurrently=True)golang-migrate — author up/down per stepALGORITHM=INPLACE)schema-design — the migration applies the designed schema changecicd-pipeline — migrations run as an explicit gate in the deploy pipelinetransaction-management — backfills must be chunked, not one giant transactiondevelopment
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).