Using MongoDB TTL Indexes to Automatically Expire Stale Data
Learn how MongoDB TTL indexes automatically remove stale data after a configurable time‑to‑live, with a step‑by‑step example, limitations, and verification tips.
11 Apr 2026, 08:23 UTC

Problem: stale data accumulating in a collection
Applications that store temporary information—such as user sessions, logs, or cached results—often need a way to remove old records without manual cleanup scripts. Left unchecked, these collections grow indefinitely, increasing storage costs and slowing queries.
Thesis: a TTL index lets MongoDB delete documents automatically after a configurable time‑to‑live, removing the need for external purge jobs.
How TTL indexes work
A TTL index is a special single‑field index created on a BSON date (or timestamp) field. When you add the expireAfterSeconds option, MongoDB’s background TTL monitor (which runs roughly every 60 seconds) checks each document: if the current time exceeds the field value plus the expireAfterSeconds interval, the document is queued for deletion.
Because the monitor runs periodically, the actual deletion can lag up to 60 seconds behind the theoretical expiry time.
Creating a TTL index – step‑by‑step example
Assume we want to store user sessions in a collection called sessions and automatically delete them after one hour of inactivity.
- Ensure you have the right privileges – you need
readWriteon the database (ordbAdminto create indexes). - Create the collection if it does not exist (MongoDB creates it lazily on first insert, but we show it explicitly for clarity):
use myapp db.createCollection("sessions") - Insert a sample session document with a date field that records the last activity time. For the test we set it to a time in the past so it should expire immediately:
db.sessions.insertOne({ userId: "alice", token: "abc123", lastAccess: new Date("2026-09-01T12:00:00Z") }) - Create the TTL index on the
lastAccessfield, specifying an expireAfterSeconds of 0 (meaning “expire now”):db.sessions.createIndex({ lastAccess: 1 }, { expireAfterSeconds: 0 }) - Verify the index definition:
db.sessions.getIndexes()
You should see an entry containing"expireAfterSeconds" : 0. - Wait for the background monitor – because it runs every ~60 seconds, give it at least 70 seconds before checking.
- Check that the document was removed:
db.sessions.countDocuments({})The count should be 0. You can also rundb.sessions.find()to confirm no rows remain.
In a production setting you would use a realistic expireAfterSeconds value, e.g. 3600 for one hour, and set lastAccess to the current time on each request.
Trade‑offs and limitations
- Single‑field only – a TTL index cannot be compound; trying to create one on multiple fields throws an error.
- Field must be a BSON date/timestamp – if the field is missing, null, or of another type, the document is ignored by the TTL process, which can lead to accidental data retention.
- Background monitor latency – deletions are not instantaneous; plan for up to a minute of delay.
- No manual control per document – you cannot exempt specific rows from expiration without moving them to another collection or using a sentinel far‑future date.
Practical verification steps
After creating a TTL index with a non‑zero expireAfterSeconds:
- Insert a document with a known timestamp (e.g.,
new Date()). - Record the expected expiry time:
now + expireAfterSeconds. - After waiting slightly longer than the interval plus a safety margin (e.g., expireAfterSeconds + 70 seconds), run
db.collection.countDocuments({}). - If the count dropped as expected, the TTL mechanism is working. If not, re‑check the index definition and ensure the indexed field is present and correctly typed.
Actionable closing
TTL indexes provide a low‑maintenance way to keep temporary data collections bounded. By pairing a date field with the expireAfterSeconds option, you offload expiration logic to MongoDB’s built‑in monitor. Remember the single‑field constraint, verify the field type, and account for the up‑to‑60‑second deletion lag. Test the setup with a short expiry value (like 0 or a few seconds) in a staging environment before deploying to production, and monitor collection size or the database profiler to confirm that delete operations are being triggered as expected.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.