When a financial-services client asked us to migrate their core ledger — 14TB of transactional data spread across three AWS regions — the brief was uncompromising: not one second of user-facing downtime. Their platform clears payments around the clock, so the usual maintenance-window approach was off the table from day one.
The expand/contract pattern
Rather than a single cut-over, we treated the migration as a sequence of backward-compatible steps. The schema and the application evolve independently, and at no point does a running instance depend on a change that has not already shipped.
- Expand. Add the new columns, tables, and dual-write paths. The old code keeps working; the new structures are populated in the background.
- Migrate. Backfill historical rows in throttled batches while both schemas stay in sync.
- Contract. Once every reader is on the new path and verified, remove the old columns and the dual-write shim.
Each phase is independently reversible, which meant we could pause the rollout at any batch boundary without stranding a request mid-flight.
Keeping three regions honest
Cross-region replication is where most zero-downtime plans quietly fail. We ran logical replication between the source and target clusters and gated the final promotion on a continuous checksum job that compared row counts and per-partition hashes on both sides.
The migration only felt safe because every step had a defined rollback. We never crossed a bridge we couldn't walk back across.
What we would do again
Two decisions paid for themselves repeatedly: writing the backfill as an idempotent, resumable job, and putting the checksum reconciliation in front of a human before the cut-over rather than after. The whole migration completed over eleven days, and the on-call channel stayed quiet the entire time — which, for a 14TB move across three regions, is the outcome we were proudest of.