Cosmos DB partitioning and consistency

Microsoft Azure · Expert · 18 min

Partitioning and consistency choices are effectively permanent and decide Cosmos DB's cost, latency and correctness.

Under the hood a container's data is split into physical partitions, each serving up to 10,000 RU/s and 50 GB. Provisioned RU/s are divided evenly between them, so a hot partition key (say /country where one country dominates) throttles at a fraction of the container's throughput while the rest sits idle. Good keys have high cardinality, spread writes evenly, and match your most frequent queries' filters so they stay single-partition.

Techniques:

  • Synthetic keys (tenantId-yyyymm) to spread load.
  • Hierarchical partition keys (up to three levels, e.g. /tenantId, /userId) to get past the 20 GB logical-partition limit for big tenants while still routing queries on a prefix.
  • The partition key of a container cannot be changed; migrating means copying to a new container (container copy jobs or the change feed).

Consistency levels, strongest to weakest: Strong, Bounded Staleness, Session (default), Consistent Prefix, Eventual. Session guarantees read-your-writes within a session token and suits most apps. Strong and Bounded Staleness reads cost twice the RUs of weaker levels, and Strong adds cross-region write latency. With multi-region writes, Strong is not available and concurrent updates are resolved by last-writer-wins on _ts (or a custom policy), so design for conflicts.

The change feed exposes ordered changes per partition key range, the basis for materialized views and event-driven pipelines.

Example

# pip install azure-cosmos azure-identity
from azure.cosmos import CosmosClient, PartitionKey
from azure.identity import DefaultAzureCredential

client = CosmosClient("https://cosmos-saas.documents.azure.com:443/", DefaultAzureCredential(),
                      consistency_level="Session")
db = client.get_database_client("saas")

# Hierarchical partition key: tenant, then user
events = db.create_container_if_not_exists(
    id="events",
    partition_key=PartitionKey(path=["/tenantId", "/userId"], kind="MultiHash"),
    offer_throughput=4000)

events.upsert_item({"id": "e1", "tenantId": "t-big", "userId": "u-7", "type": "login"})

# Prefix query: routed only to t-big's partitions, not fanned out to all
rows = events.query_items("SELECT * FROM c WHERE c.type = 'login'", partition_key=["t-big"])
print(sum(1 for _ in rows))  # 1

Remember

  • Physical partitions cap at 10,000 RU/s and 50 GB; hot keys throttle early
  • Partition keys are permanent; choose for even writes and query routing
  • Hierarchical keys lift the 20 GB logical-partition ceiling
  • Session consistency is the default; Strong costs more and blocks multi-region writes

Watch out for

  • Choosing a low-cardinality key like /status or /country, creating a hot partition.

Check yourself

  1. A container has 30,000 RU/s over 3 physical partitions and one tenant generates most writes. Roughly what can that tenant's partition sustain?
  2. Which consistency level is the default and guarantees read-your-own-writes within a client session?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/azure/cosmos-db/partitioning-overview

Builds on

Unlocks

Nothing builds on this yet.