Imagine trying to find a specific topic in a massive book with no index at the back. You'd have to scan every single page from start to finish. This is exactly what MongoDB has to do if you query a collection with no indexes—it performs a collection scan (COLLSCAN), which is very slow on large datasets.
An index is a special data structure that stores a small portion of the collection's data in an easy-to-traverse form. It's like the index at the back of the book; it allows the database to quickly find the location of the documents that match your query without scanning everything.
How Indexes Work
An index stores the values for a specific field (or set of fields) in order. Each entry in the index also stores a pointer to the original document.
When you run a query on an indexed field, MongoDB can search the small, ordered index first to find the relevant documents, and then use the pointers to fetch them. This is exponentially faster than a collection scan.
The Golden Rule: You should create indexes on fields that you frequently query on.
Creating Indexes in Mongoose
The easiest way to define indexes is directly in your Mongoose schema.
1. Single-Field Index
This is the most common type of index. You create it on a single field that you often use in your query filters.
JavaScript
const userSchema = new mongoose.Schema({
// Create an index on the 'email' field
email: { type: String, required: true, unique: true, index: true },
name: String
});
Because email is now indexed, queries like User.findOne({ email: 'test@example.com' }) will be extremely fast.
2. Compound Index
A compound index is an index on multiple fields. The order of fields in the index matters!
When to use it: When you frequently query on multiple fields at the same time.
JavaScript
const eventSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, required: true },
eventType: { type: String, required: true },
timestamp: { type: Date, default: Date.now }
});
// Create a compound index on userId (ascending) and timestamp (descending)
eventSchema.index({ userId: 1, timestamp: -1 });
This index would optimize queries like "Find the most recent events for a specific user": Event.find({ userId: 'some_user_id' }).sort({ timestamp: -1 })
Analyzing Queries with explain()
How do you know if your query is using an index? You use the .explain() method. This method doesn't run the query but instead returns a detailed report on how MongoDB would run it.
Let's run it in the Mongo shell:
JavaScript
// Run a query with .explain("executionStats")
> db.users.find({ email: "user@example.com" }).explain("executionStats")
The output will be a large JSON object. You need to look for two key things inside executionStats:
- totalDocsExamined: The number of documents the database had to inspect.
- stage: The stage used to find the documents.
- COLLSCAN: Bad! A full collection scan was performed. Your query is not using an index.
- IXSCAN: Good! An index scan was used. Your query is optimized.
Optimization Goal: For any common query, you want to see an IXSCAN where totalDocsExamined is very close to the number of documents returned.
Trade-offs of Indexes
Indexes are not free!
- Write Performance: Every time you write (insert, update, delete) to the collection, MongoDB must also update the indexes. More indexes mean slower writes.
- Storage: Indexes consume RAM and disk space.
Don't just index every field. Be strategic and create indexes that support your application's most common and important query patterns.