Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migration

Pattern

A reusable solution you can apply to your work.

Understand This First

Context

This is an operational pattern that addresses one of the most delicate tasks in software evolution: changing the shape of data, schemas, or system behavior while preserving what already exists. Migrations arise whenever a database schema changes, an API version evolves, a configuration format updates, or data must move from one system to another.

In agentic coding, agents can generate migration code quickly, but a badly generated migration can destroy production data in seconds. This is one area where human review is non-negotiable.

Problem

Your application needs to change how it stores or structures data. But the existing data, potentially millions of records serving real users, must survive the transition intact. You can’t just delete the old schema and create a new one. How do you evolve a system’s data structures without losing data or breaking running services?

Forces

  • The new code expects the new schema, but the old data is in the old schema.
  • Migrations must be reversible in case something goes wrong, but not all changes have clean reversal paths (dropping a column destroys data).
  • Large datasets make migrations slow, and slow migrations cause downtime.
  • Multiple developers working simultaneously may create conflicting migrations.

Solution

Express schema and data changes as versioned, ordered migration scripts that can be applied (and ideally reversed) in sequence. Each migration has an “up” direction (apply the change) and a “down” direction (reverse it). The system tracks which migrations have been applied, so it knows where it stands and what comes next.

Use a migration framework appropriate to your stack (Rails migrations, Flyway, Alembic, Knex, Prisma Migrate, or similar). These tools manage ordering, track applied migrations, and provide a consistent interface for writing and running changes.

Write migrations that are safe and incremental. Prefer additive changes (adding a column, adding a table) over destructive ones (dropping a column, renaming a field). When a destructive change is necessary, use a multi-step approach: first deploy code that works with both old and new schemas, then migrate the data, then remove the old schema support.

Always create a Git Checkpoint before running migrations, especially in production. Test migrations against a copy of production data before applying them to the real thing. And have a rollback plan: know what “down” looks like before you run “up.”

How It Plays Out

A team adds a “display name” field to their user table. The migration adds the column with a default value, then a data migration populates it from existing first/last name fields. The code is deployed in two steps: first the version that reads display name if present and falls back to first/last name, then (after the migration runs) the version that requires display name. Zero downtime, no data loss.

A developer asks an agent to generate a migration that splits a single address text field into street, city, state, and zip columns. The agent produces a migration that creates the new columns and drops the old one. The developer catches the problem: the “down” migration cannot reconstruct the original address from the parts. The fix: keep the old column during the transition period and only drop it after verifying the new columns are fully populated.

Warning

Never run an untested migration against production data. Always test against a recent copy of production first. Data destruction is the one category of mistake that version control cannot undo.

Example Prompt

“Write a database migration that adds street, city, state, and zip columns to the addresses table. Keep the original address column during the transition. Include a data migration that splits existing addresses into the new fields.”

Consequences

Migrations give you a controlled, repeatable process for evolving data structures. Every team member’s database matches the current schema. Schema history is preserved in version control alongside code. Environments can be brought to any schema version by running the appropriate sequence of migrations.

The cost is complexity. Migration scripts accumulate over time and must be maintained. Reversibility isn’t always achievable. Long-running migrations on large tables can cause downtime or performance degradation. And migration ordering conflicts between team members require careful coordination.

  • Depends on: Version Control — migration scripts are tracked in version control.
  • Uses: Git Checkpoint — checkpoints before and after migrations provide safety.
  • Enables: Deployment — migrations are often a step in the deployment process.
  • Enables: Rollback — reversible migrations make schema rollbacks possible.