| name | azure-cosmosdb |
| description | Azure Cosmos DB partition keys, consistency levels, change feed, SDK patterns |
| when-to-use | When working with Azure Cosmos DB |
| user-invocable | false |
| paths | ["**/cosmos*","**/azure*"] |
| effort | medium |
Core Principle
Choose partition key wisely, design for your access patterns, understand consistency tradeoffs.
Cosmos DB distributes data across partitions. Your partition key choice determines scalability, performance, and cost. Design for even distribution and query efficiency.
Cosmos DB APIs
| API | Use Case |
|---|
| NoSQL (Core) | Document database, most flexible |
| MongoDB | MongoDB wire protocol compatible |
| PostgreSQL | Distributed PostgreSQL (Citus) |
| Apache Cassandra | Wide-column store |
| Apache Gremlin | Graph database |
| Table | Key-value (Azure Table Storage compatible) |
This skill focuses on NoSQL (Core) API - the most common choice.
Key Concepts
| Concept | Description |
|---|
| Container | Collection of items (like a table) |
| Item | Single document/record (JSON) |
| Partition Key | Determines data distribution |
| Logical Partition | Items with same partition key |
| Physical Partition | Storage unit (max 50GB, 10K RU/s) |
| RU (Request Unit) | Throughput currency |
Partition Key Design
Good Partition Keys
{ "id": "order-123", "userId": "user-456", ... }
{ "id": "doc-1", "tenantId": "tenant-abc", ... }
{ "id": "reading-1", "deviceId": "device-789", ... }
{ "id": "log-1", "partitionKey": "2024-01-15_errors", ... }
Hierarchical Partition Keys
{
"id": "order-123",
"tenantId": "acme-corp",
"userId": "user-456",
"items": [...]
}
Bad Partition Keys
{ "status": "pending" | "completed" | "cancelled" }
{ "timestamp": "2024-01-15T10:30:00Z" }
SDK Setup (TypeScript)
Install
npm install @azure/cosmos
Initialize Client
import { CosmosClient, Database, Container } from '@azure/cosmos';
const endpoint = process.env.COSMOS_ENDPOINT!;
const key = process.env.COSMOS_KEY!;
const databaseId = process.env.COSMOS_DATABASE!;
const client = new CosmosClient({ endpoint, key });
export const database: Database = client.database(databaseId);
export function getContainer(containerId: string): Container {
return database.container(containerId);
}
Type Definitions
export interface BaseItem {
id: string;
_ts?: number;
_etag?: string;
}
export interface User extends BaseItem {
userId: string;
email: string;
name: string;
createdAt: string;
updatedAt: string;
}
export interface Order extends BaseItem {
userId: string;
orderId: string;
items: OrderItem[];
total: number;
status: 'pending' | 'paid' | 'shipped' | 'delivered';
createdAt: string;
}
export interface OrderItem {
productId: string;
: ;
: ;
: ;
}
CRUD Operations
Create Item
import { getContainer } from './cosmosdb';
import { User } from './types';
const usersContainer = getContainer('users');
async function createUser(data: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> {
const now = new Date().toISOString();
const user: User = {
id: crypto.randomUUID(),
...data,
createdAt: now,
updatedAt: now
};
const { resource } = await usersContainer.items.create(user);
return resource as User;
}
Read Item (Point Read)
async function getUser(userId: string, id: string): Promise<User | null> {
try {
const { resource } = await usersContainer.item(id, userId).read<User>();
return resource || null;
} catch (error: any) {
if (error.code === 404) return null;
throw error;
}
}
async function getUserById(userId: string): Promise<User | null> {
try {
const { resource } = await usersContainer.item(userId, userId).read<User>();
return resource || null;
} catch (error: any) {
if (error.code === 404) ;
error;
}
}
Query Items
async function getUserOrders(userId: string): Promise<Order[]> {
const ordersContainer = getContainer('orders');
const { resources } = await ordersContainer.items
.query<Order>({
query: 'SELECT * FROM c WHERE c.userId = @userId ORDER BY c.createdAt DESC',
parameters: [{ name: '@userId', value: userId }]
})
.fetchAll();
return resources;
}
async function getOrdersByStatus(status: string): Promise<Order[]> {
const ordersContainer = getContainer('orders');
const { resources } = await ordersContainer.items
.query<Order>({
query: 'SELECT * FROM c WHERE c.status = @status',
parameters: [{ name: '@status', value: status }]
})
.fetchAll();
resources;
}
(): <{ : []; ?: }> {
ordersContainer = ();
queryIterator = ordersContainer..<>(
{
: ,
: [{ : , : userId }]
},
{
: pageSize,
continuationToken
}
);
{ resources, : nextToken } = queryIterator.();
{
: resources,
: nextToken
};
}
Update Item
async function updateUser(userId: string, id: string, updates: Partial<User>): Promise<User> {
const existing = await getUser(userId, id);
if (!existing) throw new Error('User not found');
const updated: User = {
...existing,
...updates,
updatedAt: new Date().toISOString()
};
const { resource } = await usersContainer.item(id, userId).replace(updated);
return resource as User;
}
async function patchUser(userId: string, id: string, operations: any[]): Promise<User> {
const { resource } = await usersContainer.item(id, userId).(operations);
resource ;
}
(, , [
{ : , : , : },
{ : , : , : ().() },
{ : , : , : }
]);
Delete Item
async function deleteUser(userId: string, id: string): Promise<void> {
await usersContainer.item(id, userId).delete();
}
Optimistic Concurrency (ETags)
async function updateUserWithETag(
userId: string,
id: string,
updates: Partial<User>,
etag: string
): Promise<User> {
const existing = await getUser(userId, id);
if (!existing) throw new Error('User not found');
const updated: User = {
...existing,
...updates,
updatedAt: new Date().toISOString()
};
try {
const { resource } = await usersContainer.item(id, userId).replace(updated, {
accessCondition: { type: 'IfMatch', condition: etag }
});
return resource as User;
} catch (error: any) {
if (error.code === 412) {
throw new Error('Document was modified by another process');
}
error;
}
}
Consistency Levels
| Level | Guarantees | Latency | Use Case |
|---|
| Strong | Linearizable reads | Highest | Financial, inventory |
| Bounded Staleness | Consistent within bounds | High | Leaderboards, counters |
| Session | Read your writes | Medium | User sessions (default) |
| Consistent Prefix | Ordered reads | Low | Social feeds |
| Eventual | No ordering guarantee | Lowest | Analytics, logs |
Set Consistency Per Request
const { resource } = await usersContainer.item(id, userId).read<User>({
consistencyLevel: 'Strong'
});
const { resources } = await container.items.query(
{ query: 'SELECT * FROM c' },
{ consistencyLevel: 'BoundedStaleness' }
).fetchAll();
Batch Operations
Transactional Batch (Same Partition)
async function createOrderWithItems(userId: string, order: Order, items: any[]): Promise<void> {
const ordersContainer = getContainer('orders');
const operations = [
{ operationType: 'Create' as const, resourceBody: order },
...items.map(item => ({
operationType: 'Create' as const,
resourceBody: { ...item, userId, orderId: order.orderId }
}))
];
const { result } = await ordersContainer.items.batch(operations, userId);
if (result.some(r => r.statusCode >= 400)) {
throw new Error('Batch operation failed');
}
}
Bulk Operations
async function bulkImportUsers(users: User[]): Promise<void> {
const operations = users.map(user => ({
operationType: 'Create' as const,
resourceBody: user,
partitionKey: user.userId
}));
const chunkSize = 100;
for (let i = 0; i < operations.length; i += chunkSize) {
const chunk = operations.slice(i, i + chunkSize);
await usersContainer.items.bulk(chunk);
}
}
Change Feed
Process Changes
import { ChangeFeedStartFrom } from '@azure/cosmos';
async function processChangeFeed(): Promise<void> {
const container = getContainer('orders');
const changeFeedIterator = container.items.changeFeed({
changeFeedStartFrom: ChangeFeedStartFrom.Beginning()
});
while (changeFeedIterator.hasMoreResults) {
const { result: items, statusCode } = await changeFeedIterator.fetchNext();
if (statusCode === 304) {
await sleep(1000);
continue;
}
for (const item of items) {
console.log('Changed item:', item);
}
}
}
Change Feed Processor Pattern
async function startChangeFeedProcessor(): Promise<void> {
const sourceContainer = getContainer('orders');
const leaseContainer = getContainer('leases');
const changeFeedProcessor = sourceContainer.items.changeFeed
.for(item => {
console.log('Processing:', item);
})
.withLeaseContainer(leaseContainer)
.build();
await changeFeedProcessor.start();
}
Python SDK
Install
pip install azure-cosmos
Setup and Operations
import os
from azure.cosmos import CosmosClient, PartitionKey
from azure.cosmos.exceptions import CosmosResourceNotFoundError
from typing import Optional, List
from datetime import datetime
import uuid
endpoint = os.environ['COSMOS_ENDPOINT']
key = os.environ['COSMOS_KEY']
database_name = os.environ['COSMOS_DATABASE']
client = CosmosClient(endpoint, key)
database = client.get_database_client(database_name)
def get_container(container_name: str):
return database.get_container_client(container_name)
users_container = get_container('users')
def create_user(email: str, name: str, user_id: str = None) -> dict:
user_id = user_id or str(uuid.uuid4())
now = datetime.utcnow().isoformat()
user = {
'id': user_id,
'userId': user_id,
'email': email,
'name': name,
'createdAt': now,
'updatedAt': now
}
return users_container.create_item(user)
def get_user(user_id: ) -> []:
:
users_container.read_item(item=user_id, partition_key=user_id)
CosmosResourceNotFoundError:
() -> []:
query =
parameters = [{: , : email_domain}]
(users_container.query_items(
query=query,
parameters=parameters,
enable_cross_partition_query=
))
() -> :
user = get_user(user_id)
user:
ValueError()
user.update(updates)
user[] = datetime.utcnow().isoformat()
users_container.replace_item(item=user_id, body=user)
() -> :
users_container.delete_item(item=user_id, partition_key=user_id)
():
query =
items = users_container.query_items(
query=query,
enable_cross_partition_query=,
max_item_count=page_size,
continuation_token=continuation_token
)
page = items.by_page()
results = ((page))
{
: results,
: page.continuation_token
}
Indexing
Custom Indexing Policy
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{ "path": "/userId/?" },
{ "path": "/status/?" },
{ "path": "/createdAt/?" }
],
"excludedPaths": [
{ "path": "/content/*" },
{ "path": "/_etag/?" }
],
"compositeIndexes": [
[
{ "path": "/userId", "order"
Create Container with Index
await database.containers.createIfNotExists({
id: 'orders',
partitionKey: { paths: ['/userId'] },
indexingPolicy: {
indexingMode: 'consistent',
includedPaths: [
{ path: '/userId/?' },
{ path: '/status/?' },
{ path: '/createdAt/?' }
],
excludedPaths: [
{ path: '/*' }
]
}
});
Throughput Management
Provisioned Throughput
await database.containers.createIfNotExists({
id: 'orders',
partitionKey: { paths: ['/userId'] },
throughput: 1000
});
const container = database.container('orders');
await container.throughput.replace(2000);
Autoscale
await database.containers.createIfNotExists({
id: 'orders',
partitionKey: { paths: ['/userId'] },
maxThroughput: 10000
});
Serverless
await database.containers.createIfNotExists({
id: 'orders',
partitionKey: { paths: ['/userId'] }
});
CLI Quick Reference
az cosmosdb create --name myaccount --resource-group mygroup
az cosmosdb sql database create --account-name myaccount --name mydb --resource-group mygroup
az cosmosdb sql container create \
--account-name myaccount \
--database-name mydb \
--name orders \
--partition-key-path /userId \
--throughput 400
az cosmosdb sql query --account-name myaccount --database-name mydb \
--container-name orders --query "SELECT * FROM c"
az cosmosdb keys list --name myaccount --resource-group mygroup
az cosmosdb keys list --name myaccount --resource-group mygroup --type connection-strings
Cost Optimization
| Strategy | Impact |
|---|
| Right partition key | Avoid hot partitions (wasted RUs) |
| Index only what you query | Reduce write RU cost |
| Use point reads | 1 RU vs 3+ RU for queries |
| Serverless for dev/test | Pay per request |
| Autoscale for production | Scale down during low traffic |
| TTL for temporary data | Auto-delete old items |
Time-to-Live (TTL)
await database.containers.createIfNotExists({
id: 'sessions',
partitionKey: { paths: ['/userId'] },
defaultTtl: 3600
});
const session = {
id: 'session-123',
userId: 'user-456',
ttl: 1800
};
Anti-Patterns
- Bad partition key - Low cardinality causes hot partitions
- Cross-partition queries - Expensive; design for single-partition queries
- Over-indexing - Increases write cost; index only queried paths
- Large items - Max 2MB; store blobs in Azure Blob Storage
- Ignoring RU cost - Monitor and optimize expensive queries
- Strong consistency everywhere - Use Session (default) unless required
- No retry logic - Handle 429 (throttling) with exponential backoff
- Missing TTL - Set TTL for temporary/session data