Cosmos DB Data Model: Containers, Items, and Partition Keys

Cosmos DB organizes data in a three-level hierarchy: database accounts contain databases, databases contain containers, and containers hold items. Understanding this structure — and especially how partition keys control data distribution — is the foundation for designing Cosmos DB schemas that perform well at scale.

The data hierarchy: account, database, container, item

A Cosmos DB account is the top-level Azure resource. It defines the region (or regions for multi-region replication), consistency level, and connection credentials. An account can contain multiple databases, but throughput and storage are provisioned at the container level (or optionally shared at the database level).

A database is a namespace that groups related containers. It has no schema and no throughput of its own unless you use database-level throughput sharing. You can have multiple databases in one account to separate applications logically while sharing infrastructure costs.

A container is the unit of scalability in Cosmos DB. It maps to a collection in MongoDB API terms, or a table in Cassandra API terms. Throughput (RU/s) is provisioned per container. A container has exactly one partition key, which determines how data is distributed internally.

An item is a single JSON document stored in a container. It has no predefined schema — different items in the same container can have different fields. Every item has system properties that Cosmos DB manages automatically.

# Create an account
az cosmosdb create \
  --resource-group myResourceGroup \
  --name mycosmosaccount \
  --kind GlobalDocumentDB \
  --locations regionName=eastus

# Create a database
az cosmosdb sql database create \
  --resource-group myResourceGroup \
  --account-name mycosmosaccount \
  --name myDatabase

# Create a container with a partition key
az cosmosdb sql container create \
  --resource-group myResourceGroup \
  --account-name mycosmosaccount \
  --database-name myDatabase \
  --name orders \
  --partition-key-path /customerId \
  --throughput 400

JSON document structure and system properties

Items in Cosmos DB are JSON documents. There is no schema enforcement — you can store whatever JSON structure you need. Cosmos DB adds several system-managed properties to every item:

{
  "id": "order-8f3a2c1b",
  "customerId": "cust-00192",
  "orderDate": "2026-03-19T14:30:00Z",
  "status": "shipped",
  "total": 149.99,
  "items": [
    {
      "productId": "prod-441",
      "name": "Wireless Keyboard",
      "quantity": 1,
      "price": 79.99
    },
    {
      "productId": "prod-882",
      "name": "USB Hub",
      "quantity": 2,
      "price": 34.99
    }
  ],
  "shippingAddress": {
    "street": "123 Main St",
    "city": "Seattle",
    "state": "WA",
    "zip": "98101"
  },
  "_rid": "AbCdEfGhIjKl==",
  "_self": "dbs/myDatabase/colls/orders/docs/order-8f3a2c1b",
  "_etag": "\"00000000-0000-0000-1234-abcdef012345\"",
  "_attachments": "attachments/",
  "_ts": 1742393400
}

The id field is required and must be unique within a logical partition (a partition key value). The combination of id and partition key value uniquely identifies an item in the entire container. System properties prefixed with _ are managed by Cosmos DB: _ts is the Unix timestamp of the last modification, and _etag is used for optimistic concurrency control.

Tip

The id field should be a globally unique value — a GUID or a domain-specific unique identifier like an order number. If you use sequential integers as IDs, items with the same partition key will still work, but if two logical partitions happen to produce the same ID, you will have unexpected conflicts. Use UUIDs or prefixed IDs like “order-8f3a2c1b” to ensure global uniqueness.

Partition keys: how data distribution works

The partition key is the field in your documents that Cosmos DB uses to distribute data across physical partitions. When you write an item, Cosmos DB hashes the partition key value and routes the item to the appropriate physical partition. Items with the same partition key value are always stored in the same logical partition and on the same physical partition.

Cosmos DB divides container storage and throughput across physical partitions. Each physical partition holds up to 50 GB and serves up to 10,000 RU/s. As your container grows beyond these limits, Cosmos DB splits physical partitions automatically. This split is transparent — but the quality of your partition key determines whether the split distributes load evenly or creates new hot partitions.

A good partition key has:

  • High cardinality — many distinct values. A partition key with 10 possible values can only spread load across 10 logical partitions. A partition key with millions of distinct values can spread load across many partitions.
  • Even distribution — roughly equal data volume and request rate across all key values. An even distribution means no single partition gets more than its share of traffic.
  • Alignment with query patterns — if most queries filter by a specific field, using that field as the partition key allows Cosmos DB to satisfy the query from a single partition (a “single-partition query”), which is cheaper in RUs and faster.

The hot partition problem

A hot partition occurs when too much read or write traffic concentrates on a single partition key value. Throughput is distributed evenly across all partitions, so if 80% of your writes go to one partition key, that partition uses 80% of the container’s RU budget while the other partitions sit mostly idle. When that hot partition exhausts its share of RUs, requests are rate-limited (HTTP 429 responses) even if the overall container throughput looks underutilized.

Common hot partition patterns to avoid:

  • Partition key on a low-cardinality field — using status as the partition key when 95% of items have status “active”.
  • Partition key on a date without bucketing — using date when all current writes are for today’s date.
  • Partition key on a tenant ID in a multi-tenant system — if one tenant generates 10x the traffic of all others, their partition is a hot partition.
Container typeRecommended partition keyWhy
Orders for an e-commerce site/customerIdHigh cardinality, queries filter by customer
User sessions/sessionIdEach session is independent, very high cardinality
IoT telemetry/deviceIdQueries filtered by device, traffic distributed across devices
Product catalog/categoryIdQueries browse by category; reasonable cardinality
Multi-tenant SaaS data/tenantIdGood isolation per tenant; monitor for hot tenants

TTL, indexing policy, and container settings

Cosmos DB supports automatic deletion of items via TTL (Time To Live). Set a default TTL on the container and items will be deleted automatically after that many seconds. Items can also override the container TTL with their own ttl field.

# Enable TTL on a container (delete items after 7 days by default)
az cosmosdb sql container update \
  --resource-group myResourceGroup \
  --account-name mycosmosaccount \
  --database-name myDatabase \
  --name orders \
  --ttl 604800
{
  "id": "temp-session-9f1a",
  "userId": "user-4421",
  "data": "...",
  "ttl": 3600
}

TTL is useful for session data, rate-limiting counters, temporary tokens, and any data with a natural expiry. Deleted items consume RUs for deletion proportional to the item size.

The indexing policy determines which fields Cosmos DB indexes. By default, all fields are indexed, which enables flexible queries but increases write RU cost and storage. For write-heavy containers, reduce indexing by specifying only the paths you actually query:

{
  "indexingMode": "consistent",
  "includedPaths": [
    { "path": "/customerId/?" },
    { "path": "/orderDate/?" },
    { "path": "/status/?" }
  ],
  "excludedPaths": [
    { "path": "/*" }
  ]
}

This indexing policy indexes only customerId, orderDate, and status, and excludes everything else (including large nested arrays that would otherwise inflate the index).

Common mistakes

  1. Choosing a partition key that reflects the current data volume rather than future scale. A key that looks fine with 10,000 items may create hot partitions at 10 million items. Think about the access pattern and distribution at 100x your current data volume before finalizing the partition key.
  2. Using the same value for both id and partition key. This pattern (known as a single-document partition) is valid for some use cases like key-value lookups, but it means no item in the container shares a partition key with any other item. This prevents efficient range queries and makes cross-item transactions impossible. Use a partition key that groups related items together.
  3. Leaving the default indexing policy when writes are the bottleneck. The default “index everything” policy doubles or triples the RU cost of writes compared to a targeted index. If your write cost is high and you know which fields you query, configure a selective indexing policy immediately — it cannot be applied retroactively without re-indexing all data (which happens automatically but takes time).

Frequently asked questions

Can I change the partition key after creating a container?

No. The partition key is set at container creation time and cannot be changed. If you choose the wrong partition key, you must create a new container with the correct key and migrate your data. This is why partition key selection deserves careful thought before writing any data to the container.

What is the maximum size of a single logical partition in Cosmos DB?

Each logical partition can hold up to 20 GB of data and consume up to 10,000 RU/s (Request Units per second) of throughput. If a single partition key value accumulates more than 20 GB of items, the container will start rejecting writes for that key with a 413 error.

What is a synthetic partition key?

A synthetic partition key is a field added to your documents that combines multiple real attributes to create better distribution. For example, if your data naturally clusters around a small set of dates, you might create a field that concatenates date plus a hash or bucket number — so "2026-03-19" becomes "2026-03-19_bucket3". This spreads data across more partition key values.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.