MongoDB from Basics to Production · MongoDB in Production
TTL Indexes, Change Streams and Write Concern
Expire documents automatically with TTL indexes, react to changes in real time with change streams, and choose durability with write and read concerns.
What you'll build
Three production features: sessions that expire on their own with a TTL index, a change stream that receives every new order as it's inserted (for cache invalidation or notifications), and writes that wait for the majority of replica set members.
The idea
- A TTL index (
expireAfterSeconds) deletes documents after a date field plus the TTL; a background task runs about every 60 seconds. - Change streams (
watch()) deliver inserts, updates and deletes in order, resumable with a resume token. They need a replica set. - Write concern
w: "majority"acknowledges a write only when most members have it, so it survives a failover; read concern"majority"reads only such data.
The code
import os
import threading
import time
from datetime import datetime, timezone
from pymongo import MongoClient
from pymongo.write_concern import WriteConcern
client = MongoClient(os.environ["MONGO_URL"], serverSelectionTimeoutMS=3000)
db = client["ops_example"]
db.sessions.create_index("lastSeen", expireAfterSeconds=1800)
info = next(i for i in db.sessions.list_indexes() if i["name"] == "lastSeen_1")
print("TTL index:", dict(info["key"]), "expireAfterSeconds =", info["expireAfterSeconds"])
orders = db.get_collection("orders", write_concern=WriteConcern(w="majority", wtimeout=5000))
seen = []
with db.orders.watch([{"$match": {"operationType": "insert"}}]) as stream:
def place_orders():
time.sleep(0.5)
for i in (1, 2):
orders.insert_one({"_id": i, "total": 10 * i, "at": datetime(2026, 1, 15, tzinfo=timezone.utc)})
threading.Thread(target=place_orders).start()
for change in stream:
seen.append(change["fullDocument"]["_id"])
print("change stream:", change["operationType"], change["fullDocument"]["_id"], change["fullDocument"]["total"])
if len(seen) == 2:
token = stream.resume_token
break
print("resume token saved:", token is not None)
client.drop_database("ops_example")
Output
TTL index: {'lastSeen': 1} expireAfterSeconds = 1800
change stream: insert 1 10
change stream: insert 2 20
resume token saved: TrueRun this on your computer — see “Get Started” for setup.
Common mistakes
- Expecting TTL deletion at an exact second; it runs periodically, so filter expired documents in queries if precision matters.
- Polling a collection for changes instead of using change streams.
- Not storing the resume token, so a restarted consumer misses changes.
w: 1for critical data, risking loss of acknowledged writes on failover.
Key points
- TTL indexes expire documents automatically.
- Change streams deliver ordered, resumable change events.
- Use majority write concern for data that must survive failovers.
Further reading: TTL indexes · Change streams · Write concern
Practice questions
How precise is TTL deletion?
Answer
Not exact: a background task removes expired documents about every 60 seconds, so documents may live slightly longer.
Why store a change stream's resume token?
Answer
So a restarted consumer can continue from where it stopped without missing or reprocessing events.
What does write concern w: 'majority' guarantee?
Answer
The write is acknowledged only after most replica set members have it, so it won't be rolled back after a failover.
Open in the app to mark lessons complete, take notes and track your progress.