Cosmos DB isn’t just a NoSQL database; it’s a globally distributed, multi-model database service that lets you provision throughput and storage independently across physical partitions, offering tunable consistency and low latency worldwide.

Let’s see it in action. Imagine we have a simple products collection in an existing MongoDB database. We want to migrate this to Cosmos DB’s API for MongoDB.

Existing MongoDB Data:

[
  { "_id": "60c72b2f9b1e8c001f8e4e5a", "name": "Laptop", "price": 1200, "category": "Electronics" },
  { "_id": "60c72b2f9b1e8c001f8e4e5b", "name": "Desk Chair", "price": 250, "category": "Furniture" }
]

Cosmos DB Setup (API for MongoDB):

First, we create a Cosmos DB account, then a database, and finally a collection. For the collection, we’ll set a provisioned throughput. Let’s say we need 400 RU/s for this initial load and ongoing operations.

// Cosmos DB Collection Configuration (Conceptual)
{
  "name": "products",
  "databaseName": "inventoryDb",
  "partitionKey": {
    "path": "/category",
    "kind": "Hash"
  },
  "throughput": {
    "manual": 400
  }
}

Migration Strategy: Incremental Sync

For minimal downtime, we won’t do a single big-bang migration. Instead, we’ll use a tool that can perform an initial bulk load and then continuously sync changes from the source MongoDB to Cosmos DB. Azure Data Factory (ADF) is a strong candidate for this.

  1. Initial Load with ADF:

    • Create a Linked Service in ADF for your source MongoDB.
    • Create a Linked Service for your Cosmos DB (API for MongoDB).
    • Create a Copy Data activity in ADF.
    • Source: Your MongoDB products collection.
    • Sink: Your Cosmos DB products collection.
    • Configure this to run once, copying all existing documents.
  2. Change Data Capture (CDC) and Incremental Sync:

    • This is the crucial part for minimal downtime. For MongoDB, you’d typically enable the oplog. Cosmos DB’s API for MongoDB doesn’t directly expose an oplog in the same way.
    • Alternative 1: Trigger-based Sync (if source allows): If your source database has triggers, you could write triggers to capture changes (inserts, updates, deletes) into a separate "change log" collection. ADF could then periodically poll this change log and apply those changes to Cosmos DB.
    • Alternative 2: Application-Level Sync: Modify your application to write changes to both the source MongoDB and a temporary staging area (e.g., Azure Queue Storage, another table in Cosmos DB) simultaneously. A separate process (e.g., an Azure Function or another ADF pipeline) can then read from this staging area and update Cosmos DB. This requires application code changes.
    • Alternative 3: Timestamp-based Sync: If your documents have a reliable lastModified timestamp, ADF can be configured to poll the source for documents modified since the last sync. This is less robust for deletes.
    • For this example, let’s assume Application-Level Sync:
      • Your application now writes to source_db.products and cosmos_staging.changes.
      • ADF Pipeline (Scheduled):
        • Source: Cosmos DB (API for MongoDB) Linked Service.
        • Query: db.changes.find({ processed: false })
        • Sink: Cosmos DB (API for MongoDB) Linked Service.
        • Operation: Upsert.
        • Logic: For each document from changes, extract the operation type (insert, update, delete) and the document data. If it’s an insert/update, upsert the document into inventoryDb.products. If it’s a delete, remove it from inventoryDb.products. After successful processing, update the processed: true flag on the document in cosmos_staging.changes.
        • Schedule: Run this pipeline every 1-5 minutes.
  3. Cutover:

    • Once the incremental sync has been running for a while and you’re confident that Cosmos DB is up-to-date, you can perform the cutover.
    • Stop Application Writes: Briefly halt writes to the source database.
    • Final Sync: Run the incremental sync pipeline one last time to catch any lingering changes.
    • Update Application Configuration: Reconfigure your application to point to the Cosmos DB endpoint.
    • Resume Application Writes: Start your application with writes directed to Cosmos DB.

The beauty of this approach is that the application only experiences a very brief write interruption during the final cutover, not the entire migration duration.

The most surprising true thing about Cosmos DB is that its "global distribution" doesn’t mean you have one giant database spread everywhere; rather, you provision throughput and storage per region within a globally distributed account, and Cosmos DB handles the replication and consistency across those regions based on your configuration.

The partitioning strategy is critical. Choosing a good partition key (like /category in our example) ensures even data distribution and efficient query performance, preventing "hot partitions" that can throttle your throughput. If /category had very few distinct values (e.g., only "A" and "B"), it would be a poor choice.

The one thing most people don’t know is how the RU/s (Request Units per second) are consumed. Every database operation (read, write, query) has a cost in RUs, which is influenced by the size of the document, the operation type, and the consistency level. A simple _id lookup might cost 1 RU, while a complex query with a filter on a non-indexed field could cost hundreds or thousands of RUs, potentially exceeding your provisioned throughput and causing throttling. You can inspect the x-ms-request-charge header in the response to see the RU cost of each operation.

Once you’ve mastered incremental migration and partitioning, the next challenge is optimizing query performance across globally distributed data, especially when dealing with cross-partition queries.

Want structured learning?

Take the full Cosmos-db course →