Treat TTL as asynchronous background retention: measure eligibility, deletion lag, metrics, Date/array semantics, collMod changes, and operational deletion cost.

TTL Indexes: Expiration Semantics, Background Deletion, and Time-Based Retention

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.

Intermediate110–155 minutesTTL timing + metrics + collMod labMongoDB 8.3.8 · mongosh 2.10.0Last reviewed: September 2026

Learning objectives

01

Distinguish logical expiration eligibility from the later background delete operation.

02

Use absolute-date TTL with expireAfterSeconds:0 and reason about missing, wrong-type, and date-array fields.

03

Observe TTL progress through collection state and serverStatus metrics instead of assuming a precise deletion instant.

04

Change a TTL interval with collMod while understanding mass-deletion and fragmentation risk.

05

Carry TTL semantics into replica-set, retention, security, and capacity decisions.

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:27068. 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, KMS, and Enterprise Advanced are not mandatory. Runtime output shown as “expected” is documentation-derived because this generation environment has no Docker/mongod/mongosh runtime.

Evidence before index enthusiasm

TTL is a deletion mechanism whose timing depends on a background process. The lab therefore records timestamps, remaining documents, and metrics.ttl; it never treats “expired at T” as proof that the document is physically absent at T.

1. Expiration time is not deletion time

A time-to-live (TTL) index is a special single-field index on a Date field (or an array containing Dates). A document becomes eligible after the configured threshold, but MongoDB removes eligible documents through a background TTL process. The TTL index does not guarantee that expired data is deleted immediately; the current documentation states that the monitor runs periodically and deletion can lag under workload. Therefore TTL is suitable for retention, sessions, caches, and cleanup—not for a security rule that requires a record to become inaccessible at an exact millisecond.

Replica-set boundary

On replica sets, the TTL background thread deletes only on the primary. Secondaries replicate those delete operations. This standalone lab demonstrates eligibility and deletion mechanics but cannot reproduce election/failover behavior.

2. Seed past, future, missing, wrong-type, and array dates

bash · isolated Chapter 11 Lesson 2 lab setup
docker rm -f atlasmart-mongo-ch11-l2 2>/dev/null || truedocker volume rm atlasmart-mongo-ch11-l2-data 2>/dev/null || truedocker run -d --name atlasmart-mongo-ch11-l2 \  -p 127.0.0.1:27068:27017 \  -v atlasmart-mongo-ch11-l2-data:/data/db \  mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimmongosh "mongodb://127.0.0.1:27068/atlasmart?directConnection=true" --quiet --eval \'printjson({server:db.version(),hello:db.hello().isWritablePrimary}); printjson(db.getSiblingDB("admin").runCommand({getParameter:1,featureCompatibilityVersion:1}))' 
javascript · create documents with distinct expiration semantics
const c=db.sessions_ch11_l2;c.drop();const now=new Date();c.insertMany([ {_id:1,sessionId:"expired-past",createdAt:now,expiresAt:new Date(now.getTime()-120000)}, {_id:2,sessionId:"near-future",createdAt:now,expiresAt:new Date(now.getTime()+30000)}, {_id:3,sessionId:"far-future",createdAt:now,expiresAt:new Date(now.getTime()+3600000)}, {_id:4,sessionId:"missing-expiry",createdAt:now}, {_id:5,sessionId:"wrong-type",createdAt:now,expiresAt:"2026-09-02T12:00:00Z"}, {_id:6,sessionId:"array-earliest-wins",createdAt:now,expiresAt:[new Date(now.getTime()+3600000),new Date(now.getTime()-60000)]}]);printjson({now,docs:c.find().sort({_id:1}).toArray()});
javascript · create absolute-time TTL index and inspect metrics
const c=db.sessions_ch11_l2;print(c.createIndex({expiresAt:1},{name:"ttl_expiresAt",expireAfterSeconds:0}));printjson(c.getIndexes());printjson(db.serverStatus().metrics.ttl);
text · what the fixture is designed to prove
Expected semantics, not an exact wall-clock schedule:- expireAfterSeconds:0 means the Date value itself is the expiration time.- expired-past and array-earliest-wins are immediately eligible, but deletion is asynchronous.- array-earliest-wins uses the earliest Date in the indexed array for the threshold.- missing-expiry never expires through this TTL index.- wrong-type is not a Date, so it does not expire through this TTL index.- near-future becomes eligible after its Date passes, but physical deletion can occur later.- TTL deletions are observable in serverStatus().metrics.ttl and are performed by the primary in a replica set.- A compound TTL design is invalid: TTL is a single-field index property.

3. Poll instead of pretending TTL is immediate

The bounded polling loop waits at most about 130 seconds. It is intentionally tolerant: the expected invariant is eventual removal of eligible Date-valued documents, not a fixed number of seconds after eligibility.

javascript · observe deletion lag and TTL counters
const c=db.sessions_ch11_l2;const start=new Date();for(let attempt=0;attempt<26;attempt++){ const rows=c.find({},{_id:0,sessionId:1,expiresAt:1}).sort({sessionId:1}).toArray(); const ttl=db.serverStatus().metrics.ttl; printjson({attempt,at:new Date(),remaining:rows,ttl}); if(!c.findOne({sessionId:"expired-past"}) && !c.findOne({sessionId:"array-earliest-wins"})) break; sleep(5000);}printjson({elapsedMs:new Date()-start,remaining:c.find({},{_id:0,sessionId:1,expiresAt:1}).sort({sessionId:1}).toArray()});
Do not turn TTL lag into an authorization gap

If a token, entitlement, or legal hold must stop being usable at an exact business deadline, enforce that deadline in the read/authorization predicate as well. TTL can clean up storage later.

4. Changing retention is an operational event

collMod can convert an existing single-field index to TTL or change expireAfterSeconds. Reducing the interval may make a large historical population immediately eligible, creating delete load and storage fragmentation. In production, estimate the eligible population first and consider batched manual cleanup before shortening retention.

javascript · change TTL interval without rebuilding the index
const c=db.sessions_ch11_l2;printjson(db.runCommand({ collMod:"sessions_ch11_l2", index:{name:"ttl_expiresAt",expireAfterSeconds:120}}));printjson(c.getIndexes());

5. Deliberately wrong: compound TTL assumption

TTL is not an option you can attach meaningfully to an arbitrary compound index. The retention index itself must be single-field; design other query indexes separately.

javascript · attempt an invalid compound TTL index
const c=db.sessions_ch11_l2;try{ print(c.createIndex({createdAt:1,expiresAt:1},{name:"wrong_compound_ttl",expireAfterSeconds:60}));}catch(e){ printjson({name:e.name,code:e.code,message:e.message}); }printjson(c.getIndexes());

6. Verification, cleanup, and production judgment

Verification checklist

  • The TTL index metadata shows expireAfterSeconds.
  • Past Date values are eligible before future Date values.
  • An array uses its earliest Date to calculate eligibility.
  • Missing and non-Date fields are not treated as ordinary TTL dates.
  • The polling output distinguishes eligibility from actual deletion time.
  • metrics.ttl is captured before/after observation.
  • The interval is modified with collMod, not by recreating an equivalent index.

Production judgment. TTL reduces application cleanup code but creates write/delete I/O and potentially bursty reclaim work. Monitor delete metrics, replication lag, cache/disk pressure, and the population near the retention boundary. Schema validation can ensure the TTL field is a Date. Test retention changes on production-like volumes, define rollback before shortening intervals, and never confuse replicated TTL deletion with backup or archival. Lesson 3 moves from time-based membership to schema-based flexibility: wildcard indexes can index unknown paths, but that convenience also expands maintenance scope.

bash · cleanup / full reset
docker rm -f atlasmart-mongo-ch11-l2docker volume rm atlasmart-mongo-ch11-l2-data

Check your understanding

  1. Does a document disappear exactly when its TTL expires?
  2. What does expireAfterSeconds:0 mean for a Date field?
  3. What happens when the TTL field is an array of Dates?
  4. Which replica-set member performs TTL background deletes?
  5. Why can shortening a TTL interval be risky?
Review the answers

1. No. Expiration makes it eligible; a background process deletes it later, and lag can exceed the nominal monitor interval under load.

2. The Date value itself is the absolute expiration time.

3. MongoDB uses the earliest Date value to compute the expiration threshold.

4. The primary; secondaries replicate the resulting deletes.

5. A large historical population can become eligible at once, creating delete load, replication work, and storage fragmentation.

Authoritative references

  • MongoDB 8.3 release notes — Current 8.3 baseline and patch-sensitive behavior; re-check before reproduction.
  • Index types — Current index-family overview and boundaries.
  • Explain results — Planner and execution evidence used throughout this chapter.
  • mongosh changelog — mongosh version used for chapter commands.
  • TTL indexes — Expiration, background deletion, metrics, replica behavior, restrictions, and collMod changes.
  • Schema validation — Useful for enforcing a consistent Date-valued TTL field.

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.