Skip to content
Back to skills

Db Migration

ASecurity

Expand/contract pattern for zero-downtime database schema changes. Replaces the (wrong) \"forward-only by default\" rule. The four-phase pattern: expand → backfill → cutover → contract.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 9, 2026
ai-agentsgobashsqlgitdatabasebackend

Security analysis

A100/100

Scanned October 9, 2026

npx -y skills add qyndex/claude-init --skill db-migration --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Db Migration?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Db Migration
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/qyndex-db-migration/badge)](https://www.skillsdirectory.com/skills/qyndex-db-migration)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: db-migration
description: "Expand/contract pattern for zero-downtime database schema changes. Replaces the (wrong) \"forward-only by default\" rule. The four-phase pattern: expand → backfill → cutover → contract."
when_to_use: A schema change is needed on a live database. Any DDL touching tables in production. User says "add a column", "rename a column", "change column type", "drop a column", "migration".
allowed-tools: Read, Glob, Grep, Edit, Write, Bash
model: opus
---

# DB Migration — Expand / Contract

Zero-downtime schema changes require the four-phase pattern. A single "forward-only migration" deploy is wrong for any non-trivial change at scale.

## The four phases

```
Phase A — EXPAND
  - Add new schema element (column, table, index) — additive only
  - Old code continues to work (it doesn't see the new element)
  - Deploy and verify no errors

Phase B — DUAL-WRITE / BACKFILL
  - New code writes to BOTH old and new (dual-write)
  - Backfill historical data into the new element (batched, off-peak)
  - Old code still reads the old element

Phase C — CUTOVER (read switch)
  - New code reads from the new element
  - Old code still writes the old element
  - Monitor for divergence; verify the read path is correct

Phase D — CONTRACT
  - Remove dual-write; new code writes only the new element
  - Drop the old element (column / table) — separate deploy
  - Update the schema docs
```

## Per-change patterns

### Add a column
- A: `ALTER TABLE x ADD COLUMN new_col TYPE NULL` (NULL allowed; no default to avoid table rewrite on big tables)
- B: write to both old and new (if replacing); or just start using new
- C: read from new
- D: drop old (if applicable)

### Rename a column
- A: `ALTER TABLE x ADD COLUMN new_name TYPE` (add new)
- B: dual-write old + new; backfill (UPDATE x SET new_name = old_name in batches)
- C: read switches to new
- D: drop old, rename if desired (`ALTER COLUMN ... RENAME` if portable)

### Change a column type
- A: add new typed column (`alter table x add column new_col NEW_TYPE`)
- B: dual-write with conversion; backfill
- C: read from new
- D: drop old

### Split a table
- A: create new tables
- B: dual-write to new tables; backfill
- C: read switches to new tables
- D: drop old table

### Drop a column
- A: stop reading from it (code change first)
- B: stop writing to it (next deploy)
- C: verify no references
- D: drop the column

## Per-DB specifics

### Postgres
- **`ALTER TABLE ADD COLUMN` is fast IF no default + NULL allowed.** Avoid `NOT NULL DEFAULT 'x'` on big tables — it rewrites.
- **Concurrent indexes**: `CREATE INDEX CONCURRENTLY` — doesn't block writes.
- **Long-running migrations**: use `lock_timeout` and `statement_timeout` to avoid blocking pile-ups.
- **Backfill**: `UPDATE ... WHERE id BETWEEN N AND N+1000` in batches; commit per batch.

### MySQL
- **`pt-online-schema-change`** for big tables (Percona toolkit).
- **`gh-ost`** for low-impact schema changes (GitHub's tool).
- Native `ALGORITHM=INPLACE, LOCK=NONE` available for some changes in MySQL 8.

### Aurora / DynamoDB / Mongo / etc.
- Apply the same expand/contract logic; the primitives differ.

## CI gates

- Migration files require a corresponding **reverse migration** OR explicit "no-rollback; data-loss" tag
- Migrations touching large tables (>100k rows) require **`CONCURRENTLY` or pt-osc/gh-ost**
- Phase-A and Phase-D are **separate PRs** (don't combine; each deploys independently)
- Spec's Rollout section lists the four phases with target ship dates

## Hard rules

- **Never combine phases in one deploy.** Each phase is a separate code change + deploy + verify cycle.
- **Always have a reverse migration** for Phase A and Phase D (Phase B/C are code-only).
- **Backfill in batches.** Big single-transaction backfills lock tables.
- **Monitor during cutover.** Watch the read/write divergence at Phase C.
- **Document.** Each migration touches an ADR explaining why.

## References

- Square Engineering — "Zero downtime migrations" (canonical)
- Stripe — "Online schema migrations" blog series
- gh-ost README — https://github.com/github/gh-ost
- See also: `/upgrade` command, `.claude/rules/backend.md`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.