Introduction
When a MongoDB application starts handling more data, queries that once worked quickly can become slow. A request that returned results in a few milliseconds may take several seconds when a collection grows to millions of documents.
This often happens because MongoDB must examine too many documents to find the records a query needs.
Indexes help solve this problem. They allow MongoDB to locate matching documents without scanning the entire collection for every query.
However, adding indexes without understanding your queries can create other problems. Too many indexes consume disk space and memory, and they increase the work MongoDB must perform when inserting, updating, or deleting documents.
In this article, we will explore how MongoDB indexes work, how to identify slow queries, how to create the right indexes, and how to verify whether an index actually improves performance.
What Is an Index in MongoDB?
An index is a data structure that helps MongoDB find documents more efficiently.
Think of a book with an index at the back. If you want to find a particular topic, you can look it up in the index instead of reading every page.
MongoDB indexes work in a similar way. They maintain information about selected document fields so the database can locate matching records more efficiently.
Consider a collection named orders containing these documents:
{
_id: ObjectId("..."),
customerId: 101,
status: "Completed",
totalAmount: 2500,
orderDate: ISODate("2026-10-01T10:00:00Z")
}
Suppose your application frequently runs this query:
db.orders.find({
customerId: 101
});
Without a suitable index, MongoDB may need to examine many documents to find all orders belonging to customer 101.
You can create an index on customerId:
db.orders.createIndex({
customerId: 1
});
The value 1 specifies ascending index order. For a simple equality lookup, an ascending or descending single-field index can generally serve the same purpose.
After creating the index, MongoDB can use it to locate matching documents more efficiently, depending on the query and the selected execution plan.
How to Find Slow Queries in MongoDB
Before creating an index, identify which queries are actually slow.
Adding an index without understanding the query workload can waste storage and may not improve performance.
MongoDB provides several tools for investigating query performance.
1. Use explain() to Inspect a Query
The explain() method shows how MongoDB plans to execute a query.
For example:
db.orders.find({
customerId: 101
}).explain("executionStats");
The executionStats mode executes the query plan and returns information about its execution.
Pay attention to these fields:
Field | What It Tells You |
|---|
winningPlan
| The execution plan MongoDB selected |
nReturned
| Number of documents returned |
totalDocsExamined
| Number of documents examined |
totalKeysExamined
| Number of index keys examined |
executionTimeMillis
| Execution time reported for the explain operation |
These values help you understand how much work MongoDB performed to produce the result.
For example, imagine a query returns 20 documents but examines 100,000 documents. That may indicate an inefficient query or a missing index.
However, a high number of examined documents does not automatically mean the query is badly designed. The query may legitimately need to examine many records, and the execution plan must be interpreted in context.
2. Understand COLLSCAN and IXSCAN
Two important execution stages are COLLSCAN and IXSCAN.
COLLSCAN means MongoDB scans documents in the collection to find matches.
IXSCAN means MongoDB scans an index to locate matching index entries.
For example, an explain plan may contain:
{
stage: "IXSCAN",
keyPattern: {
customerId: 1
}
}
This indicates that MongoDB is scanning the index on customerId.
If a query frequently returns only a few records but performs a collection scan across millions of documents, an appropriate index may help.
However, COLLSCAN is not always a problem. If a query returns a large percentage of a small collection, scanning the collection may be more efficient than using an index and fetching many documents individually.
The goal is not to eliminate every collection scan. The goal is to reduce unnecessary work.
3. Find Queries That Take Too Long
MongoDB's database profiler can record query operations according to its configured profiling level and filters.
You can inspect the current profiling configuration with:
db.getProfilingStatus();
The profiler can help investigate slow operations, but enabling detailed profiling may add overhead and generate additional data.
In production, use profiling carefully. Consider your MongoDB version, workload, environment, and operational monitoring tools before changing the profiling level.
You can also use MongoDB's monitoring facilities to review query latency, resource consumption, and frequently executed operations.
Create an Index for a Slow Query
Suppose your application frequently retrieves orders for a specific customer:
db.orders.find({
customerId: 101
});
Create an index on the field used in the filter:
db.orders.createIndex({
customerId: 1
});
Then run the query again with explain():
db.orders.find({
customerId: 101
}).explain("executionStats");
Compare the execution plan and statistics before and after creating the index.
Look for improvements such as:
Fewer documents examined.
A more suitable winning plan.
Lower query latency under comparable conditions.
Less unnecessary work for repeated queries.
Do not rely on a single execution time alone. Results can vary because of caching, server load, data distribution, and other factors.
Also, an index that improves one query can affect write performance and consume resources. Test the change against the broader workload.
Use Compound Indexes for Multiple Conditions
Many application queries filter by more than one field.
For example:
db.orders.find({
customerId: 101,
status: "Completed"
});
You could create separate indexes on customerId and status, but a compound index may better support this query pattern.
db.orders.createIndex({
customerId: 1,
status: 1
});
A compound index stores index keys for multiple fields in the specified order.
The field order matters because MongoDB can use a compound index most effectively when the query conditions match the index's leading fields.
For example, an index on { customerId: 1, status: 1 } can support queries filtering by customerId, and queries filtering by both customerId and status.
It generally cannot provide the same direct index support for a query that filters only by status, because status is not the leading field.
Follow the Equality-Sort-Range Guideline
When designing compound indexes, the Equality-Sort-Range guideline is a useful starting point:
Equality: Fields matched using exact values.
Sort: Fields used to order the results.
Range: Fields used with range conditions, such as greater than or less than.
For example:
db.orders.find({
customerId: 101,
status: "Completed",
totalAmount: {
$gte: 1000
}
}).sort({
orderDate: -1
});
One possible index is:
db.orders.createIndex({
customerId: 1,
status: 1,
orderDate: -1,
totalAmount: 1
});
This places equality fields first, followed by the sort field and then the range field.
It is a starting point, not a universal rule. The best index depends on the query shape, selectivity, sort requirements, data distribution, and MongoDB's execution plan. Test candidate indexes using representative data.
Use Indexes to Support Sorting
Sorting can become expensive when MongoDB cannot obtain the required order efficiently from an index.
Suppose your application retrieves the latest orders for a customer:
db.orders.find({
customerId: 101
}).sort({
orderDate: -1
}).limit(10);
A compound index can support the filter and sort:
db.orders.createIndex({
customerId: 1,
orderDate: -1
});
This index places the customer's orders together in the index and orders them by date in descending order.
MongoDB may be able to retrieve the first ten matching orders without sorting all matching documents in memory.
Use explain() to verify whether the index supports the query as expected. Depending on the query and plan, you may see an index scan and no separate blocking sort stage.
If the query also filters by other fields, evaluate whether those fields belong in the compound index.
Use a Unique Index to Prevent Duplicate Values
Indexes can enforce data rules as well as improve performance.
Suppose each customer must have a unique email address.
You can create a unique index:
db.customers.createIndex(
{
email: 1
},
{
unique: true
}
);
MongoDB will reject inserts or updates that violate the unique constraint.
Before creating this index on an existing collection, check for duplicate values. If duplicates already exist, the index creation may fail.
For example:
db.customers.aggregate([
{
$group: {
_id: "$email",
count: {
$sum: 1
}
}
},
{
$match: {
count: {
$gt: 1
}
}
}
]);
This aggregation identifies email values that occur more than once.
If your collection permits missing or null email values, decide how those values should be handled before creating the unique index. A regular unique single-field index has specific behavior for missing and null values, and a partial unique index may be more suitable for certain requirements.
For example, you can enforce uniqueness only for documents that contain a string email:
db.customers.createIndex(
{
email: 1
},
{
unique: true,
partialFilterExpression: {
email: {
$type: "string"
}
}
}
);
This is useful when documents without a valid email are allowed but actual email strings must be unique.
Use a Partial Index When Only Some Documents Matter
Sometimes, an application repeatedly queries only a subset of a collection.
For example, an order-processing service may retrieve pending orders much more often than completed orders.
You can create a partial index:
db.orders.createIndex(
{
orderDate: -1
},
{
partialFilterExpression: {
status: "Pending"
}
}
);
This index contains entries only for documents that match the partial filter.
It can reduce index size and maintenance costs compared with indexing every order, especially when pending orders represent a small portion of the collection.
However, MongoDB can use the partial index only when it can determine that using it will not omit documents required by the query.
For example, a query that explicitly filters for pending orders may be eligible:
db.orders.find({
status: "Pending"
}).sort({
orderDate: -1
});
A query that requests all orders cannot use this partial index as its only source of results because the index does not contain completed orders.
Partial indexes are useful when the application's query patterns consistently target a known subset of documents.
Understand Covered Queries
A covered query is a query that can be answered entirely from an index without fetching the full documents from the collection.
For example, suppose an application needs only a customer's name and email:
db.customers.find(
{
customerId: 101
},
{
_id: 0,
name: 1,
email: 1
}
);
You might create an index containing the filter field and the returned fields:
db.customers.createIndex({
customerId: 1,
name: 1,
email: 1
});
If the index and query satisfy MongoDB's requirements for a covered query, MongoDB can return the requested fields directly from the index.
The _id field is included in query results by default, so the example explicitly excludes it. If _id is required, the index and query must be designed accordingly.
Covered queries can reduce document-fetch operations, but adding every projected field to every index is rarely a good strategy. Large indexes consume storage and increase the cost of writes.
Use covered indexes for important, frequently executed queries where the benefit justifies the additional maintenance.
Remove Unnecessary Indexes Carefully
Every additional index consumes storage and needs to be maintained when relevant documents change.
If a collection has too many indexes, writes can become slower and memory may be used less efficiently.
You can list the indexes on a collection:
db.orders.getIndexes();
MongoDB also provides index usage statistics:
db.orders.aggregate([
{
$indexStats: {}
}
]);
These statistics can help identify indexes that have not been used during the observed period.
However, an index showing no usage is not automatically safe to remove. It may support an infrequent report, a scheduled job, a seasonal workload, or an administrative query.
Before dropping an index:
Review its key pattern and options.
Observe index usage over a representative period.
Check application queries and scheduled jobs.
Confirm that no important query depends on it.
Test the impact before removing it from production.
You can also use hidden indexes to test the effect of making an index unavailable to the query planner before actually dropping it, where supported by your MongoDB version. Hidden indexes remain maintained, so they are useful for evaluating query-planning impact, not for measuring the full write-performance benefit of removing an index.
Common Indexing Mistakes
Mistake | Why It Causes Problems | Better Approach |
|---|
Creating an index for every field | Increases storage use and write overhead | Index fields based on real query patterns |
Ignoring compound index order | Queries may not use the index efficiently | Put frequently used equality fields and required sort fields in an appropriate order |
Assuming every COLLSCAN is bad | Small collections or broad queries may scan efficiently | Compare the plan with the query's expected workload |
Creating indexes without checking duplicates | Unique index creation can fail | Find and resolve duplicate values first |
Ignoring sort requirements | MongoDB may perform an expensive sort | Design an index that supports both filtering and sorting |
Removing an index after a short observation period | Infrequent queries may still need it | Review a representative workload before removal |
Measuring performance only once | Cache state and server load can distort results | Compare repeated tests under similar conditions |
Forgetting write overhead | Indexes must be maintained during inserts, updates, and deletes | Evaluate both read and write performance |
Best Practices for MongoDB Index Performance
To get reliable results from indexes, follow these practices:
Start with slow query evidence. Use explain("executionStats") and database monitoring to identify expensive queries.
Index real query patterns. Consider filters, sorting, and the fields returned by important queries.
Use compound indexes deliberately. Field order affects which queries can benefit.
Measure documents examined. Compare totalDocsExamined with nReturned, while considering the purpose of the query.
Test with representative data. Index behavior can change as a collection grows and data distribution changes.
Monitor write performance. An index that improves reads may make writes more expensive.
Avoid unnecessary duplication. Before adding an index, check whether an existing compound index already supports the query.
Review indexes periodically. Application query patterns change as features are added or removed.
Validate unique constraints before deployment. Resolve duplicate data before creating a unique index.
Use production-safe index operations. Consider disk space, resource consumption, deployment procedures, and the behavior of your MongoDB version.
Conclusion
MongoDB indexes can make a major difference when queries need to locate a small number of documents in a large collection. However, effective indexing starts with understanding the query rather than simply adding indexes to frequently used fields.
Use explain("executionStats") to investigate slow queries, create single-field or compound indexes that match your filters and sorting requirements, and consider partial or unique indexes when they fit your data rules.
Also remember that indexes have a cost. They consume storage and add work to inserts, updates, and deletes. An index should improve the overall workload, not just one query in isolation.
The most reliable approach is to measure query performance, make one targeted change at a time, and verify the result using realistic data. This helps keep MongoDB queries efficient as your application and collections grow.
Join the conversation! Your thoughts help the community grow.