agentleFS
reddygopalkrishnareddygopalkrishna/developer-agent-skills/skills/database-migrations/SKILL.md

database-migrations

Skill8 starsChanged 9 days agoMITView on GitHub

Plans and writes zero-downtime database schema migrations (expand/contract, safe indexes, batched backfills, rollback). Use when adding, renaming or dropping columns or tables, changing types or constraints, adding indexes, or backfilling data on a live database.

52 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use deployment-patterns, postgres-patterns and 8 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • royaluniondesign-sysSkill

    deployment-patterns

    Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications.

    Also in 6 projects that use this

  • nek1987Skill

    postgres-patterns

    PostgreSQL patterns for reviewing migrations and writing efficient queries. Use when reviewing Alembic migrations, optimizing queries, or debugging database issues.

    Also in 7 projects that use this

  • nek1987Skill

    redis-patterns

    Upstash Redis patterns for caching and rate limiting.

    Also in 3 projects that use this

  • HABUBUSSSkill

    backend-patterns

    Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.

    Also in 9 projects that use this

  • seikaikyoSkill

    fastapi-patterns

    FastAPI + SQLModel + Neon PostgreSQL 後端開發最佳實踐。適用於 Render 部署的 API 服務。涵蓋路由設計、資料庫操作、錯誤處理、認證授權。

    Also in 3 projects that use this

  • SentrySkill

    react-testing

    Write and review React/TypeScript tests for Sentry's frontend using Jest and React Testing Library. Use when adding or editing tests in static/ (*.spec.tsx), writing component/hook tests, mocking API responses with MockApiClient, testing routing or network requests, or when asked to "write a frontend test", "add a React test", "test this component", or "fix a flaky RTL test".

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

12 rules. 0 are common, 11 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 48 edited versions we read keep these.

  • 11

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Not for: designing a brand-new schema with no live data (use the database-schema-design skill), or tuning slow queries (use …

    line 18
  2. Very specific:

    Identify the engine, version and tool: Run SELECT version(); (PostgreSQL) or SELECT VERSION().

    line 22
  3. Distinctive:

    Write the rollback or forward-fix plan: before running anything. Mark the steps that are irreversible.

    line 29
  4. Distinctive:

    Read references/postgres.md when the database is PostgreSQL (lock levels, exact recipes, non-transactional migrations per tool, finding …

    line 79
  5. Distinctive:

    Read references/mysql.md when the database is MySQL or MariaDB (online DDL, metadata locks, gh-ost and pt-online-schema-change).

    line 80
  6. Distinctive:

    Run backfills as a separate script or job that commits per batch, is safe to re-run, and can be stopped at any time.

    line 84
  7. Distinctive:

    Start with 1,000-10,000 rows per batch and measure.

    line 97
  8. Distinctive:

    Run all migrations from an empty database in CI, and against a recent anonymised snapshot when possible.

    line 122
  9. Distinctive:

    Run the previous app version's test suite against the new schema to prove backward compatibility.

    line 123
  10. Distinctive:

    Check that the migration is idempotent or that the tool records it as applied exactly once.

    line 125
  11. Distinctive:

    Every step is backward compatible with the code currently deployed, and the deploy order is written down.

    line 140
  12. Distinctive:

    Every DDL statement is guarded by a lock timeout or an explicit online-DDL clause.

    line 141

“Common” is measured: kept by at least 6 in 10 of the 48 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Zero-Downtime Database Migrations

During every deploy, the old and new versions of the application run against the same schema at the same time. Every migration must work with both the code running now and the code being deployed, and must never hold a lock that blocks traffic for longer than you would accept as an outage.

When to use

  • Adding, renaming, dropping or retyping columns and tables on a database that serves traffic.
  • Adding indexes, foreign keys, unique, NOT NULL or CHECK constraints to existing tables.
  • Backfilling or transforming existing rows.
  • Reviewing a migration file (hand-written or ORM-generated) before it ships.

Not for: designing a brand-new schema with no live data (use the database-schema-design skill), or tuning slow queries (use sql-optimization).

Workflow

  1. Identify the engine, version and tool. Run SELECT version(); (PostgreSQL) or SELECT VERSION(); (MySQL). Lock behaviour changes between versions. Find the migration tool (Flyway, Liquibase, Alembic, Django, Rails, Prisma, EF Core, golang-migrate, ...) and whether it wraps each migration in a transaction.
  2. Read the actual SQL. For ORM-generated migrations, print the SQL before judging it (see "Inspect generated SQL" below). Never review only the ORM diff.
  3. Classify every statement with the risk table below. Anything not "safe" needs a pattern.
  4. Split into expand → migrate → contract phases, each shipped as its own deploy. One migration should do one risky thing.
  5. Guard every DDL statement with a lock timeout (PostgreSQL SET lock_timeout, MySQL SET SESSION lock_wait_timeout) so it fails fast instead of queueing behind a long query and blocking everything after it.
  6. Move data changes out of schema migrations. Backfills run as batched, resumable jobs, not inside the DDL transaction.
  7. Rehearse on a production-sized copy. Record duration, locks taken and replication lag. A migration that takes 50 ms on a dev database can take an hour on 500 million rows.
  8. Write the rollback or forward-fix plan before running anything. Mark the steps that are irreversible.
  9. Stop and get explicit confirmation from the user before running any migration against a shared, staging or production database. Show them the exact SQL, expected duration and rollback plan.

Expand / contract

The universal pattern for any change that would break running code:

PhaseSchema changeApp changeReversible?
1. ExpandAdd the new column/table/index, nullable or with a defaultNone yetYes
2. Dual write—Write to old and new; read oldYes
3. BackfillBatched job copies old → new—Yes
4. Switch readsOptionally add constraints (NOT NULL, FK) on newRead new, still write bothYes
5. Stop old writes—Write only new; remove all references to oldYes
6. ContractDrop old column/table—No — take a backup first

Each phase is a separate deploy. Phase 6 waits until every running instance (including workers, cron jobs, and other services sharing the database) has stopped referencing the old structure.

Ordering migrations against deploys

ChangeRun migrationThen
Add table, nullable column, indexBefore the app deployApp starts using it
Add column the new code requiresBefore the app deploy, nullable or with a defaultAdd NOT NULL later, after backfill
Drop column or tableAfter the code stops referencing it—
Rename column or tableNever in one stepExpand/contract: add new, dual write, backfill, switch, drop old
Change column typeNever in place on a large tableNew column + backfill + switch, unless the change is metadata-only for your engine
Add NOT NULL / FK / CHECKAfter every writer sets valid values and the backfill is doneValidate without blocking (see engine references)

ORMs that list columns explicitly in SELECT and INSERT (most do) fail as soon as a mapped column disappears. Remove the mapping and deploy first:

  • Rails: self.ignored_columns += ["legacy_status"], deploy, then drop.
  • Django: remove the field from the model using migrations.SeparateDatabaseAndState so state changes without dropping the column; drop it in a later migration.
  • EF Core, Hibernate/JPA, Prisma, SQLAlchemy: remove the property/field and deploy; drop the column in a later migration.

Risk classification

OperationRiskSafe pattern
Add nullable column, no defaultSafe—
Add column with a constant defaultPostgreSQL 11+: metadata only. MySQL 8: usually ALGORITHM=INSTANTVolatile defaults (random(), gen_random_uuid(), clock_timestamp()) rewrite the table in PostgreSQL — add nullable, backfill, then set the default
Create indexBlocks writes for the whole buildPostgreSQL CREATE INDEX CONCURRENTLY; MySQL ALGORITHM=INPLACE, LOCK=NONE
Add unique constraintBlocks writes while it builds the index; fails on duplicatesFind duplicates first; build the unique index concurrently, then attach it
Add foreign keyLocks both tables while it scansPostgreSQL NOT VALID then VALIDATE CONSTRAINT; index the referencing column
Add NOT NULL to existing columnFull scan under an exclusive lockPostgreSQL: CHECK NOT VALID → VALIDATE → SET NOT NULL (12+)
Change column typeUsually rewrites the table under an exclusive lockNew column + backfill + switch
Rename column/tableFast, but breaks every running instanceExpand/contract
Drop column/tableBreaks running code; irreversibleRemove references first; back up; confirm with the user
UPDATE/DELETE many rowsLong locks, bloat, replication lagBatched job

Read references/postgres.md when the database is PostgreSQL (lock levels, exact recipes, non-transactional migrations per tool, finding blockers). Read references/mysql.md when the database is MySQL or MariaDB (online DDL, metadata locks, gh-ost and pt-online-schema-change).

Batched backfills

Run backfills as a separate script or job that commits per batch, is safe to re-run, and can be stopped at any time.

-- One batch. The job loops :start from min(id) to max(id) in steps of :batch_size,
-- commits after each batch, and sleeps briefly between batches.
UPDATE orders
SET    status_v2 = status
WHERE  id >= :start
  AND  id <  :start + :batch_size
  AND  status_v2 IS NULL;
  • Walk the primary key in ranges. WHERE new_col IS NULL LIMIT n without an index rescans the table on every batch.
  • Start with 1,000-10,000 rows per batch and measure; aim for batches that finish in well under a second.
  • Watch replication lag and database CPU between batches; pause when lag grows.
  • Make it idempotent (the IS NULL guard) and log progress so it can resume after a crash.
  • Rows written during the backfill must already be handled by dual-write code (phase 2), otherwise you race.

Inspect generated SQL

ToolHow to see the SQL before running it
Djangopython manage.py sqlmigrate <app> <migration>
Alembicalembic upgrade <rev> --sql (offline mode)
EF Coredotnet ef migrations script <from> <to>
Liquibaseliquibase update-sql
Prismaprisma migrate dev --create-only, then read the generated migration.sql
Flyway, golang-migrate, Rails SQL filesThe migration file is the SQL; read it

Rollback vs forward fix

  • Down migrations are rarely tested and often destroy data (dropping a column that has new writes in it). Prefer forward fixes: a new migration that corrects the problem.
  • Expand-phase steps are cheap to roll back: leave the unused column in place, roll back the app.
  • Contract-phase steps (drops, destructive type changes) are irreversible. Before them: confirm a recent backup or snapshot exists and that restore has been tested, and get explicit user approval.
  • Because each phase is backward compatible, rolling back the application one version is always safe. Keep it that way.

Testing

  • Run all migrations from an empty database in CI, and against a recent anonymised snapshot when possible.
  • Run the previous app version's test suite against the new schema to prove backward compatibility.
  • Rehearse long operations on production-sized data and record how long they took.
  • Check that the migration is idempotent or that the tool records it as applied exactly once.

Anti-patterns

  • One migration that adds a column, backfills it and sets NOT NULL in a single transaction — holds locks for the whole backfill. Split it.
  • No lock timeout — the DDL waits behind one long-running query, and every query after it queues behind the DDL. That is an outage caused by a "fast" statement.
  • Renaming in place — instantly breaks every running instance still using the old name.
  • Editing a migration that already ran somewhere — environments diverge and checksum-validating tools (Flyway, Liquibase) refuse to run. Add a new migration instead.
  • Trusting ORM-generated migrations unread — they happily emit table rewrites and blocking index builds.
  • Mixing schema changes and large data changes in one migration.
  • Dropping a column the ORM still selects — the deploy order was wrong.
  • Testing only on a small dev database — lock duration and rewrite cost scale with table size.

Done when

  • Every step is backward compatible with the code currently deployed, and the deploy order is written down.
  • Every DDL statement is guarded by a lock timeout or an explicit online-DDL clause.
  • Indexes and constraints on large tables are built without blocking writes.
  • Backfills are batched, idempotent, resumable and run outside the DDL transaction.
  • The plan was rehearsed on production-sized data, with durations recorded.
  • Rollback or forward-fix steps are written; irreversible steps are flagged and have a verified backup.
  • The user explicitly approved running it against any shared or production database.

References

  • references/postgres.md — read for PostgreSQL lock levels, step-by-step recipes, non-transactional migrations per tool, and queries to find blocking sessions.
  • references/mysql.md — read for MySQL/MariaDB online DDL, metadata locks, and online schema change tools.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.