Migrating data without downtime
Somewhere in most companies is a migration nobody wants to schedule: a database that needs replacing, a schema that has to change, a system moving to the cloud. The instinct is to book a maintenance window, take the product offline and do it all at once. That plan has one failure mode — if something goes wrong at 2am, you're rolling back under pressure with users waiting. There's a better pattern, and it's mostly about patience.
The core idea: run both
Instead of a cutover, you overlap. The sequence looks like this:
- Dual write. Change the application so every write goes to both the old store and the new one. Reads still come from the old store, so nothing user-facing has changed. If the new write path fails, log it loudly but don't break the request.
- Backfill. Copy historical data across in batches, slowly enough not to disturb production. Because new writes are already flowing to both, the backfill only has to catch up, never keep up forever.
- Verify. Run continuous comparisons — record counts, checksums, spot-checks of real records. Don't move on until the two agree consistently. This step is the one teams skip and the one that saves them.
- Shift reads. Move a small percentage of read traffic to the new store, then more, watching errors and latency. Keep the old store live and correct the whole time.
- Contract. Once reads have been fully on the new store for long enough to trust it, stop dual-writing and decommission the old one.
Schema changes: expand and contract
The same philosophy applies to changing a table. Never rename a column in one deploy. Instead: expand — add the new column alongside the old; deploy code that writes both and reads the old; backfill; deploy code that reads the new; and finally contract by dropping the old column. Four small deploys, each safe on its own, replacing one deploy that could take the product down.
A migration you can't reverse isn't a plan. It's a bet.
Details that decide the outcome
- Keep a rollback path at every phase. If you cannot get back to yesterday's state within minutes, you've moved too fast.
- Batch and throttle the backfill. A migration script that saturates the database is an outage with extra steps.
- Decide how to handle disagreements. When old and new differ, which wins? Write the rule down before you need it.
- Watch business metrics, not just error rates. A migration can succeed technically and quietly halve your signups. Sign-ups, orders and logins are the real health check.
- Rehearse on a copy. Run the whole sequence against production-sized data before touching production.
When an outage is still the right call
Honesty helps here. If the dataset is small, the system is internal, and 3am on a Sunday genuinely has no users, a short planned outage is cheaper and simpler than building dual-write plumbing. The overlapping approach earns its complexity when downtime costs real money or real trust — which, for most customer-facing products, it does.
- Overlap old and new rather than cutting over: dual write → backfill → verify → shift reads → contract.
- Use expand-and-contract for schema changes; never rename in a single deploy.
- Verification is the step that makes the rest trustworthy — don't skip it.
- Keep a fast rollback available in every phase and rehearse on real-sized data.
- A planned outage is still fine for small, internal, low-traffic systems.
Frequently asked questions
Is a zero-downtime data migration really possible?
Yes, for most systems. The technique is to run old and new side by side: write to both, copy historical data in the background, verify the two match, then switch reads over. Users keep working throughout because nothing is ever switched off until the replacement is proven.
How long does a live migration take?
Longer in calendar time than a big-bang outage, and that's the trade. A phased migration might run for days or weeks while data backfills and confidence builds, but every phase is reversible and nobody loses access. You're trading elapsed time for risk.
What is the expand-and-contract pattern?
A way to change a database schema safely. First expand: add the new column or table without removing anything. Then migrate code to use it while both exist. Finally contract: remove the old structure once nothing references it. Each step is independently deployable and reversible.
ZIVARA plans and runs live migrations that don't interrupt your customers. Let's talk. Related: zero-downtime deployments explained and disaster recovery basics.