Index Type Selection
Complete the full lesson to earn 25 points — 50 with Pro
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks, the wait, and see fewer ads — read each lesson on a single page with Pro
Module: Optimize Azure Cosmos DB Solution
Lesson: Index Type Selection
Introduction: The Hidden Engine of Database Performance
When you first start working with Azure Cosmos DB, it is easy to view it as a "black box" where you simply push JSON documents in and retrieve them later. However, as your data volume grows and your application demand increases, the way Cosmos DB organizes and retrieves that data becomes the primary factor in your system’s latency and cost. At the heart of this organization lies the Indexing Strategy. Specifically, the choice of index type determines how the database engine traverses your data to satisfy queries.
Choosing the right index type is not just a technical detail; it is a fundamental architectural decision. If you index too much, you increase the Request Unit (RU) cost of every write operation because the database must update the index for every modification. If you index too little, your read queries will perform full scans of your collection, leading to high latency and massive spikes in RU consumption. This lesson focuses on the nuances of index type selection, helping you balance the trade-off between write-heavy workloads and read-intensive requirements.
Understanding the Default Indexing Policy
By default, Azure Cosmos DB indexes every single property within every document you insert. This "everything-indexed" approach is excellent for developers who are just starting or building prototypes because it allows you to run almost any query without prior configuration. However, in a production environment, this is rarely the most efficient path.
When the database engine receives a document, it creates an inverted index for each property. An inverted index is a mapping from content (the value of a field) to its location (the document ID). Because Cosmos DB stores these indexes in a way that supports efficient range scans and equality filters, it is quite powerful. Yet, the cost of maintaining this index for every single field—including large strings or deeply nested objects—can become prohibitive. As you scale, you must move from the default policy to a customized policy that reflects the actual query patterns of your application.
The Three Pillars of Indexing: Types and Modes
Before we dive into the specific index types, we must distinguish between the Indexing Mode and the Index Type. The Indexing Mode tells the system how to update the index, while the Index Type tells the system how to store the data for a specific field.
Indexing Modes
- Consistent: Updates to the index happen synchronously with the write operation. This ensures that a query immediately reflects the most recent write. This is the standard for most transactional applications.
- Lazy: Updates to the index happen asynchronously when the system has spare capacity. While this lowers the write cost, it risks returning stale data, as the index might not include the latest changes at the moment a query is executed.
Callout: Consistent vs. Lazy Indexing In modern production environments, the use of 'Lazy' indexing is extremely rare. Because distributed systems require strict consistency for many business processes, 'Consistent' is the default and recommended mode. Only consider 'Lazy' indexing if you are dealing with a non-critical analytical workload where eventual consistency is acceptable and you must prioritize minimizing write latency at all costs.
Index Types
Azure Cosmos DB provides three primary index types that you can apply to your paths:
- Hash: This index type is used for equality comparisons. If your queries frequently use
WHERE c.customerId = '123', a hash index is highly efficient. It provides a constant-time lookup for equality. - Range: This is the most versatile index type. It supports equality comparisons, range comparisons (
>,<,>=), andORDER BYoperations. If you need to sort data or filter by a range of dates or numbers, this is the mandatory choice. - Spatial: Used specifically for geospatial data. It allows you to query based on proximity, distance, or containment within shapes (e.g., "Find all stores within 5 miles of this coordinate").
Practical Application: Selecting the Right Type
To select the right index, you must perform a query analysis. Start by listing all the queries your application executes against a specific container. Categorize them by their filter criteria.
- Scenario A: High-Concurrency Lookups. If your application is a user profile service where the most common query is retrieving a user by their
userId, you should use a Hash index on theuserIdfield. This minimizes the index size and the overhead of maintaining a range index. - Scenario B: Time-Series Data. If you are storing IoT sensor data, you are likely filtering by
timestampand device ID. You will need a Range index on thetimestampfield to support queries likeSELECT * FROM c WHERE c.timestamp > '2023-01-01'. - Scenario C: Geospatial Tracking. If you are building a delivery app, you need a Spatial index on the location field to perform proximity searches like
ST_DISTANCE.
Code Example: Defining a Custom Indexing Policy
You define your indexing policy in the IndexingPolicy object within your container configuration. Below is an example of how to implement a selective policy using the Azure SDK for .NET.
// Define the container properties
ContainerProperties containerProperties = new ContainerProperties
{
Id = "OrdersContainer",
PartitionKeyPath = "/customerId",
IndexingPolicy = new IndexingPolicy
{
IndexingMode = IndexingMode.Consistent,
Automatic = true,
IncludedPaths =
{
new IncludedPath { Path = "/orderDate/?" }, // Range index for dates
new IncludedPath { Path = "/customerId/?" } // Hash index for equality
},
ExcludedPaths =
{
new ExcludedPath { Path = "/*" } // Exclude everything else by default
}
}
};
// Apply the policy during container creation
await database.CreateContainerIfNotExistsAsync(containerProperties);
Explanation of the code:
- ExcludedPaths: We set
/*to exclude all properties by default. This is the "Opt-in" strategy, which is the industry standard for optimizing performance and cost. - IncludedPaths: We explicitly define the properties we want to index. The
/?suffix indicates that we are indexing the value at that path. By default, Cosmos DB applies a range index for strings and numbers. - Efficiency: By excluding everything except the fields we actually query, we significantly reduce the amount of storage and compute required for every write operation.
Deep Dive: The Impact of Indexing on RUs
Request Units (RUs) are the currency of Cosmos DB. Every write operation incurs a cost based on the size of the document and the number of indexes that need to be updated. When you have a complex document with many fields, the default "index everything" policy can double or triple the write cost compared to an optimized policy.
Consider a document that contains a large description field or a large tags array. If you index these fields, the database engine must generate index entries for every word or tag in those fields. This results in an enormous index size and slow writes. By excluding these large fields from your indexing policy, you can lower your write RU cost significantly.
Callout: The "Index Everything" Trap Many developers assume that more indexing means faster queries. While this is true for read operations, it creates a massive "Write Tax." Always perform a query audit before moving to production. If a field is never used in a
WHEREclause, aJOIN, or anORDER BYclause, it should be excluded from your index.
Advanced Indexing Strategies
As your application matures, you might encounter scenarios where simple hash or range indexes are insufficient.
Composite Indexes
Composite indexes are required when you have queries that filter by multiple properties or sort by multiple properties. For example, if you frequently run SELECT * FROM c WHERE c.category = 'Electronics' ORDER BY c.price DESC, a single range index on category and a single range index on price will not be enough to optimize the sort. You must create a composite index that covers both.
"compositeIndexes": [
[
{ "path": "/category", "order": "ascending" },
{ "path": "/price", "order": "descending" }
]
]
Spatial Indexes
Spatial indexes are configured differently. You must specify the type as Spatial and define the geometry type (Point, LineString, Polygon, or MultiPolygon).
"includedPaths": [
{
"path": "/location/?",
"indexes": [
{ "dataType": "Point", "kind": "Spatial" }
]
}
]
Common Pitfalls and How to Avoid Them
Even experienced architects fall into common traps when managing indexing policies. Here are the most frequent mistakes:
- Indexing Large Strings: Never index long text fields (like user comments or blog posts) with a range index. These strings can be thousands of characters long. Indexing them will lead to high RU costs and potential errors if the index entry size limit is exceeded.
- Ignoring the Partition Key: The partition key is automatically indexed. Do not try to manually add it to your
includedPaths. Doing so is redundant and adds unnecessary configuration overhead. - Forgetting to Update the Policy: As your application evolves, your query patterns will change. A query that was once rare might become the most frequent. You must regularly review your query logs and update your indexing policy to match current usage.
- Over-indexing Arrays: If you have an array of objects, indexing the entire path can create a massive number of index entries. Be precise about which sub-properties within an array you need to index.
Comparison Table: Index Selection Guide
| Query Pattern | Recommended Index | Why? |
|---|---|---|
Simple Equality (=) |
Hash | Optimized for fast lookup on specific values. |
Range/Sort (>, <, ORDER BY) |
Range | Necessary for comparative logic and sorting. |
Geospatial (ST_DISTANCE) |
Spatial | Specifically designed for coordinate math. |
| Multi-column Filters/Sorts | Composite | Groups fields to prevent database engine scan-merging. |
| Large Blobs/Text | Excluded | Prevents excessive write costs and storage bloat. |
Step-by-Step: Updating an Indexing Policy
Updating an indexing policy is an online operation. Cosmos DB will transform the index in the background without requiring downtime. However, for very large collections, this transformation can consume a significant amount of RUs.
- Analyze current queries: Use the Azure Portal or the
QueryStatsoutput in your SDK to identify which fields are being filtered. - Draft the new policy: Use the JSON structure shown in the code examples above.
- Test in a development environment: Always verify the impact of the new policy on a staging collection before applying it to production.
- Apply the policy: Use the Azure SDK or CLI to update the container properties.
- CLI Command:
az cosmosdb sql container update --indexing-policy @policy.json ...
- CLI Command:
- Monitor the progress: Use the Azure Portal to check the "Indexing Progress" metric.
- Verify performance: Once the transformation is complete, check the RU cost of your queries to confirm the improvements.
Note: When updating an indexing policy on a container with millions of documents, the transformation process will consume additional RUs. It is best practice to perform these updates during periods of low traffic to avoid impacting your application's responsiveness.
Best Practices for Long-Term Success
To ensure your indexing strategy remains effective, follow these industry-standard practices:
- The "Opt-in" Philosophy: Always start with an empty index and add only the paths you absolutely need. This keeps your database lean and your write costs predictable.
- Monitor Index Utilization: Use Azure Monitor to track the RUs consumed by indexing. If your write RUs are significantly higher than your read RUs, your indexing policy is likely too aggressive.
- Use Documentation for Policy Changes: Treat your indexing policy as infrastructure-as-code. Store your policy JSON files in a version control system like Git. This allows you to track why certain indexes were added and provides a rollback path if an update causes unexpected issues.
- Be Careful with Wildcards: While the
/*wildcard is convenient, it is the enemy of optimization. Use it only during initial development or for very small, non-critical collections. - Leverage TTL with Indexing: If you are using Time-to-Live (TTL) to automatically expire documents, ensure that the property used for TTL is indexed if you ever need to query against it.
Common Questions (FAQ)
Q: Can I index a property that is deep inside an object?
A: Yes, you can use path syntax like /user/address/zipCode/?. This will index the zip code regardless of how deep it is nested in the JSON.
Q: What happens if I make a mistake in my indexing policy? A: If you exclude a field that your application needs, your queries will simply be slower because they will perform a full scan. You can fix this by updating the policy to include the field again.
Q: Are there limits to how many indexes I can have? A: While there is no hard limit on the number of paths, there is a limit on the total size of the index per document. If you index too many fields, you will hit this limit and receive an error when trying to save a document.
Q: Does indexing affect the storage cost? A: Yes. The index is stored as data. A highly complex indexing policy can increase the total storage size of your container by a significant percentage.
Key Takeaways
- Indexing is a balance: There is a direct trade-off between read performance and write cost. Every index you add makes reads faster but writes more expensive.
- Default is not optimal: Never use the default "index everything" policy for production applications. Use an "opt-in" approach where you explicitly define only the paths required for your queries.
- Select the right type: Use Hash for equality, Range for comparisons and sorting, and Spatial for geographical data. Misusing these types will lead to inefficient query execution.
- Use Composite Indexes wisely: If your queries filter or sort by multiple fields, a composite index is essential for performance. Do not rely on the engine to merge multiple single-field indexes.
- Monitor and adapt: Your indexing policy is not a "set and forget" configuration. As your application queries evolve, your indexing policy must evolve with them. Use metrics and monitoring to keep your RU consumption in check.
- Document your changes: Keep your indexing policies in source control. This ensures that your team understands why certain indexes exist and allows for easier troubleshooting when performance issues arise.
- Avoid over-indexing: Resist the urge to index every single field just in case you might need it later. Only index what you are actively using in your
WHEREorORDER BYclauses to maintain high write throughput.
By following these principles, you ensure that your Azure Cosmos DB solution remains performant, cost-effective, and scalable. The database engine is only as efficient as the instructions you provide it; by mastering index type selection, you are taking full control of your application's performance.
Reach the last section to complete this lesson and earn points — you're on section 1 of 11.
- Introduction to Cosmos DB Data Modeling
- Introduction to Cosmos DB Data Modeling Quiz5q
- Multiple Entity Types in Same Container
- Multiple Entity Types in Same Container Quiz5q
- Storing Related Entities in Same Document
- Storing Related Entities in Same Document Quiz5q
- Denormalizing Data Across Documents
- Denormalizing Data Across Documents Quiz5q
- Referencing Between Documents
- Referencing Between Documents Quiz5q
- Partition Keys and Document IDs
- Partition Keys and Document IDs Quiz5q
- Time to Live (TTL) Configuration
- Time to Live (TTL) Configuration Quiz5q
- Document Versioning Strategies
- Document Versioning Strategies Quiz5q
- Schema Versioning Patterns
- Schema Versioning Patterns Quiz5q
- Choosing Partition Strategies
- Choosing Partition Strategies Quiz5q
- Partition Key Selection Best Practices
- Partition Key Selection Best Practices Quiz5q
- Transactions and Partition Keys
- Transactions and Partition Keys Quiz5q
- Cross-Partition Query Costs
- Cross-Partition Query Costs Quiz5q
- Data Distribution Analysis
- Data Distribution Analysis Quiz5q
- Throughput Distribution Planning
- Throughput Distribution Planning Quiz5q
- Synthetic Partition Keys
- Synthetic Partition Keys Quiz5q
- Hierarchical Partition Keys
- Hierarchical Partition Keys Quiz5q
- Throughput and Storage Requirements
- Throughput and Storage Requirements Quiz5q
- Serverless vs Provisioned Throughput
- Serverless vs Provisioned Throughput Quiz5q
- Database-Level Provisioned Throughput
- Database-Level Provisioned Throughput Quiz5q
- Granular Scale Units
- Granular Scale Units Quiz5q
- Global Distribution Costs
- Global Distribution Costs Quiz5q
- Configuring Throughput in Portal
- Configuring Throughput in Portal Quiz5q
- Gateway vs Direct Connectivity Mode
- Gateway vs Direct Connectivity Mode Quiz5q
- Creating Database Connections
- Creating Database Connections Quiz5q
- Azure Cosmos DB Emulator
- Azure Cosmos DB Emulator Quiz5q
- Connection Error Handling
- Connection Error Handling Quiz5q
- Singleton Pattern for Clients
- Singleton Pattern for Clients Quiz5q
- Global Distribution Regions
- Global Distribution Regions Quiz5q
- Threading and Parallelism
- Threading and Parallelism Quiz5q
- Arrays and Nested Objects Queries
- Arrays and Nested Objects Queries Quiz5q
- Correlated Subqueries
- Correlated Subqueries Quiz5q
- Array and Type-Checking Functions
- Array and Type-Checking Functions Quiz5q
- Mathematical and String Functions
- Mathematical and String Functions Quiz5q
- Date Functions in Queries
- Date Functions in Queries Quiz5q
- Point Operations vs Query Operations
- Point Operations vs Query Operations Quiz5q
- CRUD Point Operations
- CRUD Point Operations Quiz5q
- Patch Operations for Updates
- Patch Operations for Updates Quiz5q
- Transactional Batch Operations
- Transactional Batch Operations Quiz5q
- Bulk Operations with SDK
- Bulk Operations with SDK Quiz5q
- Optimistic Concurrency with ETags
- Optimistic Concurrency with ETags Quiz5q
- Query Pagination and Continuation
- Query Pagination and Continuation Quiz5q
- Cosmos DB Mirroring for Fabric
- Cosmos DB Mirroring for Fabric Quiz5q
- Mirroring vs Spark Connector
- Mirroring vs Spark Connector Quiz5q
- Enabling Analytical Store
- Enabling Analytical Store Quiz5q
- Synapse Spark and SQL Queries
- Synapse Spark and SQL Queries Quiz5q
- Change Data Capture in Analytical Store
- Change Data Capture in Analytical Store Quiz5q
- Azure Functions and Event Hubs Integration
- Azure Functions and Event Hubs Integration Quiz5q
- Denormalization with Change Feed
- Denormalization with Change Feed Quiz5q
- Referential Integrity with Change Feed
- Referential Integrity with Change Feed Quiz5q
- Azure AI Search Integration
- Azure AI Search Integration Quiz5q
- Azure Functions Change Feed Trigger
- Azure Functions Change Feed Trigger Quiz5q
- Consuming Change Feed with SDK
- Consuming Change Feed with SDK Quiz5q
- Change Feed Estimator
- Change Feed Estimator Quiz5q
- Denormalization via Change Feed
- Denormalization via Change Feed Quiz5q
- Aggregation Persistence with Change Feed
- Aggregation Persistence with Change Feed Quiz5q
- Read-Heavy vs Write-Heavy Indexing
- Read-Heavy vs Write-Heavy Indexing Quiz5q
- Index Type Selection
- Index Type Selection Quiz5q
- Custom Indexing Policies
- Custom Indexing Policies Quiz5q
- Composite Index Implementation
- Composite Index Implementation Quiz5q
- Index Performance Optimization
- Index Performance Optimization Quiz5q
- Response Status Codes and Metrics
- Response Status Codes and Metrics Quiz5q
- Normalized RU Consumption Monitoring
- Normalized RU Consumption Monitoring Quiz5q
- Server-Side Latency Metrics
- Server-Side Latency Metrics Quiz5q
- Data Replication Monitoring
- Data Replication Monitoring Quiz5q
- Azure Monitor Alerts Configuration
- Azure Monitor Alerts Configuration Quiz5q
- Resource Logs Implementation
- Resource Logs Implementation Quiz5q
- Partition Throughput Monitoring
- Partition Throughput Monitoring Quiz5q
- Encryption Key Management
- Encryption Key Management Quiz5q
- Network-Level Access Control
- Network-Level Access Control Quiz5q
- Data Encryption Configuration
- Data Encryption Configuration Quiz5q
- Azure RBAC for Control Plane
- Azure RBAC for Control Plane Quiz5q
- Microsoft Entra ID for Data Plane
- Microsoft Entra ID for Data Plane Quiz5q
- CORS Settings Configuration
- CORS Settings Configuration Quiz5q
- Customer-Managed Keys
- Customer-Managed Keys Quiz5q
- Always Encrypted Implementation
- Always Encrypted Implementation Quiz5q
- Data Movement Strategy Selection
- Data Movement Strategy Selection Quiz5q
- SDK Bulk Operations for Data Movement
- SDK Bulk Operations for Data Movement Quiz5q
- Azure Data Factory Pipelines
- Azure Data Factory Pipelines Quiz5q
- Kafka Connector Integration
- Kafka Connector Integration Quiz5q
- Azure Stream Analytics Integration
- Azure Stream Analytics Integration Quiz5q
- Cosmos DB Spark Connector
- Cosmos DB Spark Connector Quiz5q
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles the points you earn on every lesson and quiz so you progress twice as fast, unlocks half of every practice exam — plus full case studies — with the Learn & Exam study modes, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× points per lesson & quiz
- ✓ 50% of every exam unlocked
- ✓ Learn & Exam modes
- ✓ Distraction-free lessons