Manage the complete index lifecycle with definitions, usage statistics, hidden-index experiments, correct drop/recreate renaming, and measured retirement decisions.

Create, Hide, Inspect, Rename/Drop, and Measure Indexes Without Guessing

Batch heterogeneous writes safely, interpret partial success, compare ordered and unordered execution, and use modern cross-namespace bulk APIs without assuming all-or-nothing behavior.

Intermediate120–160 minutesIndex lifecycle + hide/drop measurement labMongoDB 8.3.8 · mongosh 2.10.0Last reviewed: September 2026

Learning objectives

01

Inventory index definitions, sizes, usage counters, and query-plan evidence before changing index lifecycle state.

02

Use hidden indexes as a reversible query-planner experiment while remembering that hidden indexes remain fully maintained.

03

State correctly that MongoDB cannot rename an index in place and demonstrate the required drop/recreate workflow on a disposable index.

04

Retire an index only after query-shape evidence, node-aware usage observation, hide testing, and rollback criteria.

05

Measure write/storage overhead of unnecessary indexes without mistaking a micro-timing snippet for a benchmark.

Reproducible lab baseline

This lesson pins MongoDB Community Server 8.3.8 with mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim and mongosh 2.10.0. The server is a disposable standalone published only on loopback 127.0.0.1:27066. Authentication and TLS are disabled only for this isolated lab. Feature Compatibility Version (FCV) is observed but never changed. Default read/write concern and primary read preference apply. Atlas, Search, KMS, Enterprise Advanced, and paid services are not required. Runtime output shown as “expected” is documentation-derived because this generation environment has no Docker/mongod/mongosh runtime.

Evidence, not index folklore

Index behavior depends on query shape, projection, sort, data distribution, planner choice, cache state, topology, and patch version. The labs therefore inspect winningPlan, totalKeysExamined, totalDocsExamined, index definitions/sizes, and target query results. Small fixtures prove semantics, not production latency. Timing snippets are comparative demonstrations only, not benchmarks.

1. Index lifecycle is an operational change, not housekeeping

Indexes are persisted, replicated data structures. A safe lifecycle has evidence gates: create → inspect → observe use → test visibility → retire. The relevant evidence comes from getIndexes(), indexSizes/totalIndexSize(), query explains, and $indexStats. Usage count zero is a clue, not automatic proof of uselessness: rare incident queries, uniqueness, TTL, startup resets, hidden/unhidden resets, or another replica member can explain it.

Evidence What it answers Important limitation
getIndexes() What definitions/options exist? Does not tell you whether workload queries benefit.
indexSizes / totalIndexSize() How much index storage is allocated? Bytes vary with storage engine/compression/allocation.
$indexStats.accesses How many user operations used an index on this node since its stats epoch? Per-node; resets on restart, drop/recreate, and index modification.
explain("executionStats") How did one query shape execute? One query/input/cache state is not a workload trace.
hideIndex() What happens when planner cannot select this index? Index still consumes write, disk, and memory cost.

2. Seed an index portfolio and capture the baseline

bash · isolated Chapter 10 Lesson 5 lab setup
docker rm -f atlasmart-mongo-ch10-l5 2>/dev/null || truedocker volume rm atlasmart-mongo-ch10-l5-data 2>/dev/null || truedocker run -d --name atlasmart-mongo-ch10-l5 \  -p 127.0.0.1:27066:27017 \  -v atlasmart-mongo-ch10-l5-data:/data/db \  mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimmongosh "mongodb://127.0.0.1:27066/atlasmart?directConnection=true" --quiet --eval \'printjson({server:db.version(),hello:db.hello().isWritablePrimary}); printjson(db.getSiblingDB("admin").runCommand({getParameter:1,featureCompatibilityVersion:1}))' 
javascript · create workload and candidate indexes
const c=db.orders_ch10_l5;c.drop();const docs=[];for(let i=0;i<400;i++) docs.push({_id:i+1,tenantId:`tenant-${i%8}`,status:i%7===0?"cancelled":"paid",createdAt:new Date(Date.UTC(2026,7,1)+i*60000),totalCents:500+(i*97)%15000,customerId:`C-${i%70}`});c.insertMany(docs);c.createIndex({tenantId:1,status:1,createdAt:-1},{name:"idx_orders_workload"});c.createIndex({customerId:1},{name:"idx_customer_candidate"});c.createIndex({status:1},{name:"idx_status_candidate"});printjson(c.getIndexes());printjson({indexSizes:c.stats().indexSizes,totalIndexBytes:c.totalIndexSize()});
javascript · exercise known queries and read $indexStats
const c=db.orders_ch10_l5;for(let i=0;i<8;i++){ c.find({tenantId:`tenant-${i}`,status:"paid"}).sort({createdAt:-1}).limit(5).toArray();}c.find({customerId:"C-17"}).toArray();printjson(c.aggregate([{$indexStats:{}},{$project:{name:1,key:1,accesses:1,hidden:"$spec.hidden"}},{$sort:{name:1}}]).toArray());
Cluster caveat

$indexStats reports access metrics for the node where it runs. A replica set or sharded cluster needs node-aware collection of evidence. Its since field matters; an ops count without its measurement epoch is incomplete evidence.

3. Hide before drop when you need a reversible planner test

Hiding removes an index from query-planner consideration while keeping the structure fully updated. This makes unhide an immediate rollback. The hidden index cannot be forced with hint(). Hiding/unhiding also resets that index’s $indexStats, so record the pre-change counters first.

javascript · hide candidate, compare plan, reject hint, then unhide
const c=db.orders_ch10_l5;const shape=()=>c.find({customerId:"C-17"},{_id:0,customerId:1,totalCents:1}).explain("executionStats");print("before hide"); printjson(shape());printjson(c.hideIndex("idx_customer_candidate"));printjson(c.getIndexes());print("after hide"); printjson(shape());try{ c.find({customerId:"C-17"}).hint("idx_customer_candidate").toArray(); }catch(e){ printjson({hiddenHintRejected:true,code:e.code,message:e.message}); }printjson(c.unhideIndex("idx_customer_candidate"));print("after unhide"); printjson(shape());
Hidden does not mean free

A hidden index still consumes disk/cache, is updated on writes, and if unique still enforces uniqueness. Hiding measures read-plan dependency, not the write savings you would get after a real drop.

4. “Rename” means drop and recreate—there is no rename-index command

MongoDB index names are immutable after creation. To change a name, snapshot the exact definition/options, drop the old index, and recreate the same key/options with the desired name. That operation resets statistics and can create a period where the access path or invariant is absent. Therefore an index rename is not cosmetic in production.

javascript · disposable drop/recreate name change
const c=db.orders_ch10_l5;const old=c.getIndexes().find(x=>x.name==="idx_status_candidate");printjson({oldDefinition:old});print("MongoDB has no in-place index rename command.");c.dropIndex("idx_status_candidate");print(c.createIndex({status:1},{name:"idx_status_v2"}));printjson(c.getIndexes().filter(x=>x.key.status===1));
Production caution

If the index is unique or performance-critical, a drop/recreate name change can remove an invariant or access path during the build interval. If the name has no operational value, keeping the old name is often safer than rebuilding merely for aesthetics.

5. Evidence-driven retirement: observe → hide → drop

After a representative observation window, hide the candidate and verify application query plans, latency tails, CPU/I/O, and error rates. Only then drop it. The example uses the disposable status index; it does not claim that a short lab observation is equivalent to a production traffic window.

javascript · hide and retire a disposable candidate index
const c=db.orders_ch10_l5;print("candidate definitions before retirement");printjson(c.getIndexes().filter(x=>["idx_customer_candidate","idx_status_v2"].includes(x.name)));printjson(c.hideIndex("idx_status_v2"));print("observe application workload before permanent drop; hidden indexes still incur write cost");printjson(c.dropIndex("idx_status_v2"));printjson({remaining:c.getIndexes().map(x=>x.name),indexSizes:c.stats().indexSizes,totalIndexBytes:c.totalIndexSize()});
Rollback boundary

Before drop, rollback is unhideIndex() and is immediate because the index stayed maintained. After drop, rollback is a new index build with disk, CPU, replication, and time cost. That difference is why hide-first is operationally valuable.

6. Measure the cost of index explosion

The anti-pattern “index every field just in case” can degrade write-heavy workloads long before a hard index-count limit matters. The following isolated comparison writes identical documents to a lean collection and one with five secondary indexes. Interpret only the direction and inspect index bytes; repeat with production-like data if the decision matters.

javascript · compare lean vs over-indexed write maintenance
const lean=db.orders_ch10_l5_lean;const heavy=db.orders_ch10_l5_heavy;lean.drop(); heavy.drop();heavy.createIndexes([{a:1},{b:1},{c:1},{a:1,b:1,c:1},{d:1,e:1}]);function docs(n){const a=[];for(let i=0;i<n;i++)a.push({_id:i,a:i%20,b:i%50,c:i%7,d:i%100,e:i%3});return a;}const batch=docs(5000);let t=Date.now(); lean.insertMany(batch); const leanMs=Date.now()-t;t=Date.now(); heavy.insertMany(batch); const heavyMs=Date.now()-t;printjson({leanMs,heavyMs,leanIndexBytes:lean.totalIndexSize(),heavyIndexBytes:heavy.totalIndexSize()});
Not a benchmark

Date.now() around one insert batch ignores warm-up, cache state, checkpointing, fsync behavior, concurrency, replication, and tail latency. Use workload generators and production-like topology for performance claims.

7. Build/drop operations need capacity and failure plans

Index builds on populated collections consume CPU, memory, disk, I/O, and replicated work. MongoDB 7.1+ improved index-build failure reporting and supports a minimum available disk-space guard; exact build behavior remains version/topology sensitive. Dropping a live needed index can cause immediate query regressions, while dropping an in-progress build can abort the build across replica members. Inspect active operations and capacity before treating build/drop as a routine migration.

Security boundary

Creating, hiding, modifying, and dropping indexes are administrative capabilities. Application runtime credentials should not casually possess index-management privileges. Search indexes are a separate subsystem and are not managed by these ordinary B-tree index methods.

8. Verification, cleanup, and production judgment

Verification checklist

  • Index definitions and physical sizes are captured before lifecycle changes.
  • $indexStats is read with its per-node and reset semantics documented.
  • Hiding changes planner eligibility but does not stop maintenance.
  • A hidden index cannot be hinted.
  • The lesson uses drop/recreate—not a nonexistent rename command—to change an index name.
  • Permanent drop occurs only after a hide/observation step on a disposable candidate.
  • The over-indexed write test is explicitly treated as a demonstration rather than a benchmark.

Production judgment. The best index portfolio is the smallest set that protects required invariants and recurring query shapes at acceptable write/storage cost. Measure per-node use, query plans, tail latency, storage/cache pressure, and build/recovery headroom. Hide for reversible read-path testing; remember hiding never yields write savings. Treat drop/recreate as a real migration, especially for unique indexes. This chapter establishes the lifecycle foundation needed for Chapter 11’s specialized partial, sparse, TTL, wildcard, hashed, text, and geospatial index families.

bash · cleanup / full reset
docker rm -f atlasmart-mongo-ch10-l5docker volume rm atlasmart-mongo-ch10-l5-data

Check your understanding

  1. Why is $indexStats accesses.ops=0 not enough by itself to drop an index?
  2. What changes when an index is hidden?
  3. Does hiding reduce write amplification?
  4. How do you rename an ordinary MongoDB index?
  5. Why is unhide a cheaper rollback than recreating a dropped index?
Review the answers

1. Statistics are per-node and epoch-bound, and an index may serve rare queries or enforce properties not reflected by ordinary user-read counts.

2. The query planner cannot select it and it cannot be hinted, but the index remains present and maintained.

3. No. Hidden indexes are still updated on writes and consume storage/cache.

4. You cannot rename it in place; capture the definition, drop it, and recreate it with a new name.

5. The hidden index is already fully built and maintained, so unhiding makes it immediately planner-eligible; a dropped index must be rebuilt.

Authoritative references

  • MongoDB 8.3 release notes — Current 8.3 baseline and patch-sensitive behavior; re-check before reproduction.
  • Indexes overview — Index concepts, names, build considerations, and index-management overview.
  • Explain results — IXSCAN/FETCH/COLLSCAN evidence, covered-query plans, keys/documents examined, and execution statistics.
  • Measure index use — Using $indexStats and explain evidence instead of intuition to manage indexes.
  • mongosh release notes — mongosh version used for chapter commands.
  • Hidden indexes — Planner invisibility, continued maintenance/uniqueness, hint restriction, FCV requirement, and stats reset behavior.
  • $indexStats — Per-node usage counters, measurement epoch, reset conditions, and index specs.
  • Drop an index — Hide-first recommendation and drop procedures.
  • Create indexes — Index options, hidden-option changes, build behavior, and current index-build considerations.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.