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:

SymptomWhat It Looks Like
Latency spikesQueries randomly slow down
Disk I/O increasesDisk read throughput spikes
CPU appears normalSystem isn’t CPU bound
Query plans look correctIndexes 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:

  1. WiredTiger selects pages to evict
  2. Dirty pages are written to disk
  3. 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:

MetricMeaning
pages read into cacheDisk reads occurring
pages written from cacheEvictions happening
eviction server activityCache 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.


Further Reading