Symptoms
- SDK exceptions with status 429 and substatus such as 3200 (RU budget exceeded), with
x-ms-retry-after-msheaders. - Latency climbs because SDKs retry automatically; eventually requests fail with timeouts.
- The Cosmos DB account looks "nowhere near" its total RU/s.
Step 1: is it total RU or one partition? (5 minutes)
Portal: Insights > Throughput and Normalized RU Consumption by PartitionKeyRangeId.
ACC=$(az cosmosdb show -g rg-data -n cosmos-prod --query id -o tsv)
az monitor metrics list --resource $ACC --metric NormalizedRUConsumption \
--dimension CollectionName PartitionKeyRangeId --interval PT1M --aggregation Maximum --offset 1h -o table
az monitor metrics list --resource $ACC --metric TotalRequests \
--filter "StatusCode eq '429'" --interval PT1M --aggregation Count --offset 1h -o table
| Normalized RU pattern | Diagnosis |
|---|---|
| All partition ranges near 100% | Not enough RU/s overall |
| One or two ranges at 100%, others low | Hot partition: a key gets most of the traffic |
| Spikes to 100% only during one job | A bulk import, a cross-partition query or a change-feed rebuild |
A few percent of 429s with normalized RU near 100% is normal behaviour for a well-used autoscale container. A sustained, high 429 rate with user-facing failures is the incident.
Step 2: add headroom (minutes)
Manual throughput:
az cosmosdb sql container throughput update -g rg-data -a cosmos-prod -d appdb -n orders --throughput 20000
Autoscale (raise the max):
az cosmosdb sql container throughput update -g rg-data -a cosmos-prod -d appdb -n orders --max-throughput 40000
Warning
Increasing RU/s spreads the new capacity evenly across physical partitions. If one partition is hot, doubling RU/s only doubles that partition's share. You may need far more total RU/s than expected, so fix the key soon.
Raising RU/s beyond what current partitions can hold can trigger a partition split that takes time; the change is accepted but completes asynchronously.
Pause or slow the offending batch job if one is running. Most bulk tools accept a max-RU or degree-of-parallelism setting.
Step 3: find the expensive operations
Enable diagnostic settings (resource-specific tables) if they are not on. Then:
CDBDataPlaneRequests
| where TimeGenerated > ago(1h)
| summarize requests = count(), throttled = countif(StatusCode == 429), ru = sum(RequestCharge)
by OperationName, CollectionName
| order by ru desc
CDBPartitionKeyRUConsumption
| where TimeGenerated > ago(1h)
| summarize ru = sum(RequestCharge) by PartitionKey, CollectionName
| top 10 by ru
Query text with RU charge (needs query text logging enabled; be careful with sensitive data):
CDBQueryRuntimeStatistics
| where TimeGenerated > ago(1h)
| project TimeGenerated, CollectionName, QueryText, ActivityId
| take 50
Step 4: fix the cause
| Cause | Fix |
|---|---|
| Partition key with low cardinality or a "celebrity" value (tenant, date) | Synthetic key (tenantId_bucket), hierarchical partition keys, or a new container and data migration |
| Cross-partition queries in a hot path | Include the partition key in the WHERE clause; read by id + partition key (point read ~1 RU per KB) |
| Default "index everything" policy on write-heavy containers | Exclude paths you never filter on ("excludedPaths": [{"path": "/payload/*"}]) |
| Large documents updated often | Patch API or split hot fields into a smaller document |
| Retry storms | SDK defaults are fine; do not wrap them in another retry loop without backoff |
Partition keys cannot be changed in place. Use the container copy feature or a change-feed based migration to a new container, then switch the app.
Step 5: confirm
- 429 rate back to a low, steady level; no user-facing failures.
- Normalized RU per partition evenly distributed after any key change.
- p99 latency back to baseline in the SDK diagnostics or Application Insights dependency telemetry.
Prevent it next time
- Alert on 429 rate (not just count) and on normalized RU above 90% for 15 minutes.
- Choose partition keys from the access pattern before go-live, and test with production-like key distribution.
- Use autoscale for spiky workloads, with a max you have reviewed for cost.
- Run bulk loads with the bulk executor and a throughput cap, outside business peaks.