Reacting to data changes
|
This section documents the current MongoDB 8.x server line as published at the MongoDB Server Manual, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Atlas Search, Atlas Vector Search, and parts of encryption and backup) are Atlas-only — they are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official manual before being relied on in production, since MongoDB iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
A change stream is an ordered feed of the data changes happening in a collection, a database, or a whole deployment. Instead of polling, an application opens the stream and receives a change-event document for every insert, update, replace, delete, and drop as it is applied. Change streams read from the replica-set oplog, so a replica set or sharded cluster is required (see Replication).
Opening a stream with watch
db.collection.watch() opens a stream scoped to one collection. db.watch() widens it to every collection in
a database, and Mongo.watch() (the deployment-level form) covers every database except the internal
admin, local, and config. The returned cursor blocks until the next event is available.
// One collection
const stream = db.orders.watch();
while (stream.hasNext()) {
const event = stream.next();
print(`${event.operationType} on ${event.ns.coll} -> ${JSON.stringify(event.documentKey)}`);
}
See Change Streams in the manual, and
db.collection.watch() for the
method signature.
The change-event document
Every event is a document with a common shape. The most-used fields:
-
_id— the resume token for this event (see Resuming a stream). -
operationType—insert,update,replace,delete,drop,rename,invalidate, and others. -
clusterTime— the oplog timestamp at which the change was applied. -
ns— the namespace, as{ db, coll }. -
documentKey— the_id(plus shard-key fields on a sharded collection) of the affected document. -
fullDocument— forinsertandreplace, the whole new document; forupdate, present only when the stream was opened withfullDocument: "updateLookup". -
updateDescription— forupdateonly:{ updatedFields, removedFields, truncatedArrays }describing exactly what changed.
{
"_id": { "_data": "8264A1F2C7000000012B022C0100296E5A..." },
"operationType": "update",
"clusterTime": { "$timestamp": { "t": 1688472263, "i": 1 } },
"ns": { "db": "shop", "coll": "orders" },
"documentKey": { "_id": { "$oid": "64a1f2c7e3b0a1f2c7e3b0a1" } },
"updateDescription": {
"updatedFields": { "status": "SHIPPED", "shippedAt": { "$date": "2023-07-04T12:04:23Z" } },
"removedFields": [],
"truncatedArrays": []
}
}
See Change Events for every field of every operation type.
Resuming a stream
Each event carries a resume token in its _id. Persist the token as you process events, and after a
disconnect reopen the stream from where you left off instead of from "now":
-
resumeAfter: <token>— resume starting with the event after the given token. The token’s event must still be in the oplog. -
startAfter: <token>— likeresumeAfter, but it also works to restart after aninvalidateevent (for example when the watched collection was dropped and recreated). -
startAtOperationTime: <timestamp>— start from a cluster time rather than a token; useful for a first start at a known point.
let resumeToken = loadSavedToken(); // application-provided
const stream = db.orders.watch([], resumeToken ? { resumeAfter: resumeToken } : {});
while (stream.hasNext()) {
const event = stream.next();
handle(event);
resumeToken = event._id;
saveToken(resumeToken);
}
Change streams require readConcern: "majority" (the effective default): an event is only delivered once its
change is majority-committed and therefore cannot be rolled back. See
Resume a Change Stream.
Filtering with an aggregation pipeline
watch() accepts an aggregation pipeline as its first argument. The stages run on the server against each
change event, so the application receives only the events it cares about and only the fields it needs.
$match, $project, $addFields, $replaceRoot, and $redact are supported.
const stream = db.orders.watch([
{ $match: {
operationType: { $in: ["insert", "update"] },
"fullDocument.region": "EU"
} },
{ $project: { "fullDocument.paymentDetails": 0 } }
], { fullDocument: "updateLookup" });
See Aggregation pipeline for the stage semantics and Modify Change Stream Output.
Seeing the whole document
By default an update event carries only the delta in updateDescription. Two options add the full document:
-
fullDocument: "updateLookup"— attach a current copy of the document (fetched at lookup time, so it may already reflect later changes) asfullDocument. -
fullDocument: "whenAvailable"/"required"together withfullDocumentBeforeChange: "whenAvailable"/"required"— include the document image from before the change asfullDocumentBeforeChange. This requireschangeStreamPreAndPostImagesto be enabled on the collection withcollMod.
db.runCommand({ collMod: "orders", changeStreamPreAndPostImages: { enabled: true } });
const stream = db.orders.watch([], {
fullDocument: "required",
fullDocumentBeforeChange: "required"
});
Typical uses
-
Cache invalidation — evict or refresh a cached entry the moment its source document changes.
-
ETL and streaming — feed a downstream warehouse, search index, or message queue with a continuous, resumable stream of changes rather than periodic full scans.
-
Notifications — push in-app or email alerts when a watched document reaches a state (for example an order becoming
SHIPPED).
Because the stream is resumable, a consumer that stores its last token can stop and restart without losing or double-counting events, as long as the token is still within the oplog window.