Chapter 03 · CRUD Fundamentals: Insert, Find, Projection, Sort, Limit, and Delete
InsertOne/InsertMany, Generated _id Values, Ordered Writes, and Error Handling
Insert records without confusing “exception” with “nothing happened”: make generated identifiers, ordered/unordered batch semantics, acknowledgements, and partial success observable.
Learning outcomes
AtlasMart is loading a new product feed. Some product documents
are independent and valid, one input accidentally reuses an
existing identifier, and the ingestion service must know whether
the batch stopped, partially succeeded, or completed. Treating
insertMany() as “paste an array into MongoDB” is
unsafe because the result and failure object are part of the
data contract.
Explain insertOne and insertMany as acknowledged write operations with observable result metadata.
Trace where generated _id values come from and why a client-generated identifier helps retry and reconciliation workflows.
Predict ordered versus unordered insert behavior when one document violates the unique _id index.
Interpret partial success and BulkWriteError evidence instead of retrying a whole batch blindly.
Build a deterministic insert lab with before/after verification and cleanup.
Mandatory labs use a disposable loopback-only standalone
mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim
with a dedicated AtlasMart collection and explicit reset
commands. Driver examples pin pymongo==4.17.0.
The standalone is intentionally unauthenticated only for these
short-lived local exercises; do not publish it beyond
127.0.0.1. Docker commands use syntax accepted
directly by ordinary shells; if the first cleanup reports that
the container does not exist, that is harmless.
Retryable-write behavior that requires a replica set or
sharded cluster is explained but not claimed for this
standalone.
Docker, mongod, mongosh, and PyMongo are not available in this generation environment. Commands were checked against current official MongoDB Server and PyMongo documentation, but product commands were not executed here. Expected-output blocks describe stable fields and relationships to verify; they are not fabricated captured transcripts.
1. An insert is a state transition plus an acknowledgement
insertOne(document, options) asks MongoDB to add
one document. insertMany(documents, options) asks
it to add a sequence of documents. The collection always has a
unique index on _id. If the application or driver
does not provide _id, most drivers generate an
ObjectId before sending the write; the server can
also add one when necessary. The returned
acknowledged flag tells you whether the write used
acknowledged write concern, while insertedId/insertedIds
identify successful logical inserts in the ordinary success
path.
Acknowledgement is not “the whole business workflow succeeded.” It says the database write met the configured write-concern acknowledgement. Payment capture, cache invalidation, messaging, or a later replica-set durability guarantee are separate concerns.
use atlasmartdb.crud_products.drop()const before = db.crud_products.countDocuments({})const r = db.crud_products.insertOne({ sku: "sku-cam-100", name: "AtlasCam Mini", category: "camera", priceCents: 12900, active: true})printjson({before, acknowledged:r.acknowledged, insertedId:r.insertedId})printjson(db.crud_products.findOne({_id:r.insertedId}))
The key observation is that the inserted document can be queried
by exactly the identifier returned by the write. In mongosh, the
shell/driver typically materializes the
ObjectId before the command reaches the server. Do
not infer event time, business identity, or global ordering
merely from an ObjectId.
2. Ordered batches stop at the first ordinary write error
insertMany() defaults to ordered:true.
The server processes the supplied sequence in order. If an
ordinary insert error such as duplicate _id occurs,
later queued documents are not processed. Earlier documents
remain inserted: this is partial success, not
atomic rollback of the entire batch.
db.crud_products.drop()try { db.crud_products.insertMany([ {_id:"p-101", sku:"sku-101", name:"Tripod", priceCents:3900}, {_id:"p-102", sku:"sku-102", name:"Bag", priceCents:5900}, {_id:"p-102", sku:"sku-duplicate", name:"Duplicate id", priceCents:1}, {_id:"p-103", sku:"sku-103", name:"Battery", priceCents:2900} ], {ordered:true})} catch (e) { print("ordered error code:", e.code) printjson(e.result)}printjson(db.crud_products.find({}, {_id:1, sku:1}).sort({_id:1}).toArray())
Verify that the first two documents exist, the duplicate fails,
and p-103 was never attempted. The error object may
expose different wrapper formatting across mongosh/driver
releases, so treat stable concepts—error code, failed operation
index, inserted count, and persisted documents—as the evidence,
not punctuation in a printed exception.
3. Unordered means continue after independent errors—not atomic and not ordered
With ordered:false, MongoDB continues processing
remaining inserts after an ordinary write error. The server may
reorder work for efficiency, so your application must not rely
on execution order. This mode is useful for independent records
where maximizing successful ingestion matters more than stopping
on the first bad record.
db.crud_products.drop()try { db.crud_products.insertMany([ {_id:"p-201", sku:"sku-201", name:"Lens", priceCents:9900}, {_id:"p-202", sku:"sku-202", name:"Light", priceCents:7900}, {_id:"p-202", sku:"sku-dup", name:"Duplicate id", priceCents:1}, {_id:"p-203", sku:"sku-203", name:"Mic", priceCents:6900} ], {ordered:false})} catch (e) { print("unordered error code:", e.code) printjson(e.result)}printjson(db.crud_products.find({}, {_id:1, sku:1}).sort({_id:1}).toArray())
The expected durable state contains p-201,
p-202, and p-203. That before/after
query is more important than assuming a thrown exception means
“nothing happened.” For very large inputs, high-level drivers
also split inserts into protocol-sized batches, so one
application call can contain several wire-level batches.
4. Driver error handling: reconcile what succeeded before retrying
PyMongo raises BulkWriteError for batch write
errors. Its details include counts and indexed errors that let
the application determine which inputs failed. A naive catch
block that immediately retries the entire original list can turn
an ambiguous network incident or partial success into
duplicate-key noise and duplicate business side effects around
the database write.
from pymongo import MongoClientfrom pymongo.errors import BulkWriteErrorclient = MongoClient("mongodb://127.0.0.1:27027/?directConnection=true", serverSelectionTimeoutMS=3000)coll = client.atlasmart.crud_productscoll.drop()docs = [ {"_id":"p-301", "sku":"sku-301"}, {"_id":"p-302", "sku":"sku-302"}, {"_id":"p-302", "sku":"sku-dup"}, {"_id":"p-303", "sku":"sku-303"},]try: result = coll.insert_many(docs, ordered=False) print("inserted_ids", result.inserted_ids)except BulkWriteError as exc: d = exc.details print("nInserted", d.get("nInserted")) print("writeErrors", [(e.get("index"), e.get("code")) for e in d.get("writeErrors", [])]) print("persisted", list(coll.find({}, {"_id":1}).sort("_id", 1)))finally: client.close()
Retryable writes are a distinct server/driver feature. They require a replica set or sharded cluster and acknowledged write concern; a standalone lab cannot demonstrate their server-side guarantee. Modern drivers enable retryable writes by default where the topology supports them. For supported write operations, the driver can retry after certain transient failures, but application-level side effects outside MongoDB still need their own idempotency strategy.
5. AtlasMart insert lab: prove the result, the failure, and the final state
docker rm -f atlasmart-mongo-ch03-l1docker run --name atlasmart-mongo-ch03-l1 -p 127.0.0.1:27027:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimdocker logs atlasmart-mongo-ch03-l1 --tail 25
mongosh "mongodb://127.0.0.1:27027/atlasmart?directConnection=true" --quiet --eval 'db.crud_products.drop();const one = db.crud_products.insertOne({sku:"sku-generated", name:"Generated id demo", priceCents:1000});printjson({oneResult:one, oneDoc:db.crud_products.findOne({_id:one.insertedId})});try { db.crud_products.insertMany([ {_id:"lab-a", sku:"a"}, {_id:"lab-b", sku:"b"}, {_id:"lab-b", sku:"dup"}, {_id:"lab-c", sku:"c"} ], {ordered:false});} catch(e) { printjson({code:e.code, result:e.result}); }printjson({count:db.crud_products.countDocuments({}), docs:db.crud_products.find({}, {_id:1,sku:1}).sort({_id:1}).toArray()});'
Verification checklist
- The first insert returns an acknowledged result and a generated identifier that retrieves exactly one document.
- The unordered batch reports a duplicate-key failure but still persists the nonconflicting documents.
-
The final query is sorted by
_id, so verification output is deterministic. - You can distinguish “exception thrown” from “zero writes performed.”
- No command depends on Atlas, Enterprise Advanced, Search, TLS, or KMS.
Check your understanding
- Why can an insertMany exception still mean data was inserted?
- What does ordered:false change?
- Why should the application inspect write error indexes?
- Does acknowledged:true prove a payment workflow is complete?
- Why is retryable-write behavior not proven by this standalone lab?
Review the answers
Because batch writes can partially succeed before an error. Ordered mode preserves earlier successes; unordered mode may continue after independent errors.
It lets remaining operations continue after ordinary write errors and removes any guarantee that server execution follows input order.
They connect failures to specific input operations so the application can reconcile or repair only the affected records.
No. It only reports the database write acknowledgement under its write concern. Other systems and business side effects remain separate.
Retryable writes require a replica set or sharded cluster; this lab intentionally uses a standalone to keep the CRUD environment small and reproducible.
docker rm -f atlasmart-mongo-ch03-l1
The next lesson moves from writes to precise reads: filters, projections, stable ordering, skip, and limit.
Authoritative references
- MongoDB release notes — Official current stable server series and patch notes.
- MongoDB 8.3 release notes — Official 8.3 patch history; 8.3.8 is the latest released patch at review time.
- MongoDB CRUD operations — Official CRUD overview and server semantics.
- PyMongo CRUD guides — Official Python driver CRUD behavior and result objects.
- insertMany() — Ordered/unordered behavior, batching, generated _id values, result metadata, and error handling.
- PyMongo insert documents — Official insert_one/insert_many API and options.
- Retryable writes — Topology requirements, supported operations, retry behavior, and NoWritesPerformed labeling.