Cosmos DB Partition‑Key Design for Multi‑Tenant SaaS with Point Reads
Guidance on choosing a tenant‑aware partition key, provisioning autoscale throughput, and configuring multi‑region writes to meet sub‑10ms point reads while isolating tenant data.
12 Sept 2025, 04:42 UTC

Problem
You operate a multi‑tenant SaaS application that must serve point reads by tenant identifier in under 10 ms, keep tenant data logically isolated, control cost with predictable provisioned throughput, and remain available across regions with near‑zero RPO for writes. Choosing an inappropriate partition key can cause hot partitions, throttling, or cross‑tenant data leaks.
Takeaway
Use a single Cosmos DB database and container with a partition key derived from the tenant ID (e.g., /tenantId or a synthetic key /tenantId/dateBucket). Enable autoscale throughput, multi‑region writes with two‑to‑three preferred regions, and enforce isolation through Azure RBAC, private endpoints, and encryption. Monitor RU consumption, throttling, p99 latency, and storage per physical partition to verify balance.
Requirements
- Sub‑10ms point reads by
tenantId. - Tenant isolation at the data‑access layer (no cross‑tenant queries).
- Predictable cost via provisioned throughput (autoscale preferred).
- Multi‑region availability with write RPO ≈ 0.
- Item size bounded (≤ 2 KB typical) to stay within service limits.
Smallest Suitable Design
Topology
One Cosmos DB account → one database → one container. The container uses a partition key that isolates each tenant to its own logical partition key range.
Partition Key
/* Example: simple tenant‑id key */
/tenantId
/* Example: synthetic key to spread writes across hours */
/tenantId/%Y-%m-%d-%H
The key must be part of every item’s JSON (e.g., {"tenantId":"acme", "id":"order-123", …}). With a well‑distributed tenantId, each tenant maps to a distinct logical partition, enabling direct point reads (ReadItemAsync) that hit a single physical partition.
Throughput
Enable autoscale on the container with a maximum RU/s that accommodates peak aggregate traffic (e.g., 20 000 RU/s). Autoscale automatically scales down to 10 % of the max during low usage, providing cost predictability while absorbing bursts.
Multi‑Region Writes
Turn on EnableMultipleWriteLocations and select two or three preferred regions (e.g., WestUS2, EastUS, NorthEurope). This provides low‑latency writes locally and automatic failover if a region becomes unhealthy. Cosmos DB uses last‑write‑wins conflict resolution based on timestamps; the application must be tolerant of occasional conflicts.
Trust and Data Boundaries
- Azure RBAC: Assign least‑privilege roles (e.g.,
Cosmos DB Built‑In Data Contributor) to service principals that only need read/write on the container. - Network Isolation: Create a private endpoint for the Cosmos DB account and disable public network access. Verify with
az cosmosdb show --name <account> --resource-group <rg> --query "privateEndpointConnections". - Encryption: Service‑side encryption at rest is enabled by default; customer‑managed keys can be added if required.
- Logical Separation: Because every item includes the tenantId in the partition key, a query that omits the partition key filter will be rejected (or will incur a costly cross‑partition scan). Enforce this in the data‑access layer by always supplying
partitionKey.
Operational Checks
- Monitor Request Unit (RU) consumption and 429 throttling rate via Azure Monitor metrics (
TotalRequestUnits,StatusCode429Count). - Track p99 read latency (
Latencymetric) to ensure it stays under the 10 ms SLA. - Check storage per physical partition (
UsedStorageper partition) to detect skew; a single partition should not exceed ~20 GB (the practical split threshold). - If using Change Feed for downstream sync, observe Change Feed Processor lag (
ChangeFeedLag) to catch processing delays. - Validate replication status:
az cosmosdb show --name <account> --resource-group <rg> --query "enableMultipleWriteLocations"andpreferredLocations.
Failure Modes
- Hot partitions: Skewed tenant traffic (e.g., one tenant generates 80 % of writes) can cause a single physical partition to exceed its RU/s limit, leading to 429 responses. Mitigation: use a synthetic partition key (
/tenantId/dateBucket) or redistribute load via sharding at the application level. - Item size limits: Items > 2 KB (or > 1 MB for transactional batch) cause request failures. Enforce size validation in the API layer.
- Regional failover latency: During a regional outage, write latency may increase as traffic redirects to a secondary region. Monitor
WriteLatencyand failover metrics. - Partition split storms: Rapid data growth can trigger frequent splits, briefly increasing RU consumption and latency. Autoscale helps absorb spikes; review split frequency via diagnostic logs.
When the Design Must Change
- Need for cross‑tenant analytical queries (e.g., multi‑tenant reporting) → consider a separate analytical store or a container with a different partition key (e.g.,
/type) and use Azure Synapse Link. - Requirement for multi‑document ACID transactions spanning tenants → move to separate containers per tenant with provisioned throughput or use a transactional batch limited to a single partition.
- Unbounded item growth (e.g., large blobs) → store large payloads in Azure Blob Storage and keep only metadata in Cosmos DB.
- Per‑tenant throughput isolation to avoid noisy‑neighbor effects → provision dedicated containers or databases per tenant with manual RU/s or autoscale per container.
Practical Verification Steps
- Deploy the container with the chosen partition key and autoscale max RU/s via ARM template or Azure CLI:
az cosmosdb sql container create \
--account-name <account> \
--resource-group <rg> \
--database-name <db> \
--name <container> \
--partition-key-path /tenantId \
--throughput 4000 \
--enable-autoscale true \
--max-throughput 20000
- Enable multi‑region writes and set preferred regions:
az cosmosdb update \
--name <account> \
--resource-group <rg> \
--enable-multiple-write-locations true \
--locations regionName=WestUS2 failoverPriority=0 \
--locations regionName=EastUS failoverPriority=1 \
--locations regionName=NorthEurope failoverPriority=2
- Create a private endpoint:
az network private-endpoint create \
--name <pe-name> \
--resource-group <rg> \
--vnet-name <vnet> \
--subnet <subnet> \
--private-connection-resource-id $(az cosmosdb show --name <account> --resource-group <rg> --query "id" -o tsv) \
--group-id Sql \
--connection-name <connection-name>
- After deploying a representative workload, check metrics in Azure Monitor:
- Look for
TotalRequestUnitsstaying below the autoscale max. - Verify
StatusCode429Countis near zero. - Confirm
Latencyp99 ≤ 10 ms. - Ensure
UsedStorageper physical partition is balanced (no single partition > 80 % of total).
Limitations
The design assumes tenant identifiers are uniformly distributed and that item sizes stay within the 2 KB limit. If a tenant’s data grows beyond a single physical partition’s capacity (~20 GB), the partition will split, which may temporarily increase RU usage. Synthetic keys can mitigate hot‑spot risk but add complexity to queries that must include the synthetic suffix.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.