Verified against Microsoft Learn on September 29, 2026. The rules on this sheet come from the Azure Cosmos DB modeling, partitioning, synthetic key, hierarchical partition key, and global secondary index documentation.
Data modeling · Partitioning · Synthetic keys · Hierarchical partition keys · Global secondary indexes
Embed or reference#
Is the related data contained by its parent, with a one-to-few relationship?
Yes Continue to step 2. Embedding is the leading option.
No: one-to-many or many-to-many Reference. Store related items separately and link them by id.
Can the embedded data grow without bound, such as comments on a post?
Bounded Continue to step 3.
Unbounded Reference. Store each child as its own item with a parent id, such as
postId, so the parent item stays small.Does the related data change frequently, or change independently of the parent?
Changes rarely Continue to step 4.
Changes often Reference. Keeping the changing entity separate avoids updating many parent items; normalizing typically gives better write performance.
Is the data queried together with its parent most of the time?
Yes Embed. One point read returns everything, with fewer round trips and updates.
No Reference, or use a hybrid: reference the entity and copy only the few fields every read displays.
| Embed when | Reference when |
|---|---|
| The relationship is contained | The relationship is one-to-many |
| The relationship is one-to-few | The relationship is many-to-many |
| The data changes infrequently | The related data changes frequently |
| The data is bounded | The referenced data could be unbounded |
| The data is queried together | Related entities are large or updated independently |
Put the reference on the side that grows. When a publisher has an unbounded number of books, each book stores pub-id; the publisher item stays small.
- Hybrid model: reference an author from a book, and also copy the author name and thumbnail into the book so a book list needs one read per book.
- Keep copies in sync with the change feed. A change feed processor can update denormalized copies, build read-optimized projections, or populate normalized containers for analytics.
- Queries join within one item only. A JOIN in Azure Cosmos DB works between an item and its own nested arrays, so model the data for each access pattern instead of joining across items.
Choose a partition key#
Does one property have high cardinality and appear in most query filters?
Yes Use it, such as
/customerId. Most queries become single-partition queries and writes spread evenly.No Continue to step 2.
Is the workload almost entirely point reads and writes by id?
Yes
/idworks well: excellent write distribution and low-RU point reads.No Continue to step 3.
Can one value exceed 20 GB, or do queries naturally drill down, such as tenant, then user, then session?
Yes, with a high-cardinality first level Use a hierarchical partition key, such as
/tenantId,/userId,/sessionId.Yes, with a low-cardinality first level Use a synthetic key instead.
Is the workload write-heavy on a few values, such as every order for today's date?
Yes Use a synthetic key with a suffix to spread the writes (see the table below).
Several independent query patterns Evaluate synthetic and hierarchical keys first, then consider a global secondary index keyed for the second pattern.
The partition key is permanent
You choose the partition key when you create a container, and an item's partition key value is immutable. To change the key, create a new container with the new key and move the data, for example with a container copy job.
What a good partition key looks like#
- High cardinality: a wide range of possible values.
- Even spread: request units and storage spread evenly across logical partitions.
- Query alignment: for large read-heavy containers, a property that most queries use as an equality filter.
- String values: use string values, and convert numbers to strings when they might exceed double precision.
- Within the limits: each logical partition holds up to 20 GB and serves up to 10,000 RU/s.
Anti-patterns and their fixes#
| Anti-pattern | What happens | Better choice |
|---|---|---|
/id on a workload with filtered queries | Every filtered query becomes a cross-partition query | A property that matches the filters |
| Low cardinality: status, type, country | A few hot partitions hit the 20 GB and 10,000 RU/s limits | A higher-cardinality property or a synthetic key |
| High cardinality with no query alignment, such as a random GUID | Writes spread well, but most reads fan out to every partition | A key that also appears in common filters |
| Every write lands on one value, such as today’s date | One hot partition throttles the whole workload | A synthetic key with a suffix |
Synthetic partition keys#
| Strategy | Example value | Reads and writes |
|---|---|---|
| Concatenate properties | abc-123-2018 from deviceId abc-123 and date 2018 | One key serves queries that supply both values |
| Random suffix | 2018-08-09.1 through 2018-08-09.400 | Best write spread; reading one item means checking every suffix |
| Precalculated suffix | The date plus a hash of the VIN, from 1 to 400 | Spreads writes, and a read recomputes the suffix from the VIN |
Choose a precalculated suffix when you need both an even write spread and fast point reads by a known property.
Hierarchical partition keys#
- Up to three levels, such as
/tenantId,/userId,/sessionId, set when the container is created. Existing containers move to a new container, for example with a container copy job. - Every level needs high cardinality, especially the first. A low-cardinality first level sends all ingestion to one physical partition until it reaches 50 GB and splits.
- Scale beyond 20 GB: one first-level value can span many physical partitions. Add the item id or a GUID as the last level to guarantee it.
- Supported SDKs: .NET v3, Java v4, and Python, plus the preview JavaScript SDK. API for NoSQL accounts only.
| Query filter (key: tenantId, userId, sessionId) | Routing |
|---|---|
| tenantId, userId, and sessionId | One logical and physical partition |
| tenantId and userId | Targeted subset of partitions |
| tenantId only | Targeted subset of partitions |
| userId only, or sessionId only | Fan-out to every physical partition |
Queries route efficiently when they include the key from the first level down. Filtering on a middle or last level alone fans out.
Global secondary indexes#
- A read-only container that stores the source container’s data with a different partition key.
- It has its own partition key, indexing policy, throughput, and data model, so each workload gets performance isolation.
- It is kept in sync automatically through the change feed, asynchronously, so the source container’s write latency stays the same.
- It is eventually consistent with the source, and it adds its own storage and RU costs.
- Example: the source container uses
/customerId, and a GSI uses/orderIdfor lookups by order.

