Chapter 05 · Updates, Array Mutations, Upserts, findAndModify, and Bulk Writes

Bulk Writes Across Operations and Namespaces: Ordered vs Unordered Execution and Error Analysis

Batch heterogeneous writes safely, interpret partial success, compare ordered and unordered execution, and use modern cross-namespace bulk APIs without assuming all-or-nothing behavior.

Intermediate90–120 minutesMutation semantics + failure-aware labMongoDB 8.3.8 · mongosh 2.10.0 · PyMongo 4.17Last reviewed: September 2026

Learning outcomes

AtlasMart needs to apply many independent writes efficiently: change prices, update inventory, insert new stock records, and sometimes touch more than one collection. MongoDB bulk APIs reduce client/server round trips and return aggregate/per-operation evidence. They do not turn a list of writes into one transaction. Partial success is a first-class outcome that applications must inspect.

01

Build single-collection bulk writes from insert, update, replace, and delete operations.

02

Contrast ordered execution, which stops at the first ordinary write error, with unordered execution, which attempts remaining operations.

03

Read bulk result counts and per-operation write errors without assuming the whole list succeeded or failed.

04

Use MongoDB 8.0+ cross-namespace Mongo.bulkWrite()/MongoClient.bulk_write() with explicit version requirements.

05

Design retries from operation identity and observed results rather than blindly replaying a partially successful batch.

Reproducible lab baseline · reviewed 2 September 2026

Mandatory examples use MongoDB Community Server 8.3.8 in the pinned mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim image, a disposable standalone mongod published only on 127.0.0.1:27041, and mongosh 2.10.0. PyMongo examples target the 4.17 line where driver behavior matters. Authentication and TLS are intentionally disabled only inside this isolated loopback lab; do not copy that posture to a shared or remotely reachable server. Default standalone read/write concern semantics are used, no replica-set/sharding guarantees are claimed, and the reset path removes atlasmart-mongo-ch05-l5. Cross-namespace bulk APIs are intentionally demonstrated because MongoDB 8.3.8 and PyMongo 4.17 satisfy their minimums. Mongo.bulkWrite() requires MongoDB 8.0+; PyMongo MongoClient.bulk_write() requires PyMongo 4.9+ and MongoDB 8.0+.

1. Bulk is a batching API, not an all-or-nothing transaction

A collection-level bulkWrite() accepts a sequence of write models such as insert, update, replace, and delete. The server/driver can execute them more efficiently than many independent round trips. But each successful write can become durable before a later operation fails. Therefore, the business meaning of partial completion must be designed explicitly.

Ordered mode preserves the provided order and stops when an ordinary write error occurs. Unordered mode can execute in an arbitrary order and attempts the other operations even when some fail. Unordered mode is useful when operations are independent and maximum progress is desired; it is unsafe if later operations semantically depend on earlier ones.

2. Error analysis starts from operation indexes and persisted state

A duplicate-key error in operation 1 does not erase operation 0. In unordered mode, operation 2 may also succeed. The exception/result object reports write-error indexes and aggregate counts, but robust recovery also queries the actual business keys when necessary. A retry strategy must know which operations are naturally idempotent, which use stable unique identities, and which would double-apply if replayed.

Operation shape Blind replay risk Safer identity
$set status=featured Often idempotent if same value Stable document key + desired final state
$inc onHand:-1 Double decrement on replay Idempotency/event key or transaction/workflow guard
insertOne random _id Duplicate logical row Deterministic _id or unique business key
deleteOne by unique id Second replay becomes no-op Unique immutable target key

3. Modern bulk writes can span namespaces

Starting with MongoDB 8.0, mongosh exposes Mongo.bulkWrite(), which can address multiple database/collection namespaces in one call. PyMongo’s MongoClient.bulk_write() provides a similar client-level API and requires PyMongo 4.9 or later with MongoDB 8.0 or later. This is a convenience and batching boundary, not a cross-namespace transaction guarantee. If product and inventory changes must commit or abort together as one invariant, use a transaction or redesign the aggregate; do not infer atomicity from the fact that one bulk API call contained both operations.

4. Run ordered, unordered, and cross-namespace bulk experiments

bash · start the disposable MongoDB 8.3.8 lab
docker rm -f atlasmart-mongo-ch05-l5 2>/dev/null || truedocker run --name atlasmart-mongo-ch05-l5 -p 127.0.0.1:27041:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimmongosh "mongodb://127.0.0.1:27041/atlasmart?directConnection=true" --quiet --eval 'printjson({version:db.version(), hello:db.hello().isWritablePrimary})' 
javascript · ordered stop, unordered continuation, and Mongo.bulkWrite across namespaces
db.products_bulk.drop(); db.inventory_bulk.drop();db.products_bulk.createIndex({sku:1},{unique:true});db.inventory_bulk.createIndex({sku:1},{unique:true});db.products_bulk.insertMany([{sku:"cam-100",name:"AtlasCam 100",priceCents:12500},{sku:"battery-9",name:"Battery 9",priceCents:1900}]);db.inventory_bulk.insertMany([{sku:"cam-100",onHand:10},{sku:"battery-9",onHand:20}]);// Single-collection bulk: deliberate duplicate-key error at operation 1.const ops=[ {updateOne:{filter:{sku:"cam-100"},update:{$inc:{priceCents:100}}}}, {insertOne:{document:{sku:"cam-100",name:"DUPLICATE",priceCents:1}}}, {updateOne:{filter:{sku:"battery-9"},update:{$inc:{priceCents:50}}}}];try{db.products_bulk.bulkWrite(ops,{ordered:true})}catch(e){printjson({orderedError:e.codeName||e.name,result:e.result,writeErrors:e.writeErrors})}printjson({afterOrdered:db.products_bulk.find().sort({sku:1}).toArray()});// Reset prices and run unordered: operation after the duplicate is attempted.db.products_bulk.updateOne({sku:"cam-100"},{$set:{priceCents:12500}}); db.products_bulk.updateOne({sku:"battery-9"},{$set:{priceCents:1900}});try{db.products_bulk.bulkWrite(ops,{ordered:false})}catch(e){printjson({unorderedError:e.codeName||e.name,result:e.result,writeErrors:e.writeErrors})}printjson({afterUnordered:db.products_bulk.find().sort({sku:1}).toArray()});// MongoDB 8.0+: cross-namespace bulk through the Mongo connection object.const cross=db.getMongo().bulkWrite([ {namespace:"atlasmart.products_bulk",name:"updateOne",filter:{sku:"cam-100"},update:{$set:{campaign:"fall"}}}, {namespace:"atlasmart.inventory_bulk",name:"updateOne",filter:{sku:"cam-100"},update:{$inc:{onHand:-1}}}, {namespace:"atlasmart.inventory_bulk",name:"insertOne",document:{sku:"strap-2",onHand:7}}],{ordered:true,verboseResults:true});printjson({crossNamespace:cross,product:db.products_bulk.findOne({sku:"cam-100"}),inventory:db.inventory_bulk.find().sort({sku:1}).toArray()});
python · PyMongo 4.17 MongoClient.bulk_write across namespaces
from pymongo import MongoClient, InsertOne, UpdateOneclient=MongoClient("mongodb://127.0.0.1:27041/?directConnection=true")ops=[    UpdateOne(namespace="atlasmart.products_bulk", filter={"sku":"battery-9"}, update={"$set":{"campaign":"python"}}),    UpdateOne(namespace="atlasmart.inventory_bulk", filter={"sku":"battery-9"}, update={"$inc":{"onHand":-2}}),    InsertOne(namespace="atlasmart.inventory_bulk", document={"sku":"case-3","onHand":4}),]result=client.bulk_write(ops, ordered=True, verbose_results=True)print({"matched":result.matched_count,"modified":result.modified_count,"inserted":result.inserted_count})client.close()

Expected evidence

In the ordered run, the camera price changes before the duplicate-key error and the battery price operation after the error is not attempted. In the unordered run, the duplicate still errors but the later battery update is attempted. The cross-namespace call reports aggregate/verbose results while changing product and inventory collections in the same bulk request. Those writes remain individually observable, not transactionally coupled by the bulk API itself.

Verification checklist

  • A unique SKU index makes the duplicate failure deterministic.
  • The fixture is reset before comparing ordered and unordered outcomes.
  • The persisted documents are queried after both exceptions instead of trusting only exception text.
  • Cross-namespace examples declare MongoDB 8.0+ and driver/mongosh requirements.
  • The lesson states clearly that bulk writes are not automatically transactions.
  • Retry discussion distinguishes idempotent final-state writes from non-idempotent increments.

Check your understanding

  1. What happens after the first ordinary write error in an ordered bulk?
  2. What does unordered mean?
  3. Does one Mongo.bulkWrite call across two collections make the changes atomic together?
  4. Why is replaying $inc blindly dangerous after a network/error ambiguity?
  5. What minimum versions matter for PyMongo client-level multi-namespace bulk?
Review the answers

Remaining operations are not attempted; earlier successful writes remain successful.

MongoDB/driver may execute operations in an arbitrary order and attempts remaining independent operations even when some fail.

No. It batches operations across namespaces; use transaction semantics when an all-or-nothing cross-document invariant is required.

A write that actually succeeded may be applied again, double-changing the value unless the workflow has an idempotency guard or retryable-write semantics appropriate to the topology/operation.

PyMongo 4.9+ and MongoDB Server 8.0+; this lesson uses PyMongo 4.17 with MongoDB 8.3.8.

bash · cleanup/reset
docker rm -f atlasmart-mongo-ch05-l5

Chapter 06 now leaves mutation syntax and asks the modeling question underneath it: which data belongs in one document so that the atomicity and locality demonstrated in this chapter align with the application’s actual aggregate boundaries?

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.