MongoDB $planCacheStats Stage

Beginner
⏱️ 11 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

The $planCacheStats stage reveals cached query plans for a collection—what shapes MongoDB remembers, which indexes were chosen, and how costly each plan was during evaluation. DBAs use it to troubleshoot slow queries and verify the planner is caching sensible plans.

01

Cache entries

One per plan.

02

planCacheKey

Lookup hash.

03

works

Plan cost.

04

cachedPlan

Chosen stages.

05

First stage

Pipeline rule.

06

vs explain()

All vs one.

Definition and Usage

When MongoDB runs a query, the query planner picks an execution plan—often an index scan (IXSCAN) or collection scan (COLLSCAN). Successful plans are stored in the plan cache so repeat queries skip re-planning.

💡
Beginner tip

Think of the plan cache as MongoDB’s memory of “how we ran this kind of query before”:
explain() → plan for one query you just ran.
$planCacheStats → list of all cached plans on a collection.
Pair with $indexStats to see which indexes exist and which plans use them.

Run $planCacheStats during performance reviews or after deploying new indexes. It is a diagnostic stage—not for application data pipelines.

📝 Syntax

$planCacheStats accepts an empty document or an optional sharded-cluster flag:

mongosh
{ $planCacheStats: { } }

{ $planCacheStats: { allHosts: <boolean> } }

Syntax Rules

  • First stage only$planCacheStats must be stage 1; add $match, $sort, or $project after it.
  • Empty object — most deployments use { $planCacheStats: {} } with no options.
  • allHosts — sharded clusters only. When true, broadcasts to all shard nodes; default false follows read preference.
  • Not in $facet — cannot appear inside $facet sub-pipelines or transactions.
  • Privilege — requires planCacheRead on the collection when authorization is enabled.
  • Read concern — requires read concern level "local".
mongosh
db.orders.aggregate([
  { $planCacheStats: {} }
])

⚡ Quick Reference

QuestionAnswer
What it doesLists cached query plan entries for the collection
Pipeline positionMust be the first stage
Match a specific plan$match: { planCacheKey: "…" } after stage
Plan cost metricworks — work units during plan trial
Query shape hashplanCacheShapeHash (or queryHash on older versions)
vs explain()$planCacheStats lists all cache entries; explain() is one query
List all
{ $planCacheStats:
  {} }

Every entry

Filter key
$match: {
  planCacheKey: "ABC"
}

One plan

Sort cost
$sort: {
  works: -1
}

Highest works

Active only
$match: {
  isActive: true
}

In-use plans

🧰 Key Output Fields

Each document from $planCacheStats describes one plan cache entry. These fields matter most when tuning queries:

planCacheKey Identifier

Hex hash combining query shape and available indexes. Filter on this field to inspect one specific cached plan.

{ $match: {
  planCacheKey: "B1435201"
}}
planCacheShapeHash Shape

Hash of the query shape alone (filter pattern). MongoDB 8.0+; older versions expose queryHash with the same meaning.

planCacheShapeHash: "478AD696"
// Same shape → same hash
works Cost

Work units the plan consumed during the trial period when the planner evaluated candidate plans. Higher values often mean more expensive plans.

works: Long("7")
// Sort descending to find costly plans
cachedPlan Plan

The stored execution plan—stages like IXSCAN, COLLSCAN, or slot-based engine output. Also check isActive, createdFromQuery, and host.

isActive: true
cachedPlan: { stage: "IXSCAN", ... }
host: "mongodb1:27017"

Examples Gallery

Sample orders collection—populate the plan cache with representative queries, list entries, filter by key, sort by cost, and compare with explain().

📚 Getting Started

Orders collection, indexes, and queries to fill the plan cache.

Example 1 — Setup orders and warm the plan cache

mongosh
db.orders.drop()

db.orders.insertMany([
  { _id: 1, item: "abc", price: Decimal128("12"), quantity: 2,  type: "apparel"     },
  { _id: 2, item: "jkl", price: Decimal128("20"), quantity: 1,  type: "electronics" },
  { _id: 3, item: "abc", price: Decimal128("10"), quantity: 5,  type: "apparel"     },
  { _id: 4, item: "abc", price: Decimal128("8"),  quantity: 10, type: "apparel"     },
  { _id: 5, item: "jkl", price: Decimal128("15"), quantity: 15, type: "electronics" }
])

db.orders.createIndex({ item: 1 })
db.orders.createIndex({ item: 1, quantity: 1 })
db.orders.createIndex({ quantity: 1 })
db.orders.createIndex({ quantity: 1, type: 1 })
db.orders.createIndex(
  { item: 1, price: 1 },
  { partialFilterExpression: { price: { $gte: Decimal128("10") } } }
)

// Run queries so MongoDB caches plans
db.orders.find({ item: "abc", price: { $gte: Decimal128("10") } })
db.orders.find({ item: "abc", price: { $gte: Decimal128("5") } })
db.orders.find({ quantity: { $gte: 20 } })
db.orders.find({ quantity: { $gte: 5 }, type: "apparel" })

// Now $planCacheStats will return entries for these shapes

How It Works

MongoDB only caches plans after queries execute. Run representative find() patterns first—then inspect the cache to see which shapes and indexes the planner stored.

Example 2 — List all plan cache entries

mongosh
db.orders.aggregate([
  { $planCacheStats: {} },
  {
    $project: {
      planCacheKey: 1,
      planCacheShapeHash: 1,
      queryHash: 1,
      isActive: 1,
      works: 1,
      timeOfCreation: 1,
      host: 1,
      _id: 0
    }
  }
])

// One document per cached plan
// planCacheShapeHash groups same query shapes
// Different planCacheKey → different index choices
// Output varies by MongoDB version and engine (classic vs SBE)

How It Works

The bare minimum is { $planCacheStats: {} }. Add $project to hide verbose cachedPlan details until you need them—keeps output readable for beginners.

Example 3 — Filter by planCacheKey

mongosh
// Step 1: note a planCacheKey from the list-all query
// (yours will differ — copy from your output)

db.orders.aggregate([
  { $planCacheStats: {} },
  { $match: { planCacheKey: "B1435201" } },
  {
    $project: {
      planCacheKey: 1,
      planCacheShapeHash: 1,
      works: 1,
      isActive: 1,
      cachedPlan: 1,
      estimatedSizeBytes: 1,
      _id: 0
    }
  }
])

// Returns one cache entry matching that key
// Inspect cachedPlan for IXSCAN vs COLLSCAN stages
// Same shape + different indexes → different planCacheKey

How It Works

After listing all entries, drill into one plan by copying its planCacheKey from the first run. This is the official MongoDB pattern for inspecting a specific cached plan in detail.

📈 Practical Patterns

Find expensive plans and cross-check with explain().

Example 4 — Sort by works (find costly cached plans)

mongosh
db.orders.aggregate([
  { $planCacheStats: {} },
  { $match: { isActive: true } },
  { $sort: { works: -1 } },
  {
    $project: {
      planCacheKey: 1,
      planCacheShapeHash: 1,
      works: 1,
      indexFilterSet: 1,
      estimatedSizeBytes: 1,
      _id: 0
    }
  },
  { $limit: 5 }
])

// Highest works first → plans that cost more during trial
// indexFilterSet: true → index filter applied to this shape
// Investigate high-works + COLLSCAN combinations

How It Works

works reflects planner trial cost—not live query latency—but sorting by it helps prioritize which cached shapes deserve index review. Filter isActive: true to focus on plans the planner currently uses.

Example 5 — Cross-check with explain() for one query

mongosh
// Single-query plan (explain)
db.orders.find(
  { item: "abc", price: { $gte: Decimal128("10") } }
).explain("executionStats")

// Look at:
// queryPlanner.planCacheKey
// queryPlanner.planCacheShapeHash
// executionStats.executionStages.stage  → IXSCAN or COLLSCAN

// All cached plans ($planCacheStats)
db.orders.aggregate([
  { $planCacheStats: {} },
  {
    $match: {
      planCacheShapeHash: "478AD696"  // from explain output
    }
  },
  {
    $project: {
      planCacheKey: 1,
      works: 1,
      isActive: 1,
      _id: 0
    }
  }
])

// explain() → one query, live stats
// $planCacheStats → every cached shape on the collection

How It Works

Use explain() when a specific query is slow; use $planCacheStats when you want a bird’s-eye view of everything cached. The planCacheKey and shape hash in explain output link directly to cache entries.

🧠 How $planCacheStats Works

1

Queries run first

Application find(), aggregate(), and update() queries cause the planner to evaluate and cache winning plans.

Warm
2

Stage reads cache

$planCacheStats as stage 1 reads the in-memory plan cache for the target collection on the selected host.

Read
3

One doc per entry

Each cache entry becomes a pipeline document with keys, works, cachedPlan, and metadata like host and isActive.

Emit
=

Tuning insights

Downstream $match and $sort help DBAs focus on expensive or unexpected cached plans.

📝 Notes

  • $planCacheStats must be the first pipeline stage.
  • Cache contents depend on which mongod member you connect to—replica set members may differ.
  • MongoDB 8.0+ uses planCacheShapeHash; older versions expose queryHash for the same concept.
  • Plan cache entries can become inactive (isActive: false) when indexes change or the cache evicts stale plans.
  • Not allowed in transactions or $facet sub-pipelines.
  • Previous topic: $out. Next: $project.

Conclusion

$planCacheStats opens MongoDB’s plan cache for inspection—showing which query shapes are remembered, which indexes were chosen, and how costly each cached plan was to evaluate.

Use it alongside explain() and $indexStats for a complete tuning picture. Next: $project to shape documents in everyday pipelines.

💡 Best Practices

✅ Do

  • Run representative queries first so the cache reflects real workload shapes
  • $project key fields first; expand cachedPlan only when debugging
  • Sort by works to prioritize expensive cached plans
  • Cross-check suspicious entries with explain("executionStats")
  • Run on each replica set member during performance investigations

❌ Don’t

  • Put stages before $planCacheStats in the pipeline
  • Use $planCacheStats in application-facing API pipelines
  • Assume an empty cache means queries are fast—run queries to populate it first
  • Confuse works with live query latency—they measure different things
  • Expect identical cache contents across all replica set members

Key Takeaways

Knowledge Unlocked

Five things to remember about $planCacheStats

Use these points whenever you audit query plans on a collection.

5
Core concepts
🔑 02

planCacheKey

Filter target.

ID
03

works

Trial cost.

Metric
📈 04

Stage 1

First only.

Rule
🔍 05

+ explain()

Drill down.

Workflow

❓ Frequently Asked Questions

$planCacheStats returns information about cached query plans for the collection you run it against—one output document per plan cache entry. Each entry shows the query shape, chosen plan (cachedPlan), planCacheKey, works (plan cost during trial), and whether the entry is active.
$planCacheStats must be the first stage: db.orders.aggregate([{ $planCacheStats: {} }]). You can follow it with $match, $sort, or $project to filter or reshape results—but nothing may precede it.
planCacheShapeHash (queryHash in older versions) identifies the query shape—the filter pattern without considering indexes. planCacheKey includes both the shape and available indexes, so the same shape with different indexes can produce different planCacheKey values.
explain() shows the plan for one specific query you run right now. $planCacheStats lists all plans MongoDB has cached for the collection—useful for auditing what query shapes are stored and which cached plans are active.
On systems with authorization enabled, the user needs the planCacheRead privilege on the collection. It is a diagnostic stage intended for DBAs and performance tuning—not for application-facing pipelines.
No. $planCacheStats is not allowed inside transactions or $facet sub-pipelines. Run it as a top-level first stage on the target collection during maintenance or troubleshooting windows.
Did you know?

The same query shape can produce multiple cache entries with different planCacheKey values when indexes change—because the key includes both shape and index availability. After adding an index, old inactive entries may linger until evicted. See the official $planCacheStats docs.

Continue the Stages Series

Audit plans with $planCacheStats, then shape data with $project.

Next: $project →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful