← Files Azure Cosmos DBARCHIVED FILE
skills/cosmosdb-best-practices/rules/monitoring-ru-consumption.md
7.68 KB · Sep 30, 2026 · 23:01 UTC
---
title: Track RU Consumption
impact: MEDIUM
impactDescription: enables cost optimization
tags: monitoring, ru, metrics, cost
---
## Track RU Consumption
Monitor Request Unit (RU) consumption to optimize costs and identify inefficient operations. Every operation has an RU cost.
**Incorrect (ignoring RU consumption):**
```csharp
// Operations without tracking cost
public async Task<Order> GetOrder(string orderId, string customerId)
{
// No visibility into cost
return await _container.ReadItemAsync<Order>(orderId, new PartitionKey(customerId));
// Is this costing 1 RU or 100 RU? Unknown!
}
```
**Correct (tracking RU at operation level):**
```csharp
public async Task<Order> GetOrder(string orderId, string customerId)
{
var response = await _container.ReadItemAsync<Order>(orderId, new PartitionKey(customerId));
// Log RU consumption
_logger.LogDebug(
"Read order {OrderId}: {RU} RU, {Latency}ms",
orderId,
response.RequestCharge,
response.Diagnostics.GetClientElapsedTime().TotalMilliseconds);
// Track in metrics/telemetry
_telemetry.TrackMetric("CosmosDB.ReadItem.RU", response.RequestCharge,
new Dictionary<string, string>
{
{ "Operation", "ReadItem" },
{ "Container", "orders" }
});
return response.Resource;
}
```
```csharp
// Track RU for queries (can be high!)
public async Task<List<Order>> GetCustomerOrders(string customerId)
{
var query = new QueryDefinition("SELECT * FROM c WHERE c.status = @status")
.WithParameter("@status", "active");
var totalRU = 0.0;
var results = new List<Order>();
var iterator = _container.GetItemQueryIterator<Order>(
query,
requestOptions: new QueryRequestOptions
{
PartitionKey = new PartitionKey(customerId),
PopulateIndexMetrics = true // Also get index metrics
});
while (iterator.HasMoreResults)
{
var response = await iterator.ReadNextAsync();
results.AddRange(response);
totalRU += response.RequestCharge;
// Log per-page RU
_logger.LogDebug(
"Query page: {Count} items, {RU} RU, Index: {IndexMetrics}",
response.Count,
response.RequestCharge,
response.IndexMetrics);
}
// Log total query cost
_logger.LogInformation(
"GetCustomerOrders: {Total} items, {TotalRU} total RU",
results.Count,
totalRU);
// Alert on expensive queries
if (totalRU > 100)
{
_logger.LogWarning(
"Expensive query detected: {TotalRU} RU for {Count} items",
totalRU, results.Count);
}
return results;
}
```
```csharp
// Middleware to track all operations
public class CosmosDbMetricsHandler : RequestHandler
{
private readonly IMetricTracker _metrics;
public override async Task<ResponseMessage> SendAsync(
RequestMessage request,
CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
var response = await base.SendAsync(request, cancellationToken);
sw.Stop();
_metrics.TrackDependency(
"CosmosDB",
request.RequestUri.ToString(),
sw.Elapsed,
response.IsSuccessStatusCode,
new Dictionary<string, string>
{
{ "RU", response.Headers.RequestCharge.ToString() },
{ "StatusCode", response.StatusCode.ToString() }
});
return response;
}
}
// Register handler
var client = new CosmosClient(connectionString, new CosmosClientOptions
{
CustomHandlers = { new CosmosDbMetricsHandler(_metrics) }
});
```
### Node.js / TypeScript (@azure/cosmos v4)
Every `@azure/cosmos` operation exposes `requestCharge` as a top-level numeric property on the response. Capture it on every call — point reads, queries, writes, and bulk operations.
**Incorrect (discarding requestCharge — no visibility into cost):**
```typescript
// ❌ requestCharge available but never captured
const { resource } = await container.item(orderId, userId).read();
return resource;
// Is this costing 1 RU or 100 RU? Unknown!
```
**Correct (capturing requestCharge on reads and writes):**
```typescript
import { Container, FeedResponse } from '@azure/cosmos';
// ✅ Point read — capture requestCharge
export async function getOrder(container: Container, id: string, userId: string) {
const response = await container.item(id, userId).read();
logger.debug({
op: 'ReadItem',
container: container.id,
ru: response.requestCharge,
statusCode: response.statusCode,
activityId: response.activityId,
}, 'cosmos.readItem');
return response.resource;
}
// ✅ Write — create/upsert/replace/patch/delete all expose requestCharge
export async function createOrder(container: Container, order: Order) {
const response = await container.items.create(order);
logger.debug({ op: 'CreateItem', ru: response.requestCharge }, 'cosmos.createItem');
return response.resource;
}
```
**Correct (accumulating RU across query pages — single-page tracking undercounts paged results):**
```typescript
// ✅ Query — sum requestCharge across all pages
export async function getCustomerOrders(container: Container, userId: string) {
const iterator = container.items.query<OrderSummary>({
query: 'SELECT c.id, c.userId, c.status, c.total, c.createdAt FROM c WHERE c.userId = @u ORDER BY c.createdAt DESC',
parameters: [{ name: '@u', value: userId }],
}, { partitionKey: userId });
const results: OrderSummary[] = [];
let totalRU = 0;
while (iterator.hasMoreResults()) {
const page: FeedResponse<OrderSummary> = await iterator.fetchNext();
results.push(...page.resources);
totalRU += page.requestCharge;
}
logger.info({ op: 'Query', container: container.id, count: results.length, totalRU }, 'cosmos.query.total');
if (totalRU > 100) {
logger.warn({ totalRU, count: results.length }, 'cosmos.query.expensive');
}
return results;
}
```
**`requestCharge` API surface in `@azure/cosmos` v4:**
| Operation | Response type | RU property |
|-----------|---------------|-------------|
| `container.item(id, pk).read()` | `ItemResponse<T>` | `response.requestCharge` |
| `container.items.create(doc)` | `ItemResponse<T>` | `response.requestCharge` |
| `container.items.upsert(doc)` | `ItemResponse<T>` | `response.requestCharge` |
| `container.item(id, pk).replace(doc)` | `ItemResponse<T>` | `response.requestCharge` |
| `container.item(id, pk).patch(ops)` | `ItemResponse<T>` | `response.requestCharge` |
| `container.item(id, pk).delete()` | `ItemResponse<T>` | `response.requestCharge` |
| `container.items.query(...).fetchAll()` | `FeedResponse<T>` | `response.requestCharge` |
| `container.items.query(...).fetchNext()` | `FeedResponse<T>` per page | sum across pages |
| `container.items.bulk(ops)` | `OperationResponse[]` | `op.requestCharge` per operation |
Azure Monitor queries for RU analysis:
```kusto
// Top expensive operations
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.DOCUMENTDB"
| summarize TotalRU = sum(requestCharge_s) by OperationName
| order by TotalRU desc
// RU per partition key (detect hot partitions)
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.DOCUMENTDB"
| summarize TotalRU = sum(requestCharge_s) by partitionKey_s
| order by TotalRU desc
```
Reference: [Monitor RU/s](https://learn.microsoft.com/azure/cosmos-db/monitor-request-unit-usage)
SHA-256: 0acb7fc3c191c6e64f50276855e1693cd9a150379cc88b20f3005c71e20ce0d0