Skip to content

Azure Cosmos DB Specifics

Key Points

  • Cosmos DB is Microsoft's globally distributed, multi-model NoSQL database. Five APIs: NoSQL (Core SQL), MongoDB, Cassandra, Gremlin, Table.
  • Partition key is the most important design decision. Choose for even distribution + locality of related items. Wrong choice = hot partitions + scaling hell.
  • Five consistency levels: Strong, Bounded Staleness, Session (default; recommended), Consistent Prefix, Eventual.
  • Pricing in RUs (Request Units): provisioned, autoscale, or serverless. Each operation costs RUs; 1KB read = 1 RU; writes ~5x reads.
  • Change feed: log of changes; for materialized views, integration to Event Hubs / Functions.

Concepts (deep dive)

What Cosmos DB is

Cosmos DB is Azure's globally distributed, horizontally partitioned NoSQL database — the same physical service exposed through five wire-compatible API "flavors" (NoSQL/Core SQL, MongoDB, Cassandra, Gremlin, Table). Under any API, the engine is the same: items are sharded by a partition key across many physical partitions, each partition is replicated for HA, and the whole logical container can be replicated across as many Azure regions as you want.

       ┌──────────────────────────────────────────────────────┐
       │            logical container "orders"                │
       │                                                      │
       │  hash(partitionKey) → physical partition             │
       │                                                      │
       │  ┌──────────┐   ┌──────────┐   ┌──────────┐  …       │
       │  │ phys p1  │   │ phys p2  │   │ phys p3  │          │
       │  │  +3 reps │   │  +3 reps │   │  +3 reps │          │
       │  │  10 GB   │   │  10 GB   │   │  10 GB   │          │
       │  │ ~10K RU/s│   │ ~10K RU/s│   │ ~10K RU/s│          │
       │  └──────────┘   └──────────┘   └──────────┘          │
       └──────────────────────────────────────────────────────┘
                          ▲                ▲
                          │                │
              east-us replica       west-eu replica
              (read local, write     (read local, write
               local if multi-write)  local if multi-write)

The three load-bearing decisions every Cosmos DB design rests on:

  1. Partition key — Cosmos hashes the partition-key value to pick a physical partition. The choice determines whether load spreads evenly or one partition turns into a hot spot you can't fix without re-importing the data.
  2. Consistency level — five settings between "every read sees the latest write everywhere" (Strong, expensive, high-latency) and "reads can lag by anything" (Eventual, cheap, fast). Session is the right default for almost everything.
  3. Throughput model — provisioned vs autoscale vs serverless. Cosmos charges per Request Unit (RU) consumed; how you reserve RU capacity changes both the bill and the failure mode under load.

Get those three right and Cosmos behaves brilliantly; get them wrong and you'll spend the rest of the project fighting throttling, hot partitions, and surprise bills.

Hierarchy

The data model is a four-level namespace. The account is the billing and region boundary; the database is mostly a logical grouping (and an optional shared-throughput scope); the container is the unit of partitioning, indexing, and throughput; items are the JSON documents inside.

Account → Database → Container → Items (documents)
                                  └ Partition Keys

NoSQL (Core SQL) API

var client = new CosmosClient(uri, new DefaultAzureCredential());
var container = client.GetContainer("orders", "orders");

// Insert
await container.CreateItemAsync(order, new PartitionKey(order.CustomerId));

// Read by ID + partition
var resp = await container.ReadItemAsync<Order>(orderId, new PartitionKey(customerId));

// Query
var query = new QueryDefinition("SELECT * FROM o WHERE o.status = @s")
    .WithParameter("@s", "Pending");
using var iter = container.GetItemQueryIterator<Order>(query, requestOptions: new QueryRequestOptions
{
    PartitionKey = new PartitionKey(customerId)
});
while (iter.HasMoreResults)
    foreach (var o in await iter.ReadNextAsync()) Process(o);

Partition key

The partition key is the single most important schema decision in Cosmos DB, because it's the only one you can't undo without re-importing the data. A good key spreads writes evenly across many physical partitions and keeps related items (the ones typically queried together) co-located on the same partition so a single-partition read is enough. A bad key concentrates load on one partition (every write today → today's date partition, or all e-commerce → /status='Pending' partition) and that partition becomes the throughput ceiling of your whole container.

Critical decision:

Good: high cardinality + even distribution + frequently filtered on.
Bad: small set of values; skewed traffic; "hot partition".

Examples: - ✅ /customerId if many customers + per-customer queries. - ✅ /tenantId/yearMonth for time-series. - ❌ /status (only 5 values; uneven). - ❌ /createdDate if you write today's date heavily (hot now-partition).

Synthetic keys: combine fields (/customerId-yyyymm) for distribution + locality.

RU (Request Units)

Cosmos doesn't bill in "queries" or "CPU seconds" — it bills in Request Units, an abstraction that bundles CPU, memory, and IOPS into a single number. Each operation consumes a measurable RU charge: a point-read of a 1 KB item is 1 RU; a write of the same item is 5–7; a fan-out query across partitions sums up RU charges across every partition it touched. Provisioned capacity is RU/sec; exceed it and Cosmos returns 429 Throttled. Treat RUs the way you treat latency in performance work — measure them, alarm on them, optimize them.

1 KB read (point) = 1 RU
1 KB write       = 5-7 RUs
Cross-partition query: parallel; multiple RUs
var resp = await container.ReadItemAsync<Order>(...);
Console.WriteLine($"RUs: {resp.RequestCharge}");

Track RUs in OpenTelemetry; alarm on hot operations.

Throughput modes

Mode Use
Manual provisioned Predictable load; cheapest at high RPS
Autoscale Variable load; scales 10x range
Serverless Sporadic; pay per request

Container-level or DB-level (shared).

Consistency levels

Cosmos exposes five named consistency levels along the spectrum from "every replica everywhere agrees on every read" (Strong) to "reads may see stale data with no ordering guarantee" (Eventual). Stronger consistency costs latency and RUs; weaker consistency is cheaper and faster but lets the application observe stale or out-of-order writes. Session consistency — the default — gives every client a guarantee that its own writes are immediately visible to its own reads (a per-session-token bookkeeping trick) while still allowing cross-client lag. That's the right answer for almost every user-facing app.

Strong → Bounded Staleness → Session → Consistent Prefix → Eventual
                          tighter ↔ looser; lower latency
Level Reads see
Strong Latest write
Bounded Staleness Up to N writes / N seconds behind
Session (default) Read your own writes
Consistent Prefix Order preserved; some lag
Eventual No order guarantee

Session is the default and right for most apps.

Indexing

{
  "indexingPolicy": {
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [{ "path": "/*" }],
    "excludedPaths": [{ "path": "/notIndexed/*" }]
  }
}

Default: index everything. Optimize by excluding unused fields → cheaper writes.

Composite indexes for ORDER BY on multiple fields:

"compositeIndexes": [[
  { "path": "/customerId", "order": "ascending" },
  { "path": "/createdAt", "order": "descending" }
]]

Change feed

The change feed is a persistent, ordered log of every insert and update to a container — Cosmos's built-in source for event-driven integrations. Consumers read from a checkpoint, get the changes since they last looked, advance the checkpoint. Use it to build materialized views (denormalized read models), keep a search index in sync, push to Event Hubs, or trigger Functions on change. It's the analog of database CDC, but exposed as a first-class API with built-in lease coordination across multiple consumers.

var processor = container
    .GetChangeFeedProcessorBuilder<Order>(
        "OrderProcessor",
        async (changes, ct) => { foreach (var c in changes) Project(c); })
    .WithInstanceName("worker-1")
    .WithLeaseContainer(leaseContainer)
    .Build();

await processor.StartAsync();

Stream of all changes. Power materialized views, search index updates, integrations.

Multi-region

new CosmosClientOptions
{
    ApplicationRegion = Regions.EastUS,
    ConsistencyLevel = ConsistencyLevel.Session
}

Reads served from nearest region. Multi-master for writes (configurable).

Conflict resolution

In multi-master, conflicts resolved via: - Last-Writer-Wins (default). - Custom (stored procedure).

Auditing conflicts: enable conflict feed.

Time-to-Live

container.UpdateContainerAsync(new ContainerProperties { DefaultTimeToLive = 60 });
// Per-item: { "ttl": 3600 }

Auto-delete; great for sessions, caches.

Vector search (.NET 9 + Cosmos NoSQL)

var query = new QueryDefinition("SELECT TOP 10 c.id FROM c ORDER BY VectorDistance(c.embedding, @e)")
    .WithParameter("@e", queryEmbedding);

Built-in vector indexing for AI/RAG. See Cosmos for vector data.

Cost optimization

  • Right-size RUs: use auto-scale unless flat traffic.
  • Composite indexes: avoid table scans.
  • Cross-partition queries: expensive; restructure.
  • Direct mode (vs Gateway): lower latency, fewer RUs.
  • Bulk operations: batch inserts.
new CosmosClientOptions { ConnectionMode = ConnectionMode.Direct }

Stored procedures, triggers, UDFs

JavaScript-based. Avoid for new code — better to handle logic in app. Atomic across single partition.

Mongo / Cassandra / Gremlin / Table APIs

Same backend; different protocol. Mongo API for migration from existing Mongo apps. Cassandra for wide-column. Gremlin for graph. Table for cheaper Azure Table Storage migration.

NoSQL (Core SQL) API has the most features.

Indexing Metrics + Query Stats

new QueryRequestOptions { PopulateIndexMetrics = true };
// resp.IndexMetrics = which indexes used

Optimize cold queries.


Code: correct vs wrong

❌ Wrong: cross-partition query everywhere

"SELECT * FROM c WHERE c.email = @e"   // no PK filter → fans out

✅ Correct: include PK filter or design key for query

"SELECT * FROM c WHERE c.customerId = @cid AND c.email = @e"

❌ Wrong: small partition key cardinality

PartitionKey: /country   // 200 values; one country = millions of items

✅ Correct: high cardinality

PartitionKey: /customerId

Design patterns for this topic

Pattern 1 — "Synthetic partition key"

  • Intent: combine fields for distribution + locality.

Pattern 2 — "Session consistency default"

  • Intent: RYW (read your writes) without strong cost.

Pattern 3 — "Change feed for materialized views"

  • Intent: event-sourced read models.

Pattern 4 — "Composite indexes for ORDER BY"

  • Intent: efficient sorted queries.

Pattern 5 — "Auto-scale unless flat"

  • Intent: cost-efficient.

Pros & cons / trade-offs

Aspect Pros Cons
Cosmos Global; multi-model; fast RU pricing learning curve
NoSQL API Most features Lock-in to Cosmos
Mongo API Migration Lags behind native Mongo
Strong consistency Latest writes Higher latency
Session Balanced Most apps default

When to use / when to avoid

  • Use for global distribution, multi-region writes.
  • Use for highly variable / serverless workloads.
  • Avoid for relational-heavy logic.
  • Avoid for cost-sensitive small data — Postgres cheaper.

Interview Q&A

Q1. Five APIs? NoSQL (Core SQL), MongoDB, Cassandra, Gremlin, Table.

Q2. Why partition key matters? Determines distribution. Wrong PK = hot partitions + RU exhaustion.

Q3. RU? Request Units. Each op costs RUs. Provisioned per second.

Q4. Consistency levels? Strong, Bounded Staleness, Session, Consistent Prefix, Eventual.

Q5. Default consistency? Session — read your writes; balanced.

Q6. Change feed? Stream of all changes. Power materialized views, integrations.

Q7. Multi-master writes? Both regions accept writes; conflicts resolved by LWW or custom.

Q8. TTL? Auto-delete after N seconds.

Q9. Composite indexes? For multi-field ORDER BY; default indexing doesn't.

Q10. Direct vs Gateway mode? Direct: TCP to backends; lower latency. Gateway: HTTPS through gateway.

Q11. Vector search? Cosmos NoSQL has native vector indexing for AI/RAG.

Q12. Auto-scale benefit? Scales 10x without throttling for spikes; idle = 10% min cost.


Gotchas / common mistakes

  • ⚠️ Wrong PK — hot partition; throttled.
  • ⚠️ Cross-partition queries everywhere — high RUs.
  • ⚠️ Default index everything — write cost; exclude unused.
  • ⚠️ Strong consistency for everything — latency.
  • ⚠️ No RU monitoring — cost surprise.

Further reading