I've built production systems on both MongoDB Atlas and Azure Cosmos DB, and I've been asked to migrate between them more than once. Both are excellent document stores. They're not interchangeable. The choice matters for your monthly bill, your operational complexity, and your developer experience — sometimes by a factor of three or four.
Here's the analysis I run through when a new project lands and we need to pick a document database on Azure.
This is the first thing to understand because it affects everything downstream.
Cosmos DB uses a Request Unit (RU) model. Every operation — read, write, query, delete — consumes RUs based on item size, indexing, and consistency level. You provision RU/s capacity (or use serverless, which charges per RU consumed). A simple point read of a 1 KB document costs 1 RU. A cross-partition query on a large collection might cost 50-500 RUs. The gotcha is that complex queries can consume RUs at rates that are hard to predict before you profile them.
Cosmos DB serverless removes provisioned capacity entirely, billing strictly per-million RUs consumed. For development workloads and light production traffic, this is economical. For high-throughput workloads, provisioned throughput (especially with autoscale) is more predictable.
MongoDB Atlas charges for the underlying cluster tier — M10, M20, M30, etc. — not per operation. An M10 cluster (2 vCPU, 2 GB RAM) is around $57/month. You can run unlimited operations against it. Query performance depends on your indexes and cluster tier, not on a per-operation cost meter.
The implication: Atlas is more predictable at scale. Cosmos DB can surprise you with a poorly-written query that burns through RUs. I've seen a missing index on Cosmos DB turn a routine aggregate query into a $400/month line item.
Both offer multi-region replication, but with different consistency models.
Cosmos DB has five well-defined consistency levels: Strong, Bounded Staleness, Session, Consistent Prefix, and Eventual. Multi-region writes are supported at all levels except Strong, which forces single-write-region. This is Cosmos DB's strongest differentiator for globally distributed applications — you configure per-request consistency, and the SLA is backed contractually.
Atlas global distribution uses a primary-replica model with zone sharding in Global Clusters. Multi-region writes require zone-based sharding, which requires careful schema design. For most applications that don't need true multi-master writes, Atlas cross-region reads work fine and the operational model is simpler.
My take: if you need sub-100ms reads from three continents with multi-region writes and contractual consistency guarantees, Cosmos DB is purpose-built for it. For most Azure-hosted applications with a primary region and a secondary for DR, Atlas global clusters are simpler to reason about.
Both databases support event-driven patterns, but the implementations differ.
Cosmos DB Change Feed is a sorted, per-partition log of all document creates and updates (not deletes — that's a known gap, though soft-delete patterns work around it). You read it via the Change Feed Processor SDK or Azure Functions trigger. It's reliable and integrates naturally with Azure Event Hubs and Service Bus:
// Azure Functions Cosmos trigger
[FunctionName("ProcessOrderChanges")]
public async Task Run(
[CosmosDBTrigger(
databaseName: "orders-db",
containerName: "orders",
Connection = "CosmosDBConnection",
LeaseContainerName = "leases",
CreateLeaseContainerIfNotExists = true)]
IReadOnlyList<Order> changedOrders,
ILogger log)
{
foreach (var order in changedOrders)
{
await _eventBus.PublishAsync(new OrderChangedEvent(order));
}
}MongoDB Change Streams are resumable streams built on the oplog. They support full document replacement, update descriptions (what fields changed), and deletes. The richer event shape is useful for event sourcing patterns where you need to know the delta, not just the full document state.
// MongoDB change stream in .NET
var pipeline = new EmptyPipelineDefinition<ChangeStreamDocument<Order>>()
.Match(change => change.OperationType == ChangeStreamOperationType.Insert
|| change.OperationType == ChangeStreamOperationType.Update);
using var cursor = await collection.WatchAsync(pipeline, new ChangeStreamOptions
{
FullDocument = ChangeStreamFullDocumentOption.UpdateLookup,
ResumeAfter = lastResumeToken
});
await cursor.ForEachAsync(change =>
{
// change.FullDocument contains the updated document
// change.UpdateDescription.UpdatedFields contains only the changed fields
});The resume token on Atlas change streams is more flexible than the Cosmos lease-based checkpoint model, particularly for custom consumers outside the SDK.
Both databases support Azure Managed Identity, though the implementations differ.
For Cosmos DB, managed identity is native and first-class:
var credential = new DefaultAzureCredential();
var cosmosClient = new CosmosClient(
accountEndpoint: "https://my-cosmos.documents.azure.com:443/",
tokenCredential: credential
);For Atlas, managed identity works through a middle layer — you store the connection string in Azure Key Vault and access it via managed identity. Direct Atlas authentication uses X.509 certificates with Azure AD federation, which is supported but requires more configuration:
// Atlas with connection string from Key Vault via managed identity
var secretClient = new SecretClient(
new Uri("https://my-vault.vault.azure.net/"),
new DefaultAzureCredential()
);
var connString = await secretClient.GetSecretAsync("mongodb-atlas-connection-string");
var mongoClient = new MongoClient(connString.Value.Value);For NexusEd and 360JobReady, we use Cosmos DB. Both products are Azure-native, and the serverless tier handles the traffic patterns of early-stage products economically. The Azure Functions triggers for Cosmos changes fit naturally into our event-driven notification flows.
For clients migrating from on-premises MongoDB, we recommend Atlas without hesitation. The migration path is smooth, the query language compatibility is complete (unlike Cosmos for MongoDB's partial API support), and developers don't need to relearn anything.
Neither is universally better. The right answer comes from your team's existing knowledge, your operational preferences, and whether your workload looks more like "globally distributed with Azure-native integrations" or "rich document queries with event-driven change processing."