System Design

Database Migration Rollback Explained

Learn database migration rollback strategies, including backward-compatible changes, expand-contract migrations, backups, deploy order, and recovery plans.

Quick answer

Database migration rollback is the plan for recovering when a schema or data migration causes problems.

The safest rollback strategy is often not “run the exact opposite migration.” In production systems, safer plans usually rely on backward-compatible changes, backups, expand-contract migrations, feature flags, and application deploy order.

A good migration plan defines how to stop, retry, roll forward, or restore before the change reaches production.

Why database rollback is hard

Application code can often be rolled back by redeploying an older version. Database state is harder because migrations can change shared data.

Some changes are easy to reverse:

CREATE INDEX idx_users_email ON users (email);

If the index causes write latency, dropping it may be straightforward:

DROP INDEX idx_users_email;

Other changes are risky or irreversible:

ALTER TABLE users DROP COLUMN legacy_status;

If production code or support workflows still need that column, simply rolling back the application will not restore the dropped data.

Rollback vs roll forward

There are two broad recovery approaches:

ApproachMeaningExample
RollbackReturn the database to the previous shape or state.Restore a dropped index, revert a renamed column, restore from backup.
Roll forwardApply another safe change that fixes the issue while moving ahead.Add a compatibility column, deploy a hotfix query, disable a feature flag.

In many production incidents, rolling forward is safer than trying to undo a partially applied migration.

The right choice depends on data loss risk, downtime tolerance, migration duration, and how many application versions depend on the schema.

Backward-compatible migrations

The best rollback plan starts before the migration is written.

A backward-compatible migration allows old and new application versions to work at the same time.

Examples include:

  • Add a nullable column before requiring it.
  • Add a new table without removing the old one.
  • Add an index without changing query behavior.
  • Write to both old and new columns during a transition.
  • Read from the old column until the new one is backfilled.
  • Keep old enum values until all clients stop sending them.

Backward compatibility is especially important when deployments are rolling, blue-green, or canary-based. For a period of time, more than one application version may be running.

The expand-contract pattern

Expand-contract is a common schema migration pattern.

Expand

Add the new schema without breaking old code.

ALTER TABLE users ADD COLUMN display_name TEXT;

Deploy application code that can write both name and display_name, or can read from one with fallback to the other.

Backfill

Copy old data into the new shape in batches.

UPDATE users
SET display_name = name
WHERE display_name IS NULL;

Large backfills should be batched and monitored instead of running as one huge transaction.

Switch

Deploy code that reads from the new schema.

Contract

After the old path is no longer needed, remove old columns or tables in a later release.

This sequence makes rollback safer because the old schema remains available during most of the transition.

Backups are not enough

Backups are essential, but they are not the entire rollback strategy.

Restoring a backup can be slow and disruptive. It may also discard legitimate writes that happened after the migration started.

Before a risky migration, define:

  • What backup or snapshot exists.
  • How long restore takes.
  • Whether point-in-time recovery is available.
  • What data would be lost during restore.
  • Who is allowed to trigger restore.
  • How application traffic is handled during recovery.

For small mistakes, a targeted repair script may be safer than full restore. For destructive data loss, restore may be the only acceptable option.

Data migrations need extra care

Schema migrations change structure. Data migrations change values.

Data migrations are risky because they can make incorrect changes to many rows quickly.

Safer data migration practices include:

  • Run a read-only preview query first.
  • Count affected rows before updating.
  • Update in batches.
  • Store old values in an audit table for important changes.
  • Make the script idempotent where possible.
  • Log progress and failures.
  • Test on production-like data.
  • Keep the application compatible while the migration runs.

For example, before updating a status field:

SELECT status, COUNT(*)
FROM orders
GROUP BY status;

Then run a targeted update with clear filters and row-count expectations.

Transactions and migration scripts

Some migrations should run inside one transaction. Others should not.

A small metadata change may be safe in one transaction. A large backfill can hold locks, grow transaction logs, block reads or writes, and create long rollback times.

Read Database Transactions Explained for the basic transaction model. Migration design also needs database-specific knowledge because DDL transaction behavior differs across databases.

Deploy order matters

Schema and application changes must be coordinated.

A safe order often looks like:

  1. Deploy backward-compatible schema expansion.
  2. Deploy application code that supports old and new shapes.
  3. Backfill data.
  4. Switch reads to the new shape.
  5. Stop writing old shape.
  6. Remove old schema in a later release.

The dangerous version is doing everything in one deploy: rename a column, change code, backfill data, remove old shape, and hope rollback is simple. It rarely is.

Feature flags help, but do not replace rollback

Feature flags can reduce migration risk by controlling when new code paths are used.

For example, the database can accept a new column while the application keeps the feature disabled. If errors appear after enabling the feature, the team can disable it without changing the schema again.

However, a feature flag cannot restore deleted data or undo a destructive migration. Use flags as part of the plan, not as the whole plan.

Common mistakes

The first mistake is assuming every migration tool’s down script is safe for production. A syntactically valid rollback can still lose data.

The second mistake is dropping columns in the same release that stops using them. Keep them for at least one recovery window.

The third mistake is running large updates in one transaction without testing lock behavior and rollback time.

The fourth mistake is ignoring old application versions during rolling deploys.

The fifth mistake is treating backup restore as instant. Recovery time must be tested or at least estimated realistically.

Practical migration checklist

Before production migration:

  • Classify the change as additive, compatible, destructive, or data-changing.
  • Confirm which app versions must work during deployment.
  • Decide whether rollback means rollback or roll forward.
  • Check backup and point-in-time recovery options.
  • Test the migration on production-like data size.
  • Estimate locks, runtime, and rollback time.
  • Add feature flags for risky read or write paths.
  • Prepare validation queries.
  • Prepare a stop condition.
  • Document who approves recovery actions.

Migration safety is less about perfect scripts and more about preserving options when production behaves differently than staging.

Read Database Transactions Explained for commit and rollback fundamentals, and Optimistic Locking Explained for protecting concurrent updates. Read Database Indexes Explained before adding or removing indexes in migrations. Use the SQL Formatter when reviewing migration scripts, and browse the System Design Learning Path or Topics for related backend architecture guides.