Introduction: What is Azure Cosmos DB and Why It Matters
Azure Cosmos DB is Microsoft’s globally distributed, multi-model database service designed for mission-critical applications. It provides turnkey global distribution, elastic scaling of throughput and storage, sub-10ms latencies, and guaranteed high availability, all backed by industry-leading SLAs.
Why It Matters:
- Global Distribution: Deploy databases to any Azure region with the click of a button
- Multi-Model Support: Supports document, key-value, graph, and column-family data models
- Elastic Scalability: Scale throughput and storage on-demand without downtime
- Comprehensive SLAs: Guarantees for availability, latency, throughput, and consistency
Core Concepts and Principles
Data Models
Model Type | API | Best For |
---|---|---|
Document | SQL API (Core) | General purpose document storage, CRUD operations, querying |
Key-Value | Table API | Simple key-value data storage |
Graph | Gremlin API | Relationships and graph traversals |
Column-Family | Cassandra API | Wide-column storage, high write throughput |
MongoDB | MongoDB API | MongoDB compatibility |
Cosmos DB Structure
- Account: Container for databases, region configuration, account-level settings
- Database: Container for containers, database-level settings
- Container (Collection/Table/Graph): Schema-agnostic container for items, partition key, indexing policy
- Item (Document/Row/Node/Edge): The data stored within a container
Consistency Levels
Level | Description | Guarantee | Use Case |
---|---|---|---|
Strong | Linearizability | Reads return most recent committed version | Financial transactions |
Bounded Staleness | Consistent prefix; reads lag by k versions or t time | Reads return consistent version within bounds | Status updates, product reviews |
Session | Consistent prefix; reads consistent within single client session | Monotonic reads, writes, read-your-writes | Social media, shopping cart |
Consistent Prefix | Updates returned in order, no gaps | Reads never see out-of-order writes | Status updates, tracking |
Eventual | No ordering guarantee | Eventual convergence of replicas | Non-ordered data (counts, likes) |
Request Units (RUs)
Request Units (RUs) are the currency of throughput in Cosmos DB.
- 1 RU = Resources to read a 1KB item
- Operations cost varies:
- Point reads: ~1 RU (1KB document)
- Writes: ~5-10 RUs (1KB document)
- Queries: Variable (depends on complexity, items returned)
Provisioning Options:
- Provisioned Throughput: Fixed RU/s allocation
- Autoscale: Automatically scales between max and min (10%) RU/s
- Serverless: Pay only for consumed resources, no minimum
Partitioning
Partition Key Selection
Good partition keys:
- Have high cardinality (many unique values)
- Distribute requests evenly (avoid hot partitions)
- Distribute data evenly (balance storage)
Common Partition Keys | Evaluation |
---|---|
User ID | Good for user-centric applications |
Tenant ID | Good for multi-tenant applications |
Device ID | Good for IoT scenarios |
Timestamp/Date | May cause hot partitions unless distributed |
Department/Category | May cause uneven distribution |
Synthetic Partition Keys
For better distribution, consider composite keys:
// Example: Combining tenant ID and entity type
partitionKey = `${tenantId}-${entityType}`
Step-by-Step Processes
Creating a Cosmos DB Account and Database
Create Account:
- Navigate to Azure Portal
- Create resource → Azure Cosmos DB
- Select API type (SQL, MongoDB, etc.)
- Choose subscription, resource group, account name
- Configure capacity mode (provisioned/serverless)
- Set replication regions
- Configure availability zones and VNET settings
Create Database:
- In Cosmos DB account, select “Data Explorer”
- “New Database”
- Name database and set throughput (shared or dedicated)
Create Container:
- Select “New Container”
- Set container ID
- Define partition key
- Configure RU/s (if dedicated)
- Set indexing policy and TTL
Writing Data to Cosmos DB
Connect to Database:
CosmosClient client = new CosmosClient(EndpointUri, PrimaryKey); Database database = client.GetDatabase("DatabaseId"); Container container = database.GetContainer("ContainerId");
Create Document:
// Create item dynamic newItem = new { id = "1", pk = "partitionKey1", name = "Item Name", description = "Item Description" }; // Add to container ItemResponse<dynamic> response = await container.CreateItemAsync(newItem, new PartitionKey("partitionKey1"));
Read Document:
// Point read (most efficient) ItemResponse<dynamic> response = await container.ReadItemAsync<dynamic>("1", new PartitionKey("partitionKey1")); // Query QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.name = @name") .WithParameter("@name", "Item Name"); FeedIterator<dynamic> resultSet = container.GetItemQueryIterator<dynamic>(query);
Key Techniques and Methods
Optimizing Costs
Partition Key Optimization:
- Choose keys that distribute traffic evenly
- Avoid cross-partition queries
- Batch operations when possible
Indexing Policy Optimization:
- Exclude unused paths
- Use composite indexes for multi-property queries
- Use spatial indexes only when needed
RU Optimization:
- Monitor RU consumption
- Use autoscale for variable workloads
- Consider time-of-day scaling
Sample Indexing Policy
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": [
{
"path": "/path/to/rarely/queried/property/*"
}
],
"compositeIndexes": [
[
{
"path": "/orderDate",
"order": "ascending"
},
{
"path": "/customerId",
"order": "ascending"
}
]
]
}
Querying Techniques
Efficient Queries:
- Always include partition key in filters
- Use parameters to avoid SQL injection
- Limit returned properties with projections
- Use TOP/OFFSET for pagination
Common Query Patterns:
-- Point read (most efficient) SELECT * FROM c WHERE c.id = "item1" AND c.pk = "partition1" -- Range query SELECT * FROM c WHERE c.pk = "partition1" AND c.timestamp BETWEEN "2023-01-01" AND "2023-01-31" -- Aggregation SELECT COUNT(1) AS count FROM c WHERE c.pk = "partition1" AND c.category = "electronics"
Common Challenges and Solutions
Challenge | Solution |
---|---|
High RU consumption | Optimize queries, refine indexing policy, batch operations |
Hot partitions | Revise partition key strategy, implement synthetic keys |
Query timeouts | Optimize queries, increase RUs, check for cross-partition operations |
Large document sizes | Split documents, use references between documents |
High latency | Deploy to closer regions, optimize consistency level |
Data consistency issues | Use appropriate consistency level for your scenario |
Scaling costs | Implement TTL, archive old data, use autoscale |
Best Practices
Data Modeling
- Denormalize data for common access patterns
- Embed related data within a single document when possible
- Keep documents under 2MB (hard limit)
- Use references for many-to-many relationships or very large related data
- Design for query patterns, not entity relationships
Performance Optimization
- Store related data in the same partition
- Use point reads whenever possible (id + partition key)
- Batch operations to reduce network overhead
- Use TTL to automatically expire data
- Monitor and adjust RUs based on usage patterns
- Use change feed for processing updates and maintaining derived data
Security Best Practices
- Use Azure AD for authentication when possible
- Implement least privilege access with role-based access control
- Enable private endpoints for secure connectivity
- Use customer-managed keys for sensitive data
- Enable diagnostic logs for auditing
- Use IP firewall rules to restrict access
SDK and API Quick Reference
REST API Examples
// Read document
GET https://{account}.documents.azure.com/dbs/{db-id}/colls/{coll-id}/docs/{doc-id}
Authorization: type%3dmaster%26ver%3d1.0%26sig%3d{signature}
x-ms-version: 2018-12-31
x-ms-date: {date}
SDK Examples (C#)
// Update document
ItemResponse<dynamic> response = await container.ReplaceItemAsync<dynamic>(
item,
id,
new PartitionKey(partitionKey)
);
// Query with continuation token
QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.type = @type")
.WithParameter("@type", "product");
FeedIterator<dynamic> resultSet = container.GetItemQueryIterator<dynamic>(
query,
requestOptions: new QueryRequestOptions { MaxItemCount = 100 }
);
while (resultSet.HasMoreResults)
{
FeedResponse<dynamic> response = await resultSet.ReadNextAsync();
foreach (var item in response)
{
Console.WriteLine(item.ToString());
}
// Use response.ContinuationToken for pagination
}
SDK Examples (JavaScript/Node.js)
// Create item
const { resource: createdItem } = await container.items.create(newItem);
// Query items
const querySpec = {
query: "SELECT * FROM c WHERE c.category = @category",
parameters: [
{
name: "@category",
value: "electronics"
}
]
};
const { resources: items } = await container.items.query(querySpec).fetchAll();