The plan is mostly about rollback
Migration planning looks like it is about the move. It is mostly about being able to undo the move. Each workload was sequenced so it could be validated independently and reversed independently — because the one that fails will not be the one you expected, and a batch cutover means the failure takes everything with it.
Sequence by blast radius, not by difficulty
The temptation is to start with the easy one. Better to start with something real but low-traffic, because the easy one teaches you nothing about your actual runbook. Order by what you can afford to have wrong for an hour, and let the early moves surface the gaps in your process while the stakes are small.
DNS is the rollback switch
Cutover is a DNS change, which means TTL is the real recovery time. Dropping TTL to 60 seconds days before the move — long enough for the old value to expire from every resolver — turns rollback from an hours-long propagation wait into a minute. Forgetting this is how a fifteen-minute problem becomes an afternoon.
dig +noall +answer +ttlid app.example.com
# days before cutover, drop it:
gcloud dns record-sets update app.example.com. --type=A --ttl=60 --zone=prodVerify the low TTL has actually propagated before the window. Resolvers honour the TTL they cached, not the one you just set.
Validate the data, not the deployment
A workload that starts is not a workload that migrated. Row counts, checksums on critical tables, and a read-only comparison against the source before traffic moves. Zero data loss is a claim you should be able to evidence, not one you make because nothing errored.
psql -h old -c 'select count(*), md5(string_agg(id::text, "," order by id)) from orders'
psql -h new -c 'select count(*), md5(string_agg(id::text, "," order by id)) from orders'Cheap, and it catches partial replication that row counts alone miss. Run it immediately before the cutover, not the night before.
What goes wrong
Hidden dependencies surface at the worst time — a cron job on a forgotten VM writing to the database you just moved, or a hardcoded IP in a config nobody owns. Egress cost surprises people: replicating data out of the old environment is billed, and at volume it can dominate the migration budget. And performance differences are real — different disk classes and network paths mean the same query can be measurably slower on identical-looking hardware, which is why validation has to include latency and not just correctness.
ss -tnp state established | awk '{print $4, $5}' | sort | uniq -c | sort -rn | head -20Run on the source host before you move anything. Every established connection is a dependency, and this finds the ones that are not in the architecture diagram.