MongoDB $planCacheStats Stage

Beginner
5 min read
Updated: Oct 2026
4 examples
Live preview

What Is $planCacheStats?

$planCacheStats lists cached query plans for a collection—which query shapes MongoDB remembers, which plan was chosen, and how costly that plan looked during evaluation.

Remember
$planCacheStats = "show cached plans for THIS collection"

db.orders.aggregate([
  { $planCacheStats: {} }
])

MUST be the FIRST stage
one output doc per cache entry
planCacheKey → identity of the cached plan
works        → cost measured during plan trial
cachedPlan   → chosen execution plan (often IXSCAN)

explain() → plan for ONE query now
$planCacheStats → ALL cached plans on the collection
pair with $indexStats for index usage
UsePrefer
Audit cached plans on a collection$planCacheStats
Plan for one specific queryexplain()
Index usage counters$indexStats

How to Use $planCacheStats in MongoDB

Run it first on the collection, then filter or project the cache entries.

  1. Warm the cache: run a few real find() / aggregate queries first.
  2. Start with { $planCacheStats: {} } (must be stage 1).
  3. Optional: $match on planCacheKey or isActive.
  4. Optional: $project / $sort / $limit for a readable report.
FieldRole
planCacheKeyKey for this cached plan (shape + indexes)
planCacheShapeHashQuery shape hash (older: queryHash)
worksWork units from plan selection / trial
cachedPlanChosen plan tree (e.g. IXSCAN / COLLSCAN)
isActiveWhether this entry is the active cached plan

Pseudocode

Pseudocode
assert: $planCacheStats is stage 1
assert: run on the target collection

entries = plan cache for this collection

for each entry:
  emit {
    planCacheKey,
    planCacheShapeHash,
    works,
    isActive,
    cachedPlan,
    createdFromQuery / query shape fields, …
  }

// later $match / $project reshape the list
// empty cache → empty result (run queries first)

Cheat Sheet

Keep these facts handy during performance reviews.

TopicDetail
TypeDiagnostic aggregation stage
PositionMust be first
ScopeOne collection’s plan cache
PrivilegeOften planCacheRead
Not allowed inTransactions, $facet sub-pipelines
vs explain()All cached plans vs one live plan
Empty resultCache not warmed yet

Live Preview

Filter a sample plan-cache snapshot (teaching model—not a live mongod connection).

Sample entries show planCacheKey, stage type, and works.

Result
A1F3  IXSCAN   works: 12   active
B2C4  IXSCAN   works: 18   active
C9D0  COLLSCAN works: 120  inactive

Worked Walkthrough — Warm Cache, Then Inspect

Goal: run a query so MongoDB caches a plan, then list cache entries.

Calculation
1. Create index + run find({ status: "active" })
   → planner picks a plan and may cache it

2. db.orders.aggregate([
     { $planCacheStats: {} }   ← first stage
   ])

3. Look for:
   planCacheKey  → identify the entry
   works         → lower often means cheaper trial
   cachedPlan    → IXSCAN vs COLLSCAN

4. Optional: $project / $sort for a short report

MongoDB Examples

We seed a small orders collection in Example 1, warm the plan cache with real queries, then inspect cache metadata in the following examples.

Example 1 — Sample Data

Three orders, an index on status, then warm the plan cache with two different query shapes.

mongosh
db.orders.drop()
db.orders.insertMany([
  { status: "active", total: 40 },
  { status: "active", total: 15 },
  { status: "closed", total: 99 }
])

db.orders.createIndex({ status: 1 })

// Warm the plan cache (planner may store a plan per shape)
db.orders.find({ status: "active" }).toArray()
db.orders.find({ status: "closed" }).toArray()

How the Data Is Stored in MongoDB

Confirm the documents with a simple read:

mongosh
db.orders.find().sort({ status: 1, total: 1 })

Read the important pieces like this:

FieldExamplePlain English
status"active" / "closed"Indexed filter in the warm-up finds
total15, 40, 99Order amount (not used in cache demo)
status index{ status: 1 }Lets the planner pick IXSCAN instead of COLLSCAN

How It Works

  1. Seed data. Three orders give the planner something to scan.
  2. Add an index. createIndex({ status: 1 }) changes plan choices.
  3. Warm the cache. Each distinct find() shape can create a cache entry.
  4. Next examples. $planCacheStats lists those entries—not the order rows.

Example 2 — List Cache

Goal: after Example 1, list every cached plan for orders.

mongosh
db.orders.aggregate([
  { $planCacheStats: {} }
])

How It Works

  1. First stage. Empty {} must lead the pipeline on orders.
  2. One doc per entry. Each row is a cached plan, not an order document.
  3. Needs warm-up. Run Example 1 first or you may get an empty result.
  4. Live metadata. Output reflects your server’s plan cache right now.

Example 3 — Clean Report

Goal: after Example 1, keep only the fields you need and sort by trial cost (works).

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

How It Works

  1. List first. Stage 1 still emits full cache documents.
  2. Project. Drop bulky plan trees for a quick audit.
  3. Sort. Highest works first = focus candidates.
  4. Limit. Cap the report so dashboards stay small.

Example 4 — Filter Active

Goal: after Example 1, keep only active cache entries, then peek at the chosen stage type.

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

// Tip: clear cache in mongosh when testing plan changes:
// db.orders.getPlanCache().clear()

How It Works

  1. Match after. Filter cache docs with isActive: true.
  2. Project stage. Surface whether the plan looks like IXSCAN.
  3. Tune loop. Pair with explain() on the real query.
  4. Clear when testing. getPlanCache().clear() resets entries.

Edge Cases & Pitfalls

Check these before relying on plan-cache reports.

order

Must be the first stage

Put $match / $project after it. Nothing may precede $planCacheStats.

empty

Empty cache ⇒ empty result

Run real queries first. A fresh collection often has no cache entries yet.

scope

Not for $facet or transactions

Run as a top-level first stage during diagnostics—not inside app transaction logic.

fields

Field names can vary by version

Older servers may show queryHash instead of planCacheShapeHash. Check one raw document first.

Performance Notes

ApproachCostBest for
Default $planCacheStatsLight metadata readQuick audits
$project + $limitSmaller payloadsDashboards
Polling every secondExtra admin loadAvoid unless needed
Pair with $indexStatsTwo diagnostic passesIndex + plan reviews

Prefer slim projections. Use explain() when you need deep detail for one query shape.

Key Takeaways

  • Role: list cached query plans for one collection.
  • Rules: must be first; warm the cache before inspecting.
  • Focus: planCacheKey, works, cachedPlan, isActive.
  • Pair with: explain() and $indexStats for full tuning.

One line: $planCacheStats answers “which query plans has MongoDB cached for this collection?”

Frequently Asked Questions

$planCacheStats returns one document per cached query plan for the collection you run it against. Entries include planCacheKey, query shape info, cachedPlan, works (trial cost), and whether the entry is active.
It must be the first stage: db.orders.aggregate([{ $planCacheStats: {} }]). You can follow it with $match, $sort, or $project—but nothing may precede it.
planCacheShapeHash (queryHash on older versions) identifies the query shape. planCacheKey includes the shape plus available indexes, so the same shape with different indexes can produce different keys.
explain() shows the plan for one query you run now. $planCacheStats lists all plans MongoDB has cached for the collection—useful for auditing stored shapes and active plans.
With authorization enabled, you need planCacheRead on the collection. It is a diagnostic stage for DBAs and 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.

Next: $project

Include, exclude, and reshape fields as documents flow through the pipeline.

$project tutorial →

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.

8 people found this page helpful