MongoDB Performance Tuning
A database that answers in a few milliseconds feels smooth. A database that takes seconds makes every page in an application feel stuck. Performance tuning means finding slow operations and fixing them with better indexes, smarter queries, and cleaner data design. Small changes often produce large gains.
The Tuning Cycle
Good tuning follows a loop. Measure first, change one thing, and measure again.
+----------+ +-----------+ +----------+ +-----------+
| Find the | --> | Explain | --> | Apply a | --> | Measure |
| slow | | why it is | | single | | the new |
| query | | slow | | fix | | speed |
+----------+ +-----------+ +----------+ +-----------+
^ |
+---------------------------------------------------+Step 1: Find Slow Queries
Turn On the Profiler
The database profiler records operations that run longer than a chosen time. This command logs every operation slower than 100 milliseconds:
db.setProfilingLevel(1, { slowms: 100 })Read the recorded operations with a normal query:
db.system.profile.find().sort({ ts: -1 }).limit(5)Each record shows the command, the time it took, and the number of documents MongoDB examined. Switch the profiler off after the investigation with db.setProfilingLevel(0), because profiling adds a small cost.
Watch Running Operations
db.currentOp({ secs_running: { $gte: 5 } })The command lists operations that have run for five seconds or more.
Step 2: Explain the Query
db.orders.find({ status: "shipped", city: "Pune" }).explain("executionStats")Focus on three values in the output:
| Value | Healthy Sign | Warning Sign |
|---|---|---|
| stage | IXSCAN | COLLSCAN |
| totalDocsExamined | Close to the number returned | Far larger than the number returned |
| executionTimeMillis | Low and steady | Grows as data grows |
Step 3: Apply Fixes
Add the Right Index
A collection scan reads every document. An index lets MongoDB jump straight to matches:
db.orders.createIndex({ status: 1, city: 1 })Run the same explain command again. The stage changes from COLLSCAN to IXSCAN, and the examined count drops sharply.
Use Covered Queries
A covered query finds every needed value inside the index and never reads the documents:
db.orders.createIndex({ status: 1, total: 1 })
db.orders.find({ status: "shipped" }, { _id: 0, status: 1, total: 1 })Both the filter and the returned fields live in the index, which makes this the fastest kind of read.
Return Only What You Need
Projection trims each result to the required fields. Smaller results travel faster over the network and use less memory:
db.users.find({ active: true }, { name: 1, email: 1, _id: 0 })Limit the Result Size
db.orders.find({ status: "new" }).sort({ createdAt: -1 }).limit(20)A sort on an indexed field with a limit reads only twenty entries.
Replace Large skip Values
The skip method still walks past every skipped document. Page 5000 becomes slow for that reason. Range-based paging stays quick:
// Slow for deep pages
db.orders.find().sort({ _id: 1 }).skip(100000).limit(20)
// Fast: continue after the last _id seen on the previous page
db.orders.find({ _id: { $gt: lastSeenId } }).sort({ _id: 1 }).limit(20)Filter Early in Pipelines
Place $match and $limit at the start of an aggregation pipeline. Early stages reduce the data that later stages must process, and a leading $match can use an index.
Clean Up Unused Indexes
Every index slows down inserts and updates and uses memory. Check usage and remove indexes that no query needs:
db.orders.aggregate([{ $indexStats: {} }])
db.orders.dropIndex("old_index_name")Memory and Hardware
MongoDB runs fastest when the working set fits in memory. The working set means the data and indexes that applications touch often. A server that must read from disk for every request slows down.
RAM large enough for working set RAM too small
+-----------------------------+ +----------+
| Hot data + indexes in RAM | | Hot data | <-- constant disk reads
+-----------------------------+ +----------+ <-- slow responses- Add memory when the working set grows past the available RAM.
- Use fast SSD storage for data files.
- Check server health with
db.serverStatus()and the monitoring tools of your hosting platform.
Design for Speed
- Embed data that applications read together to avoid joins.
- Avoid unbounded arrays that grow forever inside one document.
- Store frequently needed totals in the document instead of recalculating them on each request.
- Keep documents small. MongoDB limits one document to 16 megabytes.
Summary
Performance tuning starts with measurement. The profiler and currentOp reveal slow operations, and explain shows why they run slowly. Proper indexes, covered queries, projection, range-based paging, and early filtering fix most problems. Enough memory for the working set and a schema built around real access patterns keep the database fast as data grows.
