{"id":153,"date":"2026-07-13T21:06:50","date_gmt":"2026-07-14T04:06:50","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/documentdb\/?p=153"},"modified":"2026-07-13T21:06:50","modified_gmt":"2026-07-14T04:06:50","slug":"migration-47tb-under-48-hours","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/documentdb\/migration-47tb-under-48-hours\/","title":{"rendered":"How a Large Enterprise Migrated 47 TB and 7,000+ Collections to Azure DocumentDB in Under 48 Hours"},"content":{"rendered":"<p class=\"code-line\" dir=\"auto\" data-line=\"2\">Large database migrations are where good planning either pays off or falls apart.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"4\">In a MongoDB-compatible estate with thousands of collections, the hard part is not only moving data. It is deciding what to move, what to defer, how to sequence the work, and how to keep the system stable while ingestion is running at full speed.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"6\">That is exactly what we saw in a recent large enterprise migration to Azure DocumentDB:\u00a0<strong>47 TB of data<\/strong>,\u00a0<strong>7,000+ collections<\/strong>, a\u00a0<strong>27-node source footprint<\/strong>, and a production cutover window of\u00a0<strong>less than 48 hours<\/strong>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"8\">The target was intentionally smaller and simpler: a\u00a0<strong>3-node, 16 TB Azure DocumentDB cluster<\/strong>.<\/p>\n<p><figure id=\"attachment_155\" aria-labelledby=\"figcaption_attachment_155\" class=\"wp-caption alignnone\" ><a href=\"https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard.webp\"><img decoding=\"async\" class=\"wp-image-155 size-large\" src=\"https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard-1024x576.webp\" alt=\"Migration sequencing dashboard showing a four-step flow: assess, bulk ingest, build indexes, and harden for production, with monitoring indicators for CPU, disk, WAL, throughput, throttling, and progress.\" width=\"1024\" height=\"576\" srcset=\"https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard-1024x576.webp 1024w, https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard-300x169.webp 300w, https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard-768x432.webp 768w, https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard-1536x864.webp 1536w, https:\/\/devblogs.microsoft.com\/documentdb\/wp-content\/uploads\/sites\/93\/2026\/07\/documentdb-migration-sequencing-dashboard.webp 1920w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption id=\"figcaption_attachment_155\" class=\"wp-caption-text\">Migration sequencing separated assessment, bulk ingestion, index building, and production hardening.<\/figcaption><\/figure><\/p>\n<p>Migration sequencing separated assessment, bulk ingestion, index building, and production hardening.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"10\">Here is what made the migration work.<\/p>\n<h2 id=\"why-optimize-for-the-migration-phase-instead-of-steady-state\" class=\"code-line\" dir=\"auto\" data-line=\"25\">Why optimize for the migration phase instead of steady state?<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"14\">Most production database guidance assumes the system is already in steady state. You enable high availability. You build indexes. You configure replicas. You optimize for durability, resilience, and query performance.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"16\">During a bulk migration, those same choices can become bottlenecks.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"18\">For this migration, the team treated the migration path as its own architecture. The goal was not to turn on every production feature immediately. The goal was to move the right data quickly, safely, and predictably \u2014 then harden the target after the bulk load completed.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"20\">Three principles guided the run:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"22\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"22\"><strong>Assessment-first.<\/strong>\u00a0The team used real estate data to decide scope, sizing, collection placement, and job strategy.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"23\"><strong>Ingestion-first sequencing.<\/strong>\u00a0HA, cross-region replicas, and non-unique index builds were deferred until after the bulk load.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"24\"><strong>Operate with visibility.<\/strong>\u00a0Migration jobs ran aggressively, but source and target utilization were monitored continuously and throttled when needed.<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"26\">The result was a migration that looked less like a one-click copy operation and more like a carefully sequenced production event.<\/p>\n<h2 id=\"1-move-only-the-data-that-matters\" class=\"code-line\" dir=\"auto\" data-line=\"28\">1. Move only the data that matters<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"30\">The source estate had thousands of collections across multiple workloads. As with many large systems, not every collection carried equal value.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"32\">Some collections were active. Some were legacy leftovers. Some large collections contained only a subset of documents that still mattered to the workload.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"34\">The team used assessment data to identify what should move and what should not. Junk collections were skipped entirely. Large collections were filtered to include only relevant documents.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"36\">That reduced the amount of data to move and helped the team stay focused on the production cutover goal.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"38\">The lesson is simple:\u00a0<strong>the fastest data to migrate is the data you do not migrate<\/strong>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"40\">For a 47 TB estate, scope control is not just housekeeping. It is one of the most important performance optimizations.<\/p>\n<h2 id=\"2-defer-ha-and-cross-region-replication-during-bulk-ingestion\" class=\"code-line\" dir=\"auto\" data-line=\"42\">2. Defer HA and cross-region replication during bulk ingestion<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"44\">High availability and cross-region replication are important for production resilience. But during testing, enabling them during ingestion created write-ahead log pressure.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"46\">As the load ran, WAL logs accumulated. Disk pressure increased. The system eventually hit the kind of failure mode every migration team wants to avoid: storage filling during the migration.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"48\">The team changed the sequence:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"50\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"50\">Load the bulk data first.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"51\">Keep HA and cross-region replicas disabled during the heaviest ingestion period.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"52\">Turn them back on after the bulk load completed.<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"54\">This did not remove resilience from the final design. It simply recognized that the migration state and steady state have different priorities.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"56\">During ingestion, optimize for movement. After ingestion, optimize for resilience.<\/p>\n<h2 id=\"3-build-indexes-after-the-load\" class=\"code-line\" dir=\"auto\" data-line=\"58\">3. Build indexes after the load<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"60\">Indexes are critical for query performance, but they are not free.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"62\">In testing, building non-unique indexes while rapid ingestion was running caused CPU and disk utilization to spike. The system was accepting a large volume of writes while also doing expensive index work for future reads.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"64\">The team moved non-unique index builds to\u00a0<strong>post-ingestion<\/strong>, ran them in\u00a0<strong>blocking mode<\/strong>, and paused CDC online sync while the builds completed.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"66\">That created two clean phases:<\/p>\n<ol class=\"code-line\" dir=\"auto\" data-line=\"68\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"68\">Move the data quickly.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"69\">Prepare the data for steady-state query performance.<\/li>\n<\/ol>\n<p class=\"code-line\" dir=\"auto\" data-line=\"71\">This made the migration more predictable and avoided unnecessary resource contention during the most performance-sensitive part of the run.<\/p>\n<h2 id=\"4-do-not-copy-the-source-topology-blindly\" class=\"code-line\" dir=\"auto\" data-line=\"73\">4. Do not copy the source topology blindly<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"75\">The source environment was large: 27 nodes arranged as 9 shards with 3 nodes each. It would have been natural to mirror that shape on the target.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"77\">But the team looked at the workload instead of copying the topology.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"79\">No individual collection was too large for one target node. The source was sharded on\u00a0<code>_id<\/code>, while Azure DocumentDB queries for this workload performed better when collections were kept un-sharded.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"81\">So the team kept collections un-sharded on the target and distributed them across a smaller number of nodes by storage volume. Linked collections were kept together on the same node to avoid unnecessary cross-node behavior.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"83\">The final target was a 3-node, 16 TB cluster.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"85\">The lesson: the goal of a migration is not to recreate the source. It is to design the target for the workload you actually want to run.<\/p>\n<h2 id=\"5-split-the-migration-into-four-balanced-jobs\" class=\"code-line\" dir=\"auto\" data-line=\"87\">5. Split the migration into four balanced jobs<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"93\">The team used UAT to validate the tooling, train operators, find issues, and refine the production runbook before production data was at risk.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"95\">The production migration was split into\u00a0<strong>four balanced jobs<\/strong>:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"97\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"97\">Two jobs carried the largest and highest-throughput collections.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"98\">Two jobs carried the remaining collections, distributed to balance offline and online load.<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"100\">This gave the team enough parallelism to move quickly without turning the run into operational chaos.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"102\">The jobs ran at full speed, but not blindly. Source and target utilization were monitored manually. When pressure rose, the team throttled. When the system had room, the jobs continued.<\/p>\n<h2 id=\"what-made-the-cutover-work\" class=\"code-line\" dir=\"auto\" data-line=\"104\">What made the cutover work?<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"106\">No single trick made the migration successful. The win came from a series of disciplined choices:<\/p>\n<table class=\"code-line\" dir=\"auto\" data-line=\"108\">\n<thead class=\"code-line\" dir=\"auto\" data-line=\"108\">\n<tr class=\"code-line\" dir=\"auto\" data-line=\"108\">\n<th>Choice<\/th>\n<th>Why it mattered<\/th>\n<\/tr>\n<\/thead>\n<tbody class=\"code-line\" dir=\"auto\" data-line=\"110\">\n<tr class=\"code-line\" dir=\"auto\" data-line=\"110\">\n<td>Assess first<\/td>\n<td>Decisions were based on real collection, storage, and workload data.<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"111\">\n<td>Prove in UAT<\/td>\n<td>Tooling issues surfaced before production.<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"112\">\n<td>Defer HA and replicas<\/td>\n<td>Reduced WAL and disk pressure during ingestion.<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"113\">\n<td>Build indexes after load<\/td>\n<td>Avoided CPU and disk contention while data was moving.<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"114\">\n<td>Keep collections un-sharded<\/td>\n<td>Matched the target design to the workload, not the source topology.<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"115\">\n<td>Split into four jobs<\/td>\n<td>Balanced throughput and operational control.<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"116\">\n<td>Monitor continuously<\/td>\n<td>Allowed the team to run fast and throttle only when needed.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"code-line\" dir=\"auto\" data-line=\"118\">The final outcome:\u00a0<strong>47 TB moved, 7,000+ collections handled, a 27-node source consolidated onto a 3-node Azure DocumentDB target, and production cutover completed in under 48 hours<\/strong>.<\/p>\n<h2 id=\"try-the-pattern-on-your-own-migration-plan\" class=\"code-line\" dir=\"auto\" data-line=\"120\">Try the pattern on your own migration plan<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"122\">If you are planning a large MongoDB-compatible migration to Azure DocumentDB, start with these questions:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"124\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"124\">Which collections are still actively needed?<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"125\">Which large collections can be filtered?<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"126\">Which production features should be enabled after ingestion instead of before it?<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"127\">Which indexes can safely move to a post-load phase?<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"128\">Does the target need to mirror the source topology, or should it reflect the actual workload?<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"129\">How can the migration be split into balanced, observable jobs?<\/li>\n<\/ul>\n<h2 id=\"which-migration-option-should-you-choose-for-azure-documentdb\" class=\"code-line\" dir=\"auto\" data-line=\"140\">Which migration option should you choose for Azure DocumentDB?<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"142\">Azure DocumentDB supports multiple migration approaches for different source environments and operational requirements. Review the\u00a0<a href=\"https:\/\/learn.microsoft.com\/azure\/documentdb\/migration-options\" data-href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/documentdb\/migration-options\">Azure DocumentDB migration options<\/a>\u00a0to compare the available tools and choose the approach that best fits your workload and cutover plan.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"144\">Large migrations become much less risky when the runbook is driven by evidence and the work is sequenced intentionally.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"146\">The best migrations are not heroic. They are boring in exactly the right ways.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Large database migrations are where good planning either pays off or falls apart. In a MongoDB-compatible estate with thousands of collections, the hard part is not only moving data. It is deciding what to move, what to defer, how to sequence the work, and how to keep the system stable while ingestion is running at [&hellip;]<\/p>\n","protected":false},"author":96034,"featured_media":157,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,3],"tags":[],"class_list":["post-153","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-document-db","category-mongodb-migrations"],"acf":[],"blog_post_summary":"<p>Large database migrations are where good planning either pays off or falls apart. In a MongoDB-compatible estate with thousands of collections, the hard part is not only moving data. It is deciding what to move, what to defer, how to sequence the work, and how to keep the system stable while ingestion is running at [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts\/153","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/users\/96034"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/comments?post=153"}],"version-history":[{"count":3,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts\/153\/revisions"}],"predecessor-version":[{"id":162,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts\/153\/revisions\/162"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/media\/157"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/media?parent=153"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/categories?post=153"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/tags?post=153"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}