MongoDB Cache Thrashing — The Silent Performance Killer
Most MongoDB performance issues are blamed on slow disks, poor indexes, or network latency.
But one of the most destructive performance problems is often completely invisible:
Cache thrashing.
When cache thrashing occurs, MongoDB repeatedly evicts and reloads data pages from disk. Queries that should take milliseconds suddenly take seconds, and CPU usage often looks surprisingly normal.
Understanding cache thrashing requires understanding how MongoDB manages memory internally.
The WiredTiger Cache
MongoDB uses the WiredTiger storage engine, which relies heavily on an in-memory cache to accelerate reads and writes.
By default, the cache size is approximately:
50% of RAM − 1 GB
This cache stores:
- frequently accessed data pages
- indexes
- internal storage engine structures
The goal is simple:
Keep the working set of your application in memory.
When the working set fits into memory, most queries are served directly from RAM.
Memory access latency ~ 100 nanoseconds
SSD access latency ~ 100 microseconds
That is a 1000x difference.
When data fits in memory, MongoDB can deliver extremely low latency.
But when the working set exceeds available cache, things start to break down.
What is Cache Thrashing?
Cache thrashing happens when the database constantly evicts useful data from memory, only to reload it again moments later.
Instead of reusing cached pages, MongoDB repeatedly performs disk reads.
A typical thrashing cycle looks like this:
graph TD
A["Query arrives"] --> B["Page not in cache"]
B --> C["Read page from disk"]
C --> D["Evict another page from cache"]
D --> E["Next query needs the evicted page"]
E --> F["Read from disk again"]
F --> D
style B fill:#3a1a1a,stroke:#ef4444,color:#e6edf3
style C fill:#3a1a1a,stroke:#ef4444,color:#e6edf3
style D fill:#3a1a1a,stroke:#ef4444,color:#e6edf3
The system enters a vicious cycle:
read → evict → read → evict → read → evict
This dramatically increases disk I/O and latency.
The Working Set Concept
The working set is the portion of data that your application accesses frequently.
Example:
Total dataset size: 2 TB
Active working set: 20 GB
Available cache: 32 GB
This system performs extremely well.
Now consider another scenario:
Total dataset size: 2 TB
Active working set: 120 GB
Available cache: 32 GB
Now MongoDB cannot keep the working set in memory.
Pages constantly rotate through cache.
This is where thrashing begins.
How Thrashing Appears in Production
Cache thrashing often produces confusing symptoms.
Engineers frequently see:
| Symptom | What It Looks Like |
|---|---|
| Latency spikes | Queries randomly slow down |
| Disk I/O increases | Disk read throughput spikes |
| CPU appears normal | System isn’t CPU bound |
| Query plans look correct | Indexes are being used |
Because indexes may still exist in memory, queries appear logically correct — but each document fetch requires disk access.
The MongoDB Read Path
To understand why thrashing is expensive, we need to look at how reads work internally.
A typical query flow:
graph TD
A["Client Query"] --> B["Query Planner"]
B --> C["Index Lookup"]
C --> D["Document Page"]
D --> E["Return Result"]
C --> F["Page in Cache?"]
F -->|Yes| D
F -->|No| G["Read Page from Disk"]
G --> D
style G fill:#3a1a1a,stroke:#ef4444,color:#e6edf3
If the document page is not already in memory, MongoDB must perform a disk read.
During thrashing, this happens constantly.
The Eviction Engine
WiredTiger continuously runs background eviction threads responsible for managing cache memory.
When the cache becomes full:
- WiredTiger selects pages to evict
- Dirty pages are written to disk
- Clean pages are removed from memory
In healthy systems, eviction happens gradually and predictably.
In thrashing systems, eviction becomes aggressive:
eviction rate ↑
disk reads ↑
latency ↑
You may observe eviction metrics spiking in serverStatus.
Detecting Cache Thrashing
MongoDB exposes several metrics that reveal thrashing.
WiredTiger Cache Metrics
The most important indicators appear in:
db.serverStatus().wiredTiger.cache
Key signals include:
| Metric | Meaning |
|---|---|
| pages read into cache | Disk reads occurring |
| pages written from cache | Evictions happening |
| eviction server activity | Cache pressure |
A red flag pattern looks like:
pages read into cache → extremely high
cache eviction activity → extremely high
This indicates MongoDB is constantly swapping pages.
Common Causes of Cache Thrashing
1. Working Set Larger Than RAM
The most common cause.
If the frequently accessed dataset exceeds available memory, thrashing becomes inevitable.
Example:
RAM available for cache: 32 GB
Active dataset: 120 GB
MongoDB will continuously swap pages.
2. Poor Query Patterns
Some queries destroy cache locality.
Examples include:
Random document access
Unbounded range scans
Large collection scans
These queries touch many different pages, forcing frequent evictions.
3. Missing Indexes
When indexes are missing, MongoDB performs collection scans.
Example:
db.orders.find({ customerId: 123 })
Without an index on customerId, MongoDB scans the entire collection.
This loads massive amounts of data into cache — evicting useful pages.
4. Oversized Documents
Large documents mean fewer documents per memory page.
Example:
1 KB documents → many per page
50 KB documents → very few per page
Larger documents reduce cache efficiency and increase disk reads.
5. Analytical Queries on OLTP Systems
Analytical queries can easily blow up the working set.
Example:
aggregation across millions of documents
This forces MongoDB to load massive data ranges into memory.
Production Failure Pattern
A common real-world scenario:
System healthy for months
Dataset slowly grows
Working set crosses RAM threshold
Latency suddenly explodes
Engineers often attempt:
- adding indexes
- scaling CPU
- tuning queries
But the real problem is simply:
working_set > memory
No amount of indexing fixes this.
Strategies to Prevent Thrashing
Increase RAM
The simplest and most effective solution.
If the working set fits in memory, thrashing disappears.
Improve Index Design
Indexes reduce the number of pages MongoDB must touch.
Example:
Bad query pattern:
collection scan → loads thousands of pages
With index:
index lookup → loads a few pages
Shard the Dataset
Sharding distributes data across multiple nodes.
Example:
Single node working set: 120 GB
After sharding across 4 nodes:
Per-node working set: 30 GB
Now each node can cache its working set.
Archive Cold Data
Historical data that is rarely accessed should be moved out of the primary collection.
For example:
orders_active
orders_archive
This dramatically reduces the working set.
Separate OLTP and Analytics
Running analytical queries on operational databases is a common cause of thrashing.
Solutions include:
- analytics replicas
- data warehouses
- ETL pipelines
Key Takeaways
Cache thrashing is one of the most dangerous performance problems in MongoDB because it often hides behind seemingly normal metrics.
At its core, the problem is simple:
working set > available memory
When that happens, MongoDB cannot reuse cached pages and must repeatedly load data from disk.
This leads to:
- increased disk I/O
- unpredictable latency
- degraded throughput
Understanding this relationship between memory, working sets, and disk I/O is critical for operating high-performance MongoDB systems in production.
Fast databases are rarely about faster disks.
They are about keeping the right data in memory.