Back openDesk Edu for a sovereign, open-source education — every vote counts.
Vote nowSave products you love by clicking the heart icon.
A practical guide to backup strategies, DR planning, and recovery validation for small-to-medium self-hosted environments — because digital sovereignty includes recoverability.
Every migration talks about going forward: the new schema, the better querus patterns, the load that the old system never handled. Almost nobody writes down the exit plan for the old system — when its last read happens, when it gets torn down, and what the consequences are if the migration turns out to be the only copy of data you actually trust.
I recently watched a production migration save its own company's butt from exactly the wrong side. This is why your deprecated database, sitting there looking like a cleanup liability, is often the best disaster-recovery plan you own — and why deleting it "because the migration is done" is a decision you should make with eyes open.
A lead-scoring product migrated its company/signal graph from Neo4j to Dgraph. The reasons were sound: the team wanted a graph store that fit their query pattern, and Dgraph was the new canonical source. The Neo4j container was left running on the old server — mostly out of inertia, labeled a "stale leftover," a candidate for the next cleanup ticket.
The migration populated Dgraph with ~5,267 companies, and months of nightly adapter ingestion grew it to ~6,500 with 21,000+ signals. Neo4j went unnoticed and grew stale, still holding the original ~5,267 companies.
One morning, deployment infrastructure finished what a load-test OOM had started: Dgraph's data became unreadable (the full story is in Dgraph Data Loss in Production). The binary backup's "restore" produced a cluster that served zero rows. Postgres, the other datastore, had never held the graph — it only had the application's scaffolding tables.
Two sources of truth were gone. One was still alive.
The recovery path was not a backup at all. It was the original migration, run in reverse of what anyone expected:
Reaching Neo4j took more networking than data work — it was bridge-only with no published port, so I chained two SSH tunnels through a workstation:
ssh -N -L 17687:172.21.0.2:7687 weiss@178.254.2.90 & # forward the bridge IP
ssh -N -R 17687:localhost:17687 weiss@195.90.216.159 & # rebind on the dgraph host
Thirty-five minutes and several thousand rows later, the graph was back: 5,240 scored companies, tiers computed, endpoint healthy.
The only reason this was a half-hour recovery instead of "re-ingest from scratch and explain to leadership" was that the "cleanup liability" was still there.
This is not special-case luck. It is a structural property worth leaning on:
Never delete the source you migrated from until you have restored from it. A migration is only "done" when a restore — not a successful load, a restore — has been demonstrated against an empty instance.
Give old systems a cheap keep-alive lease, not a deletion ticket. The difference in cost between "keep the container, no traffic" and "tear it down" is a few GB of RAM. That is the cheapest premium you will pay for a recovery path you already know how to run.
Document the legacy restore as an incident procedure. It took me an hour to reconstruct the exact migration flags and tunnel chain the first time. Now it lives in the runbook as a numbered, tested procedure — the second recovery is a half hour.
When you do decommission, delete cold and keep the volume. Don't make the irreversible choice early; stop the container, back up its data volume off the host, and schedule the actual deletion for N months out.
The software industry's instinct is to treat end-of-life as a tidy, quick win. In practice, decommissioning is an irreversible act, and the data you are freeing up is only expensive-feeling until the day it is the only copy that can put your production system back on its feet. Keep the legacy store around. Update its lease, not its deletion date.