Ch. 12 · MongoDB

MongoDB Change Streams and Resume Tokens

React to inserts and updates with change streams, persist resume tokens, and handle invalidate and failover correctly.

~2 min readadvancedupdated Oct 5, 2026

A change stream watches a collection, database or deployment and delivers every change as a document. It is built on the replica set oplog, so it requires a replica set, and each event carries a resume token you can persist so a restart continues without missing or replaying changes.

Before you start

You should be comfortable with the aggregation pipeline and MongoDB replica sets. This article assumes the Node.js driver, though the concepts apply to any driver.

Step-by-step walkthrough

Step 1: Open a change stream with a filter

Call collection.watch(pipeline) and use a $match stage to limit the operation types you care about. Filtering server-side avoids shipping events you will discard, and you can request fullDocument: 'updateLookup' to include the current document on updates.

Step 2: Persist the resume token

Every change includes _id, the resume token. Store it after you have processed the change, and pass resumeAfter (or startAfter) when reopening. Persist only after processing so a crash reprocesses rather than skips work, which fits idempotent consumers.

Step 3: Handle invalidate and failover

If the watched collection is dropped, the stream emits an invalidate event and closes; reopen it. During a replica-set failover the stream may error, so wrap it in a retry loop that reopens with the last token. Bound the loop and alert on repeated failures.

Worked scenario

The loop writes each change and records its resume token.

const pipeline = [{ $match: { operationType: { $in: ['insert', 'update'] } } }];
const changeStream = db.collection('orders').watch(pipeline, {
  fullDocument: 'updateLookup',
});
for await (const change of changeStream) {
  await handle(change.fullDocument);
  await saveResumeToken(change._id);
}
JavaScript

Walk through the example

The filter limits the stream to inserts and updates, and updateLookup fills in the document body so the handler does not need a second read. After the handler finishes, the resume token is saved, so a restart resumes from the last processed event. If handle throws, the token is not advanced and the event is reprocessed, which requires the handler to be idempotent.

Common mistake

Saving the resume token before processing, which risks skipping an event if the process crashes mid-handler. Another is assuming the stream survives a failover without reopening; the driver often surfaces an error and the application must retry.

Verify the behavior

Insert and update documents and assert an event arrives for each with the expected fullDocument. Kill the consumer mid-stream, restart with the stored token, and confirm no event is lost or duplicated. Drop the watched collection and confirm the invalidate event is handled.

Interview exercise

Why must the change-stream handler be idempotent?

Answer and reasoning

Because a crash after processing but before the token is saved causes the same event to be delivered again on restart. Idempotency — for example, keying the write by the event id or a natural key — makes reprocessing harmless. Saving the token first would instead risk losing an event, so “at least once” delivery plus idempotent handling is the safe combination.

Continue learning

Compare delivery guarantees in Consumer deduplication and Idempotency keys. Read the MongoDB change streams documentation and try the MongoDB interview questions.

More in MongoDB

read ✓MongoDB · mid

MongoDB Bulk Writes

Batch inserts and updates with bulkWrite, choose ordered or unordered, and handle partial failures correctly.

~2 min readread →
read ✓MongoDB · mid

MongoDB Capped Collections

Use capped collections for fixed-size, insertion-ordered logs, and know why documents cannot grow.

~2 min readread →
esc