Azure Cosmos DB: Partitioning, Consistency, and Cost Control

Cosmos DB rewards good design more than almost any Azure service, and punishes bad design in your invoice. Three decisions determine whether it is fast and affordable or slow and shocking: the partition key, the consistency level, and how you provision throughput. All three are hard or impossible to change later, so this post is about getting them right the first time.

Partition Keys: The Decision You Cannot Undo

Cosmos distributes data across physical partitions by hashing your partition key, and each physical partition caps at 10,000 RU per second and 50 GB. Your provisioned throughput divides across partitions, which means a hot partition throttles even when the container as a whole has spare capacity. The two tests for a good key: high cardinality so data spreads, and alignment with your dominant query pattern so reads hit a single partition.

For a multi tenant SaaS, tenantId is the obvious candidate until one giant tenant outgrows a partition. The fix is a synthetic key like tenantId_month or hierarchical partition keys, which let you declare tenantId then userId as levels and escape both the 50 GB per key limit and the fan out penalty. For an IoT platform, deviceId spreads writes beautifully; if your queries are per device time ranges, you are done. What kills you is a key like status or country: low cardinality, guaranteed hot spots. And avoid timestamp based keys for write heavy workloads, since all current writes land on one partition.

Consistency Levels Without the Marketing

Five levels, but three real choices. Strong gives linearizability at the price of higher write latency and no multi region write. Session, the default, guarantees your own reads reflect your own writes within a session token, which is what almost every interactive application actually needs, and it costs half the read RUs of strong. Eventual (and its cousins consistent prefix and bounded staleness) is for feeds, counters, and telemetry where a few seconds of lag is invisible. My honest guidance after many projects: session for the application, eventual for analytics readers, and strong only when a regulator or a financial invariant demands it. Bounded staleness earns its complexity mainly in multi region scenarios where you need a documented staleness ceiling.

Throughput and the RU Budget

Everything costs Request Units: a 1 KB point read costs 1 RU, a write around 5, and queries whatever the query engine decides based on what it scans. Practical implications. First, point reads (id plus partition key) are dramatically cheaper than queries that return the same document, so model for point reads. Second, every property is indexed by default and every indexed property taxes every write, so trim the indexing policy to what you query:

{
  "indexingMode": "consistent",
  "includedPaths": [
    { "path": "/tenantId/?" },
    { "path": "/createdAt/?" },
    { "path": "/status/?" }
  ],
  "excludedPaths": [
    { "path": "/*" }
  ],
  "compositeIndexes": [
    [
      { "path": "/tenantId", "order": "ascending" },
      { "path": "/createdAt", "order": "descending" }
    ]
  ]
}

On provisioning: autoscale costs 1.5x the RU price but floats between 10 percent and 100 percent of your max, which wins for spiky or business hours traffic. Manual wins for flat, predictable load. Serverless suits dev environments and small event driven workloads. Check the ratio of your average to peak load: below roughly 66 percent utilization of a manual setting, autoscale is cheaper.

Watch These in Production

Alert on 429 rates above one percent, track normalized RU consumption per partition key range to catch hot partitions early, and log the RU charge header from your top queries in application telemetry, because a query that quietly costs 400 RUs is a design bug wearing a performance costume. Enable the analytical store and Synapse Link if you need reporting, rather than letting BI tools run scan queries against your transactional RUs.

Cheers
Osama

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.