Performance and Monitoring
Introduction
Slow MongoDB usually means missing indexes, large documents, or working set larger than RAM—not mysterious "NoSQL slowness." This chapter reads explain, enables the profiler, tunes connection pools, uses bulkWrite, and pairs MongoDB with Redis for cache-aside—without duplicating the Redis track.
Prerequisites
WiredTiger and Working Set
MongoDB’s default storage engine WiredTiger caches frequently accessed data and indexes in RAM.
| Term | Meaning |
|---|---|
| Working set | Data + indexes accessed often |
| Cache pressure | Working set > RAM → disk reads, latency spikes |
| Page faults | Data not in cache |
Fixes: indexes, projection, smaller documents, more RAM, shard (last resort).
explain Recap
db.posts.find({ published: true }).sort({ createdAt: -1 }).limit(20)
.explain("executionStats")Watch:
executionStats.executionTimeMillistotalDocsExaminedvsnReturnedwinningPlan.inputStage.stage:IXSCANgood,COLLSCANbad at scale
Add index — chapter 11—re-run until totalDocsExamined ≈ nReturned.
Database Profiler
Capture slow operations:
// Level 1: slow ops only (>100ms default threshold configurable)
db.setProfilingLevel(1, { slowms: 100 })
// Run your app or queries...
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty()
// Turn off
db.setProfilingLevel(0)Atlas Performance Advisor suggests indexes from slow query logs—prefer on M10+; M0 has limited metrics.
currentOp and serverStatus
db.currentOp({ "active": true, "secs_running": { $gt: 3 } })
db.serverStatus().connections
db.serverStatus().opcountersHigh connections.current → check connection leaks in app (pool not closed).
Connection Pool (Drivers)
Each app process maintains a pool of sockets to MongoDB.
PyMongo defaults (concept):
MongoClient(
uri,
maxPoolSize=100,
minPoolSize=0,
serverSelectionTimeoutMS=5000,
)| Mistake | Fix |
|---|---|
New MongoClient per request | One client per process (singleton) |
| Too many app workers × huge pool | Cap maxPoolSize; total < maxConnections on server |
| No timeout | Set serverSelectionTimeoutMS |
bulkWrite for Throughput
Batch mixed operations:
db.posts.bulkWrite([
{ insertOne: { document: { title: "A", slug: "a", published: true } } },
{ updateOne: { filter: { slug: "mongodb-basics" }, update: { $inc: { viewCount: 1 } } } },
{ deleteOne: { filter: { slug: "draft-notes" } } }
], { ordered: false })PyMongo equivalent — chapter 19. Reduces round trips vs one op per request.
Read and Write Patterns
| Pattern | Benefit |
|---|---|
| Projection | Less network I/O |
| Indexed sort | Avoid in-memory sort limit |
| Pagination by range | Avoid large skip |
| Denormalize read-heavy | Fewer $lookup — modeling |
| Separate hot metrics | Counters in Redis, facts in MongoDB |
MongoDB + Redis Cache-Aside
Architecture:
API → Redis GET key
│ miss
▼
MongoDB findOne
│
▼
Redis SETEX key TTLExample flow (concept):
# Pseudocode — full FastAPI in chapter 20
doc = redis.get(f"post:{slug}")
if doc is None:
doc = collection.find_one({"slug": slug})
if doc:
redis.setex(f"post:{slug}", 300, serialize(doc))Invalidate on update:
collection.update_one({"slug": slug}, {"$set": {...}})
redis.delete(f"post:{slug}")Advanced: change streams invalidate cache — chapter 14.
Compare Redis caching patterns and Spring Boot Redis.
Monitoring Checklist (Production)
| Signal | Tool |
|---|---|
| Replication lag | rs.printSecondaryReplicationInfo(), Atlas metrics |
| Disk usage | db.stats(), Atlas charts |
| Slow queries | Profiler, Atlas Performance Advisor |
| Connections | serverStatus().connections, Atlas |
| Opcounters | Insert/query rates vs capacity |
Link Linux monitoring for host-level metrics.
Common Performance Anti-Patterns
| Anti-pattern | Fix |
|---|---|
find() without filter | Always filter + index |
| Fetch full 16 MB docs | Project fields |
Unbounded $lookup | $match first, limit |
| Index every field | Write slowdown; index selectively |
| Long transactions | Keep < 1 second |
$where JavaScript | Remove; use operators |
Post-Chapter Checklist
- Ran
explain("executionStats")on a slow query - Used profiler or Atlas to find slow ops
- One
MongoClient(or pool) per app process planned - You can describe cache-aside with Redis
FAQ
How much RAM for MongoDB?
Rule of thumb: working set should fit RAM for steady performance; measure with monitoring.
compact command?
Rarely needed with WiredTiger; focus on indexes and schema.
Read from secondary for reports?
readPreference: secondaryPreferred—accept replication lag.
SQL EXPLAIN equivalent?
explain("executionStats") — Indexes chapter.
Atlas M0 slow?
Shared tier throttles CPU—upgrade tier or optimize queries first.