Cosmos Db
Azure Cosmos DB globally distributed multi-model database service
You are an expert in Azure Cosmos DB for building globally distributed, low-latency applications with multi-model data access. ## Key Points - **Ignoring the RU charge on every response** -- Every SDK response includes the RU cost. Not monitoring it means you cannot explain your bill, predict throttling, or optimize expensive queries. - **Account**: Top-level resource containing databases - **Database**: Namespace for containers, optional shared throughput - **Container**: Schema-agnostic collection of items, the unit of scalability - **Partition Key**: Determines data distribution across physical partitions; critical for performance - **Request Units (RU/s)**: Normalized cost metric for all database operations - **Consistency Levels**: Strong, Bounded Staleness, Session (default), Consistent Prefix, Eventual 2. **Use point reads over queries**: A point read by `id` + partition key costs exactly 1 RU for a 1KB item. SQL queries consume more. 3. **Model data for your access patterns**: Denormalize and embed related data in the same document when read together. Avoid cross-partition joins. 4. **Use autoscale throughput**: Autoscale adjusts between 10% and 100% of max RU/s. Better than manual provisioning for variable workloads. 5. **Enable analytical store for analytics**: Use Azure Synapse Link to run analytical queries without impacting transactional performance. 6. **Set appropriate TTL**: Automatically expire old data to save costs.
skilldb get azure-services-skills/Cosmos DbFull skill: 320 linesAzure Cosmos DB — Cloud Services
You are an expert in Azure Cosmos DB for building globally distributed, low-latency applications with multi-model data access.
Core Philosophy
Cosmos DB is a globally distributed database built around one critical design decision: the partition key. Everything -- performance, cost, scalability, and even which queries are possible -- flows from this choice. A good partition key has high cardinality, distributes both storage and throughput evenly, and appears in the WHERE clause of nearly every query. Choosing a partition key is not a schema detail to figure out later; it is the most consequential data modeling decision in your entire application.
Request Units are the currency of Cosmos DB. Every operation -- reads, writes, queries, stored procedures -- has a cost measured in RUs. Understanding and monitoring RU consumption is not optional; it is how you control your bill and prevent throttling. Point reads (lookup by id + partition key) cost exactly 1 RU for a 1 KB item and should be the primary access pattern. Cross-partition queries fan out to every partition and should be avoided in hot paths. If a query costs more RUs than expected, the fix is usually in the data model, not the query.
Model data for your access patterns, not for normalized purity. Cosmos DB does not support server-side joins. If you need a user's profile and their recent orders in one API call, embed the orders in the user document or use a single-partition design where both live under the same partition key. Denormalization is the expected approach, and the change feed provides a mechanism to keep denormalized copies consistent.
Anti-Patterns
- Choosing a low-cardinality partition key -- Keys like
status(3 values) orcountry(200 values) create hot partitions that throttle under load. Use high-cardinality keys liketenantId,userId, ordeviceId. - Running cross-partition queries in hot paths -- Queries without a partition key filter fan out to all partitions, consuming excessive RUs and adding latency. Always include the partition key in WHERE clauses for operational queries.
- Ignoring the RU charge on every response -- Every SDK response includes the RU cost. Not monitoring it means you cannot explain your bill, predict throttling, or optimize expensive queries.
- Storing large documents when small ones would suffice -- Keep documents under 100 KB for best performance. Large payloads inflate RU costs for every read and write. Store big blobs in Blob Storage and reference them by URL.
- Using the Cosmos DB emulator for capacity planning -- The emulator does not replicate partition distribution, network latency, or throttling behavior. Always validate performance against a real Cosmos DB instance.
Overview
Azure Cosmos DB is a fully managed NoSQL and relational database service with guaranteed single-digit millisecond response times, automatic and instant scalability, and SLA-backed availability. It supports multiple APIs: NoSQL (document/JSON), MongoDB, Cassandra, Gremlin (graph), Table, and PostgreSQL.
Key concepts:
- Account: Top-level resource containing databases
- Database: Namespace for containers, optional shared throughput
- Container: Schema-agnostic collection of items, the unit of scalability
- Partition Key: Determines data distribution across physical partitions; critical for performance
- Request Units (RU/s): Normalized cost metric for all database operations
- Consistency Levels: Strong, Bounded Staleness, Session (default), Consistent Prefix, Eventual
Setup & Configuration
Create a Cosmos DB account with Azure CLI
# Create a Cosmos DB account (NoSQL API)
az cosmosdb create \
--name mycosmosaccount \
--resource-group myResourceGroup \
--default-consistency-level Session \
--locations regionName=eastus failoverPriority=0 isZoneRedundant=true \
--locations regionName=westus failoverPriority=1 isZoneRedundant=false \
--enable-automatic-failover true
# Create a database
az cosmosdb sql database create \
--account-name mycosmosaccount \
--resource-group myResourceGroup \
--name myDatabase
# Create a container with partition key and autoscale
az cosmosdb sql container create \
--account-name mycosmosaccount \
--resource-group myResourceGroup \
--database-name myDatabase \
--name myContainer \
--partition-key-path "/tenantId" \
--max-throughput 4000
# Get the connection string
az cosmosdb keys list \
--name mycosmosaccount \
--resource-group myResourceGroup \
--type connection-strings
Enable serverless mode (for dev/test or sporadic workloads)
az cosmosdb create \
--name myserverlessaccount \
--resource-group myResourceGroup \
--capabilities EnableServerless \
--default-consistency-level Session \
--locations regionName=eastus failoverPriority=0
Core Patterns
CRUD operations with the JavaScript SDK
import { CosmosClient, Database, Container } from "@azure/cosmos";
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING!);
const database: Database = client.database("myDatabase");
const container: Container = database.container("myContainer");
// Create an item
async function createItem(item: { id: string; tenantId: string; [key: string]: unknown }) {
const { resource } = await container.items.create(item);
return resource;
}
// Read an item (point read — most efficient, 1 RU for 1KB item)
async function readItem(id: string, tenantId: string) {
const { resource } = await container.item(id, tenantId).read();
return resource;
}
// Upsert an item
async function upsertItem(item: { id: string; tenantId: string; [key: string]: unknown }) {
const { resource } = await container.items.upsert(item);
return resource;
}
// Replace an item
async function replaceItem(id: string, tenantId: string, updated: Record<string, unknown>) {
const { resource } = await container.item(id, tenantId).replace(updated);
return resource;
}
// Delete an item
async function deleteItem(id: string, tenantId: string) {
await container.item(id, tenantId).delete();
}
Querying with SQL-like syntax
// Simple query
async function queryByStatus(tenantId: string, status: string) {
const querySpec = {
query: "SELECT * FROM c WHERE c.tenantId = @tenantId AND c.status = @status ORDER BY c.createdAt DESC",
parameters: [
{ name: "@tenantId", value: tenantId },
{ name: "@status", value: status },
],
};
const { resources, requestCharge } = await container.items
.query(querySpec, { partitionKey: tenantId })
.fetchAll();
console.log(`Query consumed ${requestCharge} RUs`);
return resources;
}
// Paginated query
async function queryPaginated(tenantId: string, pageSize: number, continuationToken?: string) {
const querySpec = {
query: "SELECT * FROM c WHERE c.tenantId = @tenantId ORDER BY c.createdAt DESC",
parameters: [{ name: "@tenantId", value: tenantId }],
};
const iterator = container.items.query(querySpec, {
maxItemCount: pageSize,
continuationToken,
partitionKey: tenantId,
});
const { resources, continuationToken: nextToken, requestCharge } = await iterator.fetchNext();
return { items: resources, continuationToken: nextToken, requestCharge };
}
// Cross-partition query (more expensive — avoid if possible)
async function searchAcrossPartitions(keyword: string) {
const querySpec = {
query: "SELECT * FROM c WHERE CONTAINS(LOWER(c.name), @keyword)",
parameters: [{ name: "@keyword", value: keyword.toLowerCase() }],
};
const { resources } = await container.items.query(querySpec).fetchAll();
return resources;
}
Change Feed processing
import { ChangeFeedStartFrom } from "@azure/cosmos";
async function processChangeFeed() {
const iterator = container.items.getChangeFeedIterator({
changeFeedStartFrom: ChangeFeedStartFrom.Now(),
});
while (iterator.hasMoreResults) {
const response = await iterator.readNext();
if (response.statusCode === 304) {
// No new changes; wait and retry
await new Promise((resolve) => setTimeout(resolve, 5000));
continue;
}
for (const item of response.result) {
console.log("Changed item:", item.id);
// Process the changed item
}
}
}
Transactional batch (within a single partition)
async function transferCredits(tenantId: string, fromId: string, toId: string, amount: number) {
const batch = container.items.batch(tenantId);
// Read both items, patch both in a single atomic transaction
const operations = [
{ operationType: "Patch" as const, id: fromId, resourceBody: { operations: [{ op: "incr" as const, path: "/credits", value: -amount }] } },
{ operationType: "Patch" as const, id: toId, resourceBody: { operations: [{ op: "incr" as const, path: "/credits", value: amount }] } },
];
const response = await container.items.batch(operations, tenantId);
if (response.result) {
console.log("Transaction completed successfully");
}
}
Using hierarchical partition keys
# Create container with hierarchical (sub-partitioned) keys
az cosmosdb sql container create \
--account-name mycosmosaccount \
--resource-group myResourceGroup \
--database-name myDatabase \
--name eventsContainer \
--partition-key-path "/tenantId" "/year" "/month"
// Query with hierarchical partition key
const { resources } = await container.items
.query(
"SELECT * FROM c WHERE c.tenantId = @tid AND c.year = @y AND c.month = @m",
{ partitionKey: ["tenant-123", 2025, 3] }
)
.fetchAll();
Best Practices
-
Choose an optimal partition key: It should have high cardinality, evenly distribute storage and throughput, and be used in most queries as a filter. Common choices:
tenantId,userId,deviceId. -
Use point reads over queries: A point read by
id+ partition key costs exactly 1 RU for a 1KB item. SQL queries consume more. -
Model data for your access patterns: Denormalize and embed related data in the same document when read together. Avoid cross-partition joins.
-
Use autoscale throughput: Autoscale adjusts between 10% and 100% of max RU/s. Better than manual provisioning for variable workloads.
-
Enable analytical store for analytics: Use Azure Synapse Link to run analytical queries without impacting transactional performance.
az cosmosdb sql container create \ --account-name mycosmosaccount \ --resource-group myResourceGroup \ --database-name myDatabase \ --name analyticsContainer \ --partition-key-path "/category" \ --analytical-storage-ttl -1 \ --max-throughput 4000 -
Set appropriate TTL: Automatically expire old data to save costs.
// Enable TTL at container level, then set per-item const item = { id: "session-123", tenantId: "tenant-1", ttl: 3600, // expires in 1 hour }; -
Use indexing policies wisely: Exclude paths you never query on to save RU cost on writes.
{ "indexingMode": "consistent", "includedPaths": [{ "path": "/status/?" }, { "path": "/createdAt/?" }], "excludedPaths": [{ "path": "/largePayload/*" }, { "path": "/*" }], "compositeIndexes": [ [ { "path": "/status", "order": "ascending" }, { "path": "/createdAt", "order": "descending" } ] ] } -
Use managed identity instead of connection strings:
az cosmosdb sql role assignment create \ --account-name mycosmosaccount \ --resource-group myResourceGroup \ --role-definition-name "Cosmos DB Built-in Data Contributor" \ --principal-id <managed-identity-object-id> \ --scope "/"
Common Pitfalls
-
Hot partitions: A partition key with low cardinality (e.g.,
statuswith 3 values) creates hot spots. Monitor partition key distribution with Azure Monitor metrics. -
Unbounded cross-partition queries: Queries without a partition key filter fan out to all partitions. This is expensive and slow at scale. Always include the partition key in WHERE clauses.
-
Over-provisioning RU/s: Start with autoscale and monitor actual usage. Many workloads need far less than initially estimated.
-
Large documents: Maximum item size is 2MB. Keep documents under 100KB for best performance. Store large blobs in Blob Storage and reference by URL.
-
Missing composite indexes: ORDER BY on multiple fields or queries with multiple range filters require composite indexes. Without them, the query fails or scans inefficiently.
-
Ignoring request charge headers: Every SDK response includes the RU charge. Monitor it to understand cost drivers and optimize expensive queries.
-
Using the emulator for production capacity planning: The emulator does not replicate partition behavior or network latency. Always validate performance against an actual Cosmos DB instance.
Install this skill directly: skilldb add azure-services-skills
Related Skills
App Service
Azure App Service for hosting web apps, REST APIs, and mobile backends
Azure Functions
Azure Functions serverless compute for event-driven applications
Blob Storage
Azure Blob Storage for scalable object storage and data lake scenarios
Adversarial Code Review
Adversarial implementation review methodology that validates code completeness against requirements with fresh objectivity. Uses a coach-player dialectical loop to catch real gaps in security, logic, and data flow.
API Design Testing
Design, document, and test APIs following RESTful principles, consistent
Architecture
Design software systems with sound architecture — choosing patterns, defining boundaries,