Enterprise DevSecOps & Automated CompliancePlaybook3 min readUpdated September 2026

A Runbook for Zero-Downtime Schema Migrations on a Live Database

A schema migration that locks a table for even a few seconds reads as an outage to anyone hitting that table during the lock. The runbook below is the sequence that keeps a migration from ever touching a live table in a way that blocks reads or writes, built around one rule: every step has to be safe to stop after and safe to run twice.

Vendors Covered in this Article

Disclosure: We may earn a commission if you buy through some links on this page. It doesn't change what we recommend.

How do you split a schema migration into safe halves?

Never combine adding a column with removing one in the same deploy. The additive half (new column, new table, new index built concurrently) can run against a live database without breaking the old application code, because the old code simply doesn't know the new column exists yet. The destructive half (dropping a column, renaming a table, removing an index) only runs after every instance of the application is confirmed to have stopped reading or writing the old shape.

This split turns one risky migration into two boring ones, each independently reversible.

Step 2: deploy application code that writes both shapes

Before the migration touches the schema in a breaking way, ship application code that writes to both the old and new column or table, and reads from whichever is present, preferring the new one. This dual-write window is where most of the real risk lives, because it's the period where a bug can silently desync the two representations.

Add a background job that compares old and new values on a sample of rows during this window and alerts on mismatch, so a desync surfaces before the cutover instead of after.

How do you backfill a large table without locking it?

Backfill existing rows in small batches, ordered by primary key, with a short sleep between batches to keep replication lag and lock contention low. A single UPDATE statement across millions of rows holds locks and generates a replication spike that reads as a slowdown across every service sharing that database.

Run the backfill during a lower-traffic window even though it's technically safe at any time, because it gives you a wider margin if something behaves differently than staging predicted.

Step 4: cut over reads, then writes, then drop the old shape

Flip reads to the new column or table first, behind a feature flag, and watch error rates for a full business cycle before touching writes. Only after reads are stable do you stop writing to the old shape. Only after writes have stopped for long enough that you're confident nothing in your fleet is still running old code do you run the destructive migration that removes the old column.

Each of these four sub-steps should be its own deploy, not one big cutover commit, so a problem at any stage rolls back to the previous stable step instead of to the start.

The rollback checkpoint at every step

Before step 1 runs, write down exactly what "undo" means at each subsequent step: for the additive migration, it's dropping the new column, which is always safe since nothing depends on it yet. For the dual-write deploy, it's a flag flip back to old-only. For the backfill, it's simply stopping the job; partial backfills are harmless because reads still prefer the shape they're pointed at. Only the final destructive step has no rollback, which is exactly why it runs last, alone, and only once every other step has been stable for days.

Write down what undo means at each stage before you start:

  • Additive migration: undo means dropping the new column or table, which is always safe because nothing depends on it yet.
  • Dual-write deploy: undo is a flag flip back to writing the old shape only, with reads still served from what is present.
  • Backfill: undo means stopping the job, since a partial backfill is harmless and can resume later from the last completed batch.
  • Read cutover: undo is flipping the feature flag back, which is why reads move first and the destructive change runs last.

Watch replication lag, not just error rates

A migration can look completely healthy on application error dashboards while quietly building up replication lag that only shows up as stale reads on a read replica somewhere downstream. Add a lag check to every step of the runbook, not just the backfill step, since even the dual-write deploy adds write volume that a smaller replica can struggle to keep up with.

If lag climbs past a threshold you'd consider a real problem, pause the current step before it compounds. A backfill that's paused for an hour costs nothing. A backfill that pushes a replica far enough behind that a downstream service starts serving stale data is a much longer cleanup.

Executive Capability Standard

What Good Looks Like

Good migration practice means every schema change against a live table is split into additive and destructive halves, with a dual-write window and a defined rollback at each step, so no single deploy can cause an outage.

Building The Capability (5-Stage Skill Ladder)

1. Learn:Read your database engine's documentation on which DDL operations take exclusive locks and for how long, so you know which changes genuinely need this runbook versus which are already safe.
2. Do Manually:Write the four-step sequence into a shared migration checklist and require sign-off at the cutover step for any table above a size threshold you set.
3. Delegate:Assign a senior engineer as migration reviewer who checks the additive/destructive split before any schema change touching a hot-path table merges.
4. Automate:Adopt a migration tool that enforces concurrent index builds and batched backfills by default, so the safe pattern is the path of least resistance.
5. Buy:Consider a managed database platform with built-in online schema change tooling once manual batching becomes a recurring bottleneck for your migration velocity.

How to Get Started

Disclosure: We may earn a commission if you buy through some links on this page. It doesn't change what we recommend.

CrowdStrike

The backfill window, when both old and new columns are live, is also when a compromised host could quietly corrupt data in either shape; CrowdStrike's workload monitoring is worth having watching that window specifically.

Visit CrowdStrike→

Frequently Asked Questions

How long should we wait between the dual-write deploy and the cutover?

Long enough to see a full cycle of your traffic pattern, usually a week for most B2B products, so weekday and weekend behavior both get exercised. Cutting over after a single quiet day hides bugs that only show up under real load.

What if the backfill job needs to run for days?

That's normal for a large table and isn't itself a problem, as long as each batch is small enough that lock time per batch stays under a few hundred milliseconds. Monitor replication lag during the job and slow the batch rate if it climbs.

Do we need this level of process for every migration?

No. A brand-new table or a nullable column addition on a small table is safe to ship in one step. Reserve the full runbook for changes to tables that are actively read or written on your hot path.

About the numbers

This guide doesn't quote a sourced benchmark. Figures in it are estimates or general guidance, so check them against your own numbers.

Related Guides