mongodb 7.5.0MongoG Cursors, Results, and Cancellation
Understand paged cursor results, iteration, write and scalar output, change streams, transaction cleanup, cancellation, and runtime ownership in MongoG.
Return a cursor for interactive results
The official driver returns cursors from operations such as find() and aggregate(). When a top-level statement produces a cursor, MongoG registers it in the connection runtime, reads one bounded page with cursor.next(), and shows navigation controls.
use("mongog_tutorial");
const events = db.collection("events");
events.find(
{ type: "page-view" },
{ projection: { path: 1, createdAt: 1, visitorId: 1 } },
).sort({ createdAt: -1 });
MongoG never calls toArray() automatically. That prevents a result viewer from unexpectedly loading an unbounded collection into memory. The cursor remains owned by the query tab until it is closed, expires, or the owning context is removed.
Iterate when the script must process documents
Use for...of when each document drives script logic:
let processed = 0;
for (const event of events.find({ type: "page-view" }).limit(100)) {
processed += 1;
if (processed === 10) break;
}
print("Processed:", processed);
MongoG uses asynchronous cursor iteration and closes the iterator on early exit. Do not use cursor forEach() with a callback that returns database Promises because the driver does not await that callback in the way the script might imply.
Use explicit toArray() only when the script genuinely needs the complete bounded set in memory:
const recent = await events.find({ type: "page-view" }).sort({ createdAt: -1 }).limit(20).toArray();
print("Loaded into memory:", recent.length);
Understand other result kinds
MongoG classifies resolved statement values:
- Driver cursors become paged document results.
- Insert, update, replace, delete, and bulk results show operation counts and bounded raw details.
- Ordinary BSON and JavaScript values become structured scalar results.
- Functions and values that cannot be serialized receive an opaque preview.
- Change streams receive a registered stream handle for polling and cleanup.
const write = events.updateMany(
{ type: "page-view", reviewed: { $ne: true } },
{ $set: { reviewed: true } },
);
printjson({ matched: write.matchedCount, modified: write.modifiedCount });
Change streams and deployment requirements
Change streams require a replica set or sharded cluster. They stay open and wait for events, so close them when the observation is complete. MongoG's administration workspace provides a bounded interface for this workflow.
const stream = db.collection("events").watch([
{ $match: { operationType: { $in: ["insert", "update"] } } },
]);
stream;
Use a narrowly scoped pipeline and understand that long-lived streams consume connection resources.
Cancellation and runtime ownership
Each MongoG connection has a dedicated utility process that owns its MongoClient, cursors, sessions, and active script work. Cancel requests are checked around waits, calls, and loop boundaries. On a failed or cancelled execution, tracked cursors and streams are closed.
A successful execution keeps returned cursors open for interactive paging. Idle cursor and connection cleanup prevents abandoned resources from living forever.
Cancellation does not undo acknowledged writes. A server operation may also take time to observe cancellation. Use transactions for atomic multi-document rules, maxTimeMS for bounded server work, and idempotent maintenance scripts that can be verified after interruption.
Clean up sessions
Always release a session in finally, even when the transaction fails or the script is cancelled:
const session = client.startSession();
try {
await session.withTransaction(async () => {
await events.updateOne(
{ _id: ObjectId("66b0a2b8f20b2b4f0b17a001") },
{ $set: { reviewed: true } },
{ session },
);
});
} finally {
await session.endSession();
}
Replace the sample identifier with an identifier from your own tutorial dataset before running it.
Bound server work as well as client memory
Paged results protect the desktop from loading every returned document, but the server may still examine a large amount of data before producing the first page. Use selective filters, supporting indexes, projections, and operation limits.
maxTimeMS gives the server a time budget for eligible work:
events.find(
{ type: "page-view", createdAt: { $gte: ISODate("2026-01-01") } },
{ projection: { path: 1, createdAt: 1 } },
).sort({ createdAt: -1 }).maxTimeMS(5000);
A time limit is not a performance fix. Use explain() to understand why the operation is expensive and create an appropriate index when the access path is important.
Know when a cursor sees changes
A cursor is not a frozen copy of every matching document. Results are produced according to MongoDB’s read semantics while the cursor advances, and concurrent writes can affect what later batches observe. If a workflow requires a defined consistency boundary, choose the appropriate read concern or transaction design rather than assuming UI paging creates a snapshot.
Previous-page navigation uses data already retained by MongoG for that cursor. Refresh closes the cursor and executes a new read, which can produce different results after database changes.
Recover after cancellation
After cancellation, run a narrow verification query for every potentially affected record. If a multi-step maintenance script can be interrupted, add operation identifiers, state transitions, or upserts that make safe resumption possible.
Practice by returning a cursor with more documents than one page, moving forward and backward, inserting another matching document in a separate tab, and comparing retained pages with a refreshed query. Then run a bounded toArray() with limit(10) and explain why that explicit conversion is safe while an unbounded one is not.
Common mistakes
- Calling
toArray()on an unbounded cursor can consume large amounts of memory. - Assuming result paging reruns the original query is incorrect; MongoG advances the registered cursor.
- Cancelling a script is not equivalent to rolling back earlier writes.
- Leaving sessions or long-lived streams open wastes deployment and client resources.
- Running parallel operations within one transaction session is unsupported by the driver.
- Assuming cursor paging creates a frozen snapshot can produce incorrect conclusions during concurrent writes.
- Using client paging without an index or server time bound can still leave the database doing excessive work.
Keep exploring
Return to the documentation home for another path, or use the official references for cursor operations and change streams.