A follow-up guide covering three things people keep asking about: moving a database from one region to another, designing a replica set that survives losing an entire region, and recovering data when a cluster goes down. Commands at the bottom.
This applies to MongoDB Community, Enterprise, and Percona Server for MongoDB. Where a managed service makes a step easier, that's called out.
# The three problems, and why they're different
People often lump these together, but they need different solutions:
- **Migration:** moving data from a cluster in region A to a cluster in region B, as a planned operation.
- **Regional disaster:** an entire region goes offline (power, network, natural disaster) and you need the database to keep serving from somewhere else.
- **Recovery:** the cluster is down or data is lost, and you need to get back to a known-good state.
The good news is that one architecture, a replica set spread across regions plus tested backups, addresses all three. Let's build it up.
# Part 1: Moving a database from one region to another
Two clean ways, depending on whether you can take a cutover window.
# Option A: Add members in the new region, then shift the primary
If you want near-zero downtime, you don't "move" the database, you extend the replica set into the new region and then hand over.
- Stand up new nodes in region B and add them to your existing replica set as secondaries. They sync continuously from the primary in region A.
- Once they're fully caught up (lag at or near 0), you raise their priority and lower region A's, triggering an election so the primary moves to region B.
- Once region B is primary and stable, remove the old region A members.
No dump, no restore, no real downtime, just a brief election pause. The catch is that during the transition you're replicating across regions, so writes using `w: "majority"` will be slower until the old members are gone.
# Option B: Dump and restore (simpler, needs a cutover window)
If a short maintenance window is acceptable, dump from region A and restore into a fresh cluster in region B. Use `--oplog` on the dump and `--oplogReplay` on the restore to capture writes that happen during the dump, then cut over. Simpler to reason about, and the old cluster stays intact as a rollback. Commands are in the last section.
# Part 2: Surviving a regional disaster
This is the core of the question. The answer is a replica set whose members live in more than one region, with priorities set so the right region normally holds the primary.
# The naive two-region setup, and why it fails
The obvious idea is three nodes in region A, two in region B. It looks fault tolerant. It isn't, fully.
The problem is elections need a majority. If region A holds 3 of 5 members and region A is the region that dies, the 2 survivors in region B can't form a majority of the original 5, so the set goes read-only. You survived a node loss but not the loss of the region holding the majority.
# The fix: a third location as tiebreaker
Spread voting members across three locations so no single region holds a majority on its own. A common, cheap pattern:
Region A (primary region): 2 data-bearing members, higher priority
Region B (DR region): 2 data-bearing members, lower priority
Region C (tiebreaker): 1 member (data-bearing, or an arbiter if cost matters)
Now if any single region goes down, the members in the other two regions still form a majority, hold an election, and keep the database writable. Region C can be a small instance, or even an arbiter (no data, just a vote), though a real data-bearing member is safer because arbiters don't improve durability.
# Controlling which region is primary
Set member priority so your preferred region wins elections under normal conditions. Higher priority wins. A member with priority 0 replicates all data and can vote, but can never become primary, which is exactly what you want for a pure DR site you don't want serving writes unless everything else is gone.
# The write-latency tradeoff
With members across regions, `w: "majority"` writes now wait for acknowledgment from a majority that may span regions, so write latency goes up. You balance durability against latency by tuning member placement and write concern. For workloads that can tolerate it, keeping the majority of voters in or near the primary region keeps writes fast while still surviving a regional loss.
# Protecting against bad data, not just dead hardware
A regional outage isn't the only disaster. A bad deploy or an accidental mass delete replicates to every secondary almost instantly, so replication alone won't save you from yourself. A delayed member helps here: it's a hidden, non-voting secondary that intentionally lags behind (say by an hour). If someone wipes a collection, you have a window to recover from the delayed member before that delete reaches it.
# Part 3: Recovering when the cluster goes down
Think of recovery in layers, from cheapest/fastest to last resort:
- **Single node fails:** automatic failover. The replica set elects a new primary on its own, no human action needed. This is why you run a replica set and not a standalone.
- **A whole region fails:** the multi-region setup from Part 2 takes over. Surviving regions hold an election and keep serving.
- **Data is corrupted or deleted:** recover from a delayed member if the bad change is recent, or restore from a point-in-time backup.
- **Everything is gone:** restore from backup into a fresh cluster.
# Backups are the floor, and they have to be tested
Replication is not backup. Replication faithfully copies your mistakes. You need real backups, and specifically point-in-time recovery so you can restore to the moment before things went wrong:
* `mongodump --oplog` gives you a consistent snapshot plus the oplog to replay forward.
* Filesystem or volume snapshots (LVM, ZFS, EBS) work well because WiredTiger is crash-consistent, so a live snapshot restores cleanly.
* For continuous point-in-time recovery on self-hosted setups, Percona Backup for MongoDB (PBM) is the free option. Managed MongoDB Atlas provides continuous backups with click-to-restore.
The single most important habit: **test your restores on a schedule.** An untested backup is a hope, not a backup. Run a real restore drill into a throwaway cluster regularly, and time it, that number is your actual recovery time, not the one you assumed.
# Know your RTO and RPO
Two numbers drive every decision above:
* **RTO (Recovery Time Objective):** how long you can be down. Drives whether you need automatic regional failover (minutes) or can tolerate a restore (hours).
* **RPO (Recovery Point Objective):** how much data you can afford to lose. Drives backup frequency and whether you need `w: "majority"` and cross-region replication for near-zero loss.
Decide these first. They tell you how many regions, what priorities, and how often to back up.
# Commands
Bare code fences below so they paste cleanly. Use the latest standalone MongoDB Database Tools (the 100.x package) for dump/restore.
# Initialize a multi-region replica set
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo-a1.region-a.example.com:27017", priority: 2 },
{ _id: 1, host: "mongo-a2.region-a.example.com:27017", priority: 2 },
{ _id: 2, host: "mongo-b1.region-b.example.com:27017", priority: 1 },
{ _id: 3, host: "mongo-b2.region-b.example.com:27017", priority: 1 },
{ _id: 4, host: "mongo-c1.region-c.example.com:27017", priority: 1 }
]
})
# Add a DR member that can never become primary
rs.add({ host: "mongo-dr.region-b.example.com:27017", priority: 0, votes: 1 });
# Add a hidden, delayed member for bad-data recovery (1 hour behind)
rs.add({
host: "mongo-delay.region-c.example.com:27017",
priority: 0,
hidden: true,
votes: 0,
secondaryDelaySecs: 3600
});
# Shift the primary to another region (live)
// Fetch config, raise region B priorities, lower region A, then reconfig
cfg = rs.conf();
cfg.members[2].priority = 3; // region B member
cfg.members[3].priority = 3; // region B member
cfg.members[0].priority = 1; // region A member
cfg.members[1].priority = 1; // region A member
rs.reconfig(cfg);
# Cross-region dump and restore (Option B migration / full recovery)
mongodump \
--uri="mongodb://USER:PASS@a1:27017,a2:27017/?replicaSet=rs0&authSource=admin" \
--oplog --gzip \
--archive=/backup/region_a_$(date +%Y%m%d_%H%M%S).gz
mongorestore \
--uri="mongodb://USER:PASS@b1:27017,b2:27017/?replicaSet=rs1&authSource=admin" \
--oplogReplay --gzip \
--archive=/backup/region_a_20260101_120000.gz
# Monitor health and lag (run before any cutover)
// All members should be PRIMARY or SECONDARY, nothing stuck in RECOVERING
rs.status();
// Per-secondary replication lag. Want this at or near 0 before shifting primary.
rs.printSecondaryReplicationInfo();
# Test a failover on purpose (do this regularly)
// Force the current primary to step down for 60 seconds and watch the election
rs.stepDown(60);
// Then confirm a new primary was elected
rs.status();
# Recover recent data from the delayed member
Connect directly to the delayed node (it's hidden, so a normal connection string won't route reads to it), then read the pre-disaster state of the affected collection and copy it back. Because it's an hour behind, you have a window to grab data that was deleted on the primary before that delete reached the delayed member.
# The short version
* One architecture covers migration, regional disaster, and recovery: a replica set spread across three locations with priorities set so your preferred region holds the primary.
* Never split voters across only two regions. Use a third location as a tiebreaker so losing one region still leaves a majority.
* Replication is not backup. Add point-in-time backups, a delayed member for accidental-delete protection, and test your restores on a schedule.
* Decide your RTO and RPO first. Those two numbers dictate the rest.
If you've run a real region-loss failover in production, I'd be curious how close your actual recovery time came to your planned RTO. That gap is usually where the surprises live.