MongoDB Mastery
MongoDB Mastery lessons / Module 38: Cluster Migrations

Cluster migrations and cutover

Level 6: Sharding / Distributed25 min

Move data between clusters, versions, topologies or data centres with minimal downtime: dump/restore, replica set extension, mongosync and CDC-based approaches, with validation and rollback.

This lesson is interactive. Run every example against an in-browser MongoDB, see explain() plans drawn as trees, and take the quiz.
Open the interactive lesson
You will learn to
  • Choose a migration method based on size, downtime tolerance and versions
  • Plan initial copy, catch-up, validation and cutover
  • Validate data with counts, hashes and sampling
  • Prepare a rollback path

conceptThe concept

MethodDowntimeNotes
mongodump / mongorestoreLong (copy time)Simple, small datasets, version jumps
Add members to the replica set in the new location, then step down and remove old onesSeconds (election)Same replica set; good for hardware or data-centre moves
mongosync (cluster-to-cluster sync)Short cutoverContinuous sync from source to destination cluster with commit and optional reverse sync; version and topology requirements apply
Change streams / CDC pipelineShort cutoverCustom transformations, heterogeneous targets
Physical backup restore (PBM)Copy plus catch-upLarge datasets, same major version

Phases: initial copy → continuous catch-up (oplog or change streams) → validation → cutover (stop writes, wait for zero lag, switch connection strings) → monitoring with a rollback window.

whyWhy it matters

Migrations happen more often than people expect: hardware refresh, cloud moves, version upgrades that skip several majors, consolidations, or moving off a managed service to self-hosted infrastructure. They fail on details: missing indexes, a collection nobody knew about, users and roles not copied, or no way back.

Use caseReal-world scenario

Moving 4 TB from an on-premises replica set to a self-managed Kubernetes cluster: mongosync performs the initial sync over three days and keeps up with changes; validation compares counts and per-collection hashes; on the cutover night writes are paused for 4 minutes, lag reaches zero, the commit completes, and applications switch connection strings. Reverse sync is kept running for a week as the rollback path.

Try itExamples

Run and edit these in the interactive lesson.

mongosync outline
mongosync --cluster0 "mongodb://src-0,src-1,src-2/?replicaSet=rs0" \
          --cluster1 "mongodb://dst-0,dst-1,dst-2/?replicaSet=rs0" &
curl -X POST localhost:27182/api/v1/start -d '{ "source": "cluster0", "destination": "cluster1" }'
curl localhost:27182/api/v1/progress            # state, lagTimeSeconds, canCommit
# cutover: stop application writes, wait for lagTimeSeconds ~ 0
curl -X POST localhost:27182/api/v1/commit -d '{}'
Validation queries you can run on both sides
db.getCollectionNames().filter(c => !c.startsWith("system.")).map(c => ({ coll: c, count: db.getCollection(c).countDocuments(), indexes: db.getCollection(c).getIndexes().length }))
Content fingerprint for a collection
db.orders.aggregate([
  { $project: { k: { $concat: [{ $toString: "$_id" }, ":", { $toString: "$total" }, ":", "$status"] } } },
  { $sort: { k: 1 } },
  { $group: { _id: null, n: { $sum: 1 }, sample: { $first: "$k" }, last: { $last: "$k" } } }
])
On real clusters prefer the dbHash command on quiesced data; this pattern works for spot checks of chosen fields.

internalsUnder the hood

Catch-up depends on the source oplog window: if the initial copy takes longer than the window, the sync cannot resume and must start over. Index builds on the destination can dominate copy time, so tools build them at the right point. Validation methods: countDocuments per collection, dbHash on quiesced data, sampled document comparison, and application-level checks.

Avoid / preferDo this, not that

Avoid
mongodump --uri "$SRC" --archive | mongorestore --uri "$DST" --archive
# while the application keeps writing to the source
Prefer
mongosync --cluster0 "$SRC" --cluster1 "$DST"
curl -X POST localhost:27182/api/v1/start --data '{ "source": "cluster0", "destination": "cluster1" }'
# continuous sync, then a short planned cutover

A dump is a point-in-time copy; every write after it starts is lost at cutover. Live sync keeps applying changes until you are ready to switch.

Avoid
// counts look similar, switch the application now
Prefer
// pause writes or commit the sync, then compare per collection:
// counts, index lists, and sampled or hashed documents on both sides

"Similar" hides missing indexes, validators and partial collections. Verify while the source is quiet, and keep a rollback path until the new cluster has run under real load.

PitfallsCommon mistakes

  • Forgetting users, roles, indexes, validators, TTL settings and views in the destination.
  • Initial copy longer than the oplog window.
  • No rehearsal with production-sized data.
  • No rollback plan after cutover (writes have already happened on the new cluster).
  • Changing versions, topology and schema all in one migration.

LimitsWhen not to use it

Live sync tools have prerequisites (versions, feature compatibility, restrictions on DDL during sync). For a small database with an agreed downtime window, dump and restore during the window is simpler and easier to reason about. Do not change topology, version and schema in one migration; split them.

SecuritySecurity notes

Migration tools need broad read and write privileges on both clusters. Create temporary dedicated users, revoke them after cutover, use TLS on both legs, and do not leave dump archives on shared disks or buckets without encryption and a deletion date.

ChallengeTry it yourself

Build a migration fingerprint for the current database: for each collection, the document count and the first and last _id. You would run the same script on both clusters and diff the output.

Show one solution
One solution
const edge = (coll, dir) => {
  const d = coll.find({}, { _id: 1 }).sort({ _id: dir }).limit(1).toArray()[0]
  return d ? d._id : null
}
const fingerprint = db.getCollectionNames().sort().map(c => {
  const coll = db.getCollection(c)
  return { coll: c, count: coll.countDocuments(), firstId: edge(coll, 1), lastId: edge(coll, -1) }
})
fingerprint

InterviewQuestions you should be able to answer

How do you migrate a large MongoDB deployment with minimal downtime?

Pick a method that supports continuous sync (adding replica set members in the new location, mongosync, or CDC); size the oplog window to cover the initial copy; copy users, roles, indexes and collection options; rehearse with production-sized data; validate counts and hashes; schedule cutover with a short write freeze until lag is zero; switch connection strings; monitor closely and keep a reverse sync or fallback plan for a defined window.

QuizCheck your understanding

4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.