Delete and TTL Indexes
Introduction
Removing data is as important as inserting it. MongoDB offers deleteOne, deleteMany, and collection-level drop. For temporary data—sessions, OTP codes, raw events—TTL indexes expire documents automatically. This chapter covers safe deletes, soft-delete patterns, and TTL setup.
Prerequisites
deleteOne and deleteMany
use hello_demo
// Remove one matching document
db.posts.deleteOne({ slug: "draft-notes" })
// Remove all unpublished drafts
db.posts.deleteMany({ published: false })
// DANGEROUS — empty filter deletes everything in collection
// db.posts.deleteMany({})Return value: deletedCount.
Warning
Always Filter in Production
Run countDocuments(filter) before deleteMany in ops scripts. Empty filter {} wipes the collection.
Soft Delete Pattern
Keep rows for audit; hide in queries:
db.posts.updateOne(
{ slug: "mongodb-basics" },
{ $set: { deletedAt: new Date() } }
)
// Application queries exclude soft-deleted
db.posts.find({ deletedAt: { $exists: false } })
db.posts.find({ deletedAt: null })Restore:
db.posts.updateOne(
{ slug: "mongodb-basics" },
{ $unset: { deletedAt: "" } }
)Relational equivalent: deleted_at column in MySQL UPDATE/DELETE patterns.
findOneAndDelete
Atomic delete and return removed doc:
db.sessions.findOneAndDelete({ token: "abc123" })Useful for one-time tokens or queue consumption patterns.
drop Collection and Database
// Remove entire collection and its indexes
db.temp_logs.drop()
// Remove database — all collections gone
db.getSiblingDB("hello_demo").dropDatabase()Irreversible on standalone without backup—use mongodump first — Insert and Import Export.
TTL Indexes (Time To Live)
MongoDB deletes documents when indexed date field + expireAfterSeconds elapses.
Session example
db.sessions.createIndex(
{ createdAt: 1 },
{ expireAfterSeconds: 3600 }
)
db.sessions.insertOne({
userId: "user-1",
token: "secret",
createdAt: new Date()
})Background thread removes expired documents—may lag slightly; not a real-time timer per millisecond.
OTP / verification codes
db.verification_codes.createIndex(
{ expiresAt: 1 },
{ expireAfterSeconds: 0 }
)
db.verification_codes.insertOne({
email: "ada@example.com",
code: "482910",
expiresAt: new Date(Date.now() + 10 * 60 * 1000)
})expireAfterSeconds: 0 expires at absolute time in expiresAt.
TTL rules
| Rule | Detail |
|---|---|
| Field type | Date or array of dates |
| Single field | One TTL index per collection |
| Not instant | Monitor lag under load |
| Replica sets | TTL runs on primary |
Compare Redis key TTL — Redis expiration for cache; MongoDB TTL for durable short-lived documents.
When to Use TTL vs Application Delete
| Approach | Best for |
|---|---|
| TTL index | Sessions, logs, temp tokens, cache-like documents in MongoDB |
| Scheduled job | Complex rules, archival to cold storage |
| Soft delete | User-facing "undo", compliance retention |
Cleanup Before Drop
Dev reset:
db.posts.deleteMany({})
db.posts.dropIndexes()Production: never dropDatabase without backup and approval.
Common Mistakes
| Mistake | Fix |
|---|---|
| TTL on string date | Convert to Date |
| Expect sub-second expiry | TTL is best-effort (~60s granularity historically) |
| TTL field updated accidentally | Resets expiry clock |
deleteMany({}) in script | Require explicit confirmation flag |
Post-Chapter Checklist
-
deleteOne/deleteManywith explicit filters - Soft delete with
deletedAt+ query filter - TTL index on
sessionsorverification_codes - You know
drop()vsdeleteMany
FAQ
Recover deleted documents?
Only from backup (mongorestore) or if soft-deleted.
TTL vs capped collections?
Capped collections are FIFO fixed size—legacy; TTL is more flexible.
Delete performance?
Large deletes may block; batch delete by _id range in maintenance window.
GDPR "right to be forgotten"?
deleteOne({ userId }) across collections or anonymize fields—design in Data modeling.
Redis DEL vs MongoDB delete?
Redis DEL removes cache keys instantly; MongoDB delete removes persisted documents—different layers.