25 Topics
Chapter•320 Minutes

MongoDB Indexes and Query Performance

Learn how MongoDB indexes work, design indexes for real query patterns, understand compound-index prefixes and the ESR guideline, optimize sorting and covered queries, analyze execution plans, and manage index trade-offs in production.

1. Why Indexes Matter

The problem indexes solve

Without a suitable index, MongoDB may need to inspect many or all documents in a collection to answer a query. This is commonly called a collection scan. As a collection grows, scanning every document can increase latency and resource use.

An index is a data structure that stores selected field values in an order that helps MongoDB locate matching documents or return them in a useful order. Indexes can reduce the amount of data examined, support sorting, enforce uniqueness, and support special query types.

Indexes are not free. They consume storage and memory, take time to build, and must be maintained when indexed fields change. A collection with too many indexes can have slower writes and greater resource usage.

The objective is not to index every field. It is to choose a small, useful set of indexes that support real query patterns and constraints.

An index is a tool, not a guarantee

MongoDB's planner decides which plan is appropriate. An existing index is not necessarily selected, and an index may not help a query that returns a large fraction of the collection.

Code Example
db.orders.find({ status: "delivered" });

// Create an index that may support this filter.
db.orders.createIndex({ status: 1 });

db.orders.find({ status: "delivered" }).explain("executionStats");
Key Takeaways
  • Indexes can reduce documents examined and support sorting.
  • Indexes consume storage and increase write-maintenance work.
  • Design indexes around actual query patterns.
  • Use explain output to verify whether an index helps.
Knowledge Check

What is a common cost of adding an index?

2. How MongoDB Indexes Work Conceptually

MongoDB's standard indexes use ordered index structures. Conceptually, an index maps indexed field values to references to matching documents. This allows the query engine to navigate relevant portions of the index instead of examining every document.

For an index on status, the index entries are ordered by status value. A query for status: "delivered" can navigate to the relevant index range and then fetch matching documents as needed.

An index is not a copy of the entire collection. Ordinary indexes store indexed keys and references, with special behavior depending on the index type. If the query needs fields not present in the index, MongoDB may need to fetch documents from the collection.

The exact physical implementation and query-plan details are version- and engine-dependent. For practical tuning, focus on the observed plan, keys examined, documents examined, and returned results rather than assuming every index access has the same cost.

Code Example
db.orders.createIndex({ status: 1 });

db.orders.find({ status: "delivered" })
  .explain("executionStats");
Key Takeaways
  • Indexes store ordered keys and references to matching records.
  • An index scan can still require document fetches.
  • Execution statistics reveal how much work a plan performs.
  • Avoid assuming that every index scan is automatically efficient.
Knowledge Check

Why might MongoDB fetch collection documents after using an index?

3. Single-Field Indexes

A single-field index indexes one field. It is useful when queries frequently filter or sort by that field.

Create an ascending index with db.collection.createIndex({ field: 1 }) and a descending index with { field: -1 }. For a single-field index, MongoDB can often traverse the index in either direction for simple ascending or descending sorts. The direction becomes more important in compound indexes.

A single-field index on status can support eligible filters on status. An index on createdAt can support eligible date-range filters and sorts. Whether an index is beneficial depends on selectivity, query shape, and the amount of data returned.

Before adding an index, examine the application's real filters, sort fields, projection, frequency, and latency requirements.

Code Example
db.orders.createIndex({ status: 1 });
db.orders.createIndex({ createdAt: -1 });

// Check indexes on the collection.
db.orders.getIndexes();
Key Takeaways
  • Single-field indexes support many common filter and sort patterns.
  • The indexed field should match a real query requirement.
  • Use getIndexes() to inspect existing indexes.
  • Validate the benefit with explain and representative data.
Knowledge Check

Which command creates an ascending index on status?

4. Compound Indexes and Field Order

A compound index contains multiple fields in a defined order. It can support queries that filter or sort using compatible combinations of those fields.

For example, { status: 1, createdAt: -1 } orders index entries first by status and then by creation time within each status value. This can support eligible queries that filter by status and sort by creation time.

Field order matters. { status: 1, createdAt: -1 } is not equivalent to { createdAt: -1, status: 1 }. The first index is organized primarily by status; the second is organized primarily by date.

Compound indexes can support multiple query shapes, but only when the query predicates and sort pattern align with the index ordering. Avoid creating a compound index by merely listing every field that appears in an application model.

Order is significant

A compound index with fields in the wrong order may not efficiently support the intended filter or sort. Design from the query pattern, then verify with explain.

Code Example
db.orders.createIndex({ status: 1, createdAt: -1 });

db.orders.find({ status: "delivered" })
  .sort({ createdAt: -1 })
  .limit(20)
  .explain("executionStats");
Key Takeaways
  • Compound indexes index fields in a specific sequence.
  • The leading field strongly influences which query shapes the index supports.
  • Index order affects filter and sort support.
  • Use actual query patterns to choose field order.
Knowledge Check

What does the index { status: 1, createdAt: -1 } order first?

5. Compound Index Prefixes

A compound index supports its leading field and leading combinations of fields. These are called index prefixes.

For an index { a: 1, b: 1, c: 1 }, useful prefixes include { a: 1 }, { a: 1, b: 1 }, and { a: 1, b: 1, c: 1 }. A query that filters only on b generally cannot use this index as efficiently as an index whose leading key is b.

A compound index may therefore support several related query patterns. This can reduce the number of separate indexes needed, but it does not mean every field combination is covered.

Do not assume a suffix field can be used as though it were the leading field. Verify behavior with the query planner, especially when predicates and sorts are involved.

Code Example
db.orders.createIndex({ status: 1, customerId: 1, createdAt: -1 });

// Aligns with the first field.
db.orders.find({ status: "delivered" });

// Aligns with the first two fields.
db.orders.find({ status: "delivered", customerId: 501 });

// Does not use the leading prefix in the same way.
db.orders.find({ customerId: 501 });
Key Takeaways
  • Compound indexes support leading prefixes.
  • Suffix-only filters may not benefit efficiently from the index.
  • Prefix reuse can reduce redundant indexes.
  • Confirm index use with explain output.
Knowledge Check

For an index { a: 1, b: 1, c: 1 }, which is a leading prefix?

6. The ESR Guideline: Equality, Sort, Range

The ESR guideline is a practical way to reason about compound-index field order:

  • Equality: fields matched with equality predicates often belong first.
  • Sort: fields used to order results may follow.
  • Range: range predicates often come after equality and sort fields when preserving index-supported sorting is important.

For example, a query that filters on exact status, sorts by creation time, and applies a date range may need careful index design. The best order depends on the exact query, selectivity, and whether avoiding an in-memory sort or narrowing the range is more important.

ESR is a guideline, not a mechanical rule. A highly selective range can sometimes justify a different order. Test realistic data and examine execution plans before settling on an index.

Use ESR as a starting point

Index design is workload-specific. Compare candidate indexes with real filters, sort requirements, data distributions, and explain results.

Code Example
db.orders.createIndex({ status: 1, createdAt: -1 });

db.orders.find({ status: "delivered" })
  .sort({ createdAt: -1 })
  .limit(50)
  .explain("executionStats");
Key Takeaways
  • ESR stands for Equality, Sort, Range.
  • Equality fields often lead compound indexes.
  • Sort and range ordering depends on the workload.
  • ESR is a design heuristic rather than a universal law.
Knowledge Check

What does ESR stand for?

7. Indexes and Sorting

Sorting can be expensive when MongoDB must collect many candidate documents and sort them in memory or use other supported execution resources. An appropriate index may allow the query to return records in the required order without a separate blocking sort.

Compound sort patterns must align with index ordering or its complete reverse in supported cases. Equality predicates on leading fields can make later index fields useful for sorting.

For example, { status: 1, createdAt: -1 } can support a query that matches one status and sorts by createdAt descending. A sort by unrelated fields may require a separate sort stage.

Use explain output to look for a sort stage and understand the selected plan. Do not assume that creating an index containing the sort field automatically eliminates sorting.

Code Example
db.orders.createIndex({ status: 1, createdAt: -1 });

db.orders.find({ status: "delivered" })
  .sort({ createdAt: -1, _id: -1 })
  .limit(25)
  .explain("executionStats");
Key Takeaways
  • A suitable index can provide the requested order.
  • Compound sort patterns must align with index order.
  • Equality predicates on leading keys can enable suffix sorting.
  • Use explain to verify whether a separate sort is needed.
Knowledge Check

Does every index containing a sort field eliminate a separate sort stage?

8. Covered Queries and Projection

A covered query can be satisfied using index keys without fetching the full collection documents. The index must contain the fields needed to evaluate the query and return the requested projection, subject to MongoDB's coverage rules.

For example, if an API filters by status and returns only status and customerId, a compound index containing those fields may make coverage possible. The exact index should be designed around the query's filter and projection.

Coverage can reduce document fetches, but adding fields to an index increases its size and write-maintenance cost. Do not create huge indexes solely to cover every possible API response.

Use explain execution statistics to check whether document fetches occur and whether the chosen plan is actually covered.

Coverage has requirements

The index must provide all fields needed by the query and projection. _id is normally returned unless excluded, so projection details can affect coverage.

Code Example
db.orders.createIndex({ status: 1, customerId: 1 });

db.orders.find(
  { status: "delivered" },
  { _id: 0, status: 1, customerId: 1 }
).explain("executionStats");
Key Takeaways
  • A covered query can avoid fetching full documents.
  • The index must contain the required filter and result fields.
  • Coverage trades reduced fetches for a larger maintained index.
  • Confirm coverage using explain output.
Knowledge Check

What is a key property of a covered query?

9. Multikey Indexes for Arrays

When an index is created on an array field, MongoDB creates a multikey index. Multikey indexes support queries that match array elements and are common in schemas containing tags, categories, and embedded records.

For example, an index on tags can support eligible queries that search for a specific tag. If an array contains several values, the index can contain multiple entries for a document.

Compound multikey indexes have important restrictions. In particular, a compound multikey index cannot index more than one independently array-valued indexed field in the same document. Array query semantics also matter: matching multiple predicates against one array element may require $elemMatch.

Multikey indexes can grow significantly when arrays contain many elements. Measure index size and write costs for large arrays.

Code Example
db.articles.createIndex({ tags: 1 });

db.articles.find({ tags: "mongodb" }).explain("executionStats");
Key Takeaways
  • Indexes on array fields become multikey indexes.
  • Array indexes can support matching array elements.
  • Compound multikey indexes have restrictions.
  • Large arrays can increase index size and write cost.
Knowledge Check

What type of index does MongoDB create when an indexed field contains arrays?

10. Unique Indexes and Data Integrity

A unique index prevents multiple indexed entries from having the same key, subject to the index type and its null or missing-field behavior. It is useful for requirements such as unique usernames, email addresses, or external identifiers.

Before creating a unique index on existing data, check for duplicates. If duplicates violate the uniqueness rule, index creation will fail until the data is corrected or the rule is redesigned.

A unique index is a database-level integrity mechanism and is stronger than checking for duplicates only in application code. Application-side checks can race when two requests attempt to create the same value concurrently.

Think carefully about case sensitivity, normalization, missing values, and whether uniqueness should apply to all documents or only a subset.

Clean existing data first

A unique index cannot be created successfully if existing documents violate the uniqueness constraint.

Code Example
db.users.createIndex({ usernameNormalized: 1 }, { unique: true });

// Before creating the index, inspect duplicate values.
db.users.aggregate([
  { $group: { _id: "$usernameNormalized", count: { $sum: 1 } } },
  { $match: { count: { $gt: 1 } } }
]);
Key Takeaways
  • Unique indexes enforce uniqueness at the database level.
  • Check and resolve existing duplicates before creation.
  • Normalize values if business uniqueness is case-insensitive.
  • Understand how missing and null values interact with the chosen index.
Knowledge Check

Why is a unique index safer than only checking uniqueness in application code?