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:

ValueHealthy SignWarning Sign
stageIXSCANCOLLSCAN
totalDocsExaminedClose to the number returnedFar larger than the number returned
executionTimeMillisLow and steadyGrows 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.