1. Find slow operations
The server logs operations slower than slowms (default 100 ms). To capture them in a collection:
db.setProfilingLevel(1, { slowms: 100 })
db.system.profile.find().sort({ millis: -1 }).limit(10).pretty()
Running right now:
db.currentOp({ active: true, secs_running: { $gt: 5 } })
On Atlas, use the Query Profiler and Performance Advisor tabs.
2. Explain the query
db.orders.find({ customerId: 42, status: "OPEN" }).sort({ createdAt: -1 }).explain("executionStats")
Look at:
| Field | Bad sign |
|---|---|
winningPlan.stage contains COLLSCAN | No usable index; scans the whole collection |
totalDocsExamined ≫ nReturned | The index isn't selective, or only part of the filter uses it |
A SORT stage (not SORT_MERGE) | In-memory sort, limited to 100 MB unless it spills to disk |
totalKeysExamined ≫ nReturned | Index order doesn't match the query |
Ideal: nReturned ≈ totalKeysExamined ≈ totalDocsExamined.
3. The ESR rule for compound indexes
Order the fields: Equality → Sort → Range.
// find({customerId: 42, status: "OPEN", amount: {$gt: 100}}).sort({createdAt: -1})
db.orders.createIndex({ customerId: 1, status: 1, createdAt: -1, amount: 1 })
Putting the range field (amount) before the sort field forces an in-memory sort.
4. Build indexes safely
Since MongoDB 4.2, index builds hold an exclusive lock only briefly at the start and end. On replica sets the build runs on all members at once. Still:
- Build big indexes off-peak and watch replication lag.
- Check disk space first. The index needs room.
db.orders.aggregate([{ $indexStats: {} }]) // usage per index: drop unused ones
5. Other common causes
- Regex not anchored (
/term/) can't use an index efficiently. Use^term, or Atlas Search. $orneeds an index for each branch.- Large documents / unbounded arrays make every read heavy. Use projections and keep arrays bounded.
- Working set larger than RAM: high page faults and disk reads. Check
db.serverStatus().wiredTiger.cache(bytes read into cache) and size up.
Tip
Atlas Performance Advisor suggests indexes based on real slow queries. Review them; don't apply blindly. Each index costs write performance and memory.