Twenty terabytes doesn’t sound dramatic until it’s your production database and the migration plan involves any downtime at all, at which point every stakeholder in the company suddenly has strong opinions about the maintenance window. Zero-downtime migration at this scale is achievable, reliably, but it requires treating the migration itself as a real engineering project with its own architecture - not a script you run over a weekend and hope for the best.
Why “just take a maintenance window” stops being an option at this scale
At smaller data volumes, a straightforward dump-and-restore migration during a scheduled maintenance window is genuinely the simplest, lowest-risk approach - don’t overcomplicate a migration that doesn’t need it. At 20TB, the math changes: even a fast dump-and-restore process can take many hours, which means either an unacceptably long outage for most production systems, or a genuine risk of the migration overrunning its window and needing a rollback decision made under pressure. Beyond a certain data size, zero-downtime migration stops being a nice-to-have and becomes the only realistic option.
The pattern that actually works: continuous replication, then a fast cutover
- Set up continuous replication from the source database to Aurora PostgreSQL using a change-data-capture tool (AWS Database Migration Service, or a similar CDC-based approach) that streams ongoing writes from the source to the target in near-real-time, while the bulk historical data loads in the background. Your application keeps writing to the source database throughout this entire phase - nothing about production changes yet.
- Validate replication lag and data integrity continuously during this phase, not just once at the end. Row counts, checksums, and spot-checking specific tables catch replication issues while there’s still time to fix them, rather than discovering a data integrity problem after cutover when it’s much harder to unwind.
- Once replication lag is consistently near-zero - meaning the target is genuinely caught up and staying caught up with the source in real time - plan the actual cutover, which should now be a matter of minutes, not hours: briefly pause writes, let the last few transactions replicate through, verify the target is fully caught up, then repoint the application to the new database.
- Keep the old database running and replicating in reverse (or at minimum, fully intact and unmodified) for a defined rollback window after cutover - hours to a few days, depending on risk tolerance - so a rollback is a real, tested option if something unexpected surfaces post-migration, not a theoretical one you hope you never need.
Where these migrations actually go wrong
Almost never during the bulk data transfer itself - that part is mechanical and well-tooled. The real risk is usually schema or data-type incompatibilities that only surface under real production load after cutover (a column type that behaves subtly differently between the source engine and Aurora, an index that wasn’t recreated identically, a stored procedure or trigger that didn’t translate cleanly). Testing the cutover against a full-scale, production-like replica - not just a sampled subset of data - before doing it for real is the single highest-leverage risk reduction step, and the one most commonly skipped under time pressure.
What we actually tell clients before committing to a date
Budget real time for a full dress rehearsal against production-scale data before the actual cutover, and don’t schedule the real migration until that rehearsal has gone cleanly at least once. The temptation to skip this step and go straight to production, especially when a migration has already been delayed, is exactly how avoidable incidents happen at this data scale.
We plan and execute migrations like this as part of our cloud infrastructure work. If you’re facing a large-scale database migration and want a second opinion on the cutover plan before committing to a date, get in touch.