MongoDB: How INSERT Works

MongoDB: How the insertOne() / insertMany() (INSERT) Works Internally

Table of Contents



1. The Command We Will Follow

The INSERT equivalent in MongoDB is insertOne() for a single document and insertMany() for multiple documents. In this post we follow both commands step by step, using the students collection:

MongoDBOracle
// Single document
db.students.insertOne({
  name: "Ravi",
  age: 22,
  grade: "A"
})

// Multiple documents
db.students.insertMany([
  { name: "Sita", age: 21, grade: "B" },
  { name: "Kiran", age: 23, grade: "A" },
  { name: "Priya", age: 20, grade: "C" }
])
-- Single row
INSERT INTO students (name, age, grade)
VALUES ('Ravi', 22, 'A');

-- Multiple rows
INSERT ALL
  INTO students VALUES ('Sita', 21, 'B')
  INTO students VALUES ('Kiran', 23, 'A')
  INTO students VALUES ('Priya', 20, 'C')
SELECT 1 FROM dual;

COMMIT;

The command says: insert one or more documents into the students collection. If the collection does not exist, MongoDB creates it automatically.

Key difference from Oracle: In MongoDB, every insertOne() is auto-committed. There is no separate COMMIT unless you use a multi-document transaction (available from MongoDB 4.0 for replica sets and 4.2 for sharded clusters).

2. Overall Flowchart

This is the path of the insert command from the client to the acknowledgment. The notes on the right show what can stop the command or what happens at that step.


+----------------------------------------------+
| 1. CLIENT (mongosh / driver)                 |
|    db.students.insertOne({                   |
|      name: "Ravi", age: 22, grade: "A"      |
|    })                                        |
+-----------------------+----------------------+
                        |  insert command (BSON over TCP)
                        v
+----------------------------------------------+
| 2. mongod: NETWORK LAYER AND SESSION         |
|    accepts the connection, creates an opid   |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| 3. AUTHORIZATION CHECK                       |   NO  ──▶ Unauthorized error (stop)
|    does the user have the insert action?     |
+-----------------------+----------------------+
                        | YES
                        v
+----------------------------------------------+
| 4. DOCUMENT VALIDATION                       |   FAIL ──▶ validation error (stop)
|    a) add _id if missing (ObjectId)          |
|    b) check BSON size (max 16 MB)            |
|    c) check field names (no $ prefix, no .)  |
|    d) run schema validation rules if defined |
+-----------------------+----------------------+
                        | PASS
                        v
+----------------------------------------------+
| 5. LOCK                                      |
|    intent-exclusive (IX) lock on DB and      |
|    collection; exclusive lock on document    |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| 6. STORAGE ENGINE WRITE (WiredTiger)         |
|    a) write document to collection B-tree    |
|    b) update every index B-tree              |
|    c) write to the journal (WAL)             |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| 7. OPLOG ENTRY (replica set only)            |   single oplog document per insert
|    write the operation to local.oplog.rs     |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| 8. WAIT FOR WRITE CONCERN                    |   w:1 ──▶ primary acknowledged
|    w:1 (default), w:"majority", w:0          |   w:majority ──▶ wait for secondaries
+-----------------------+----------------------+   w:0 ──▶ fire and forget (no wait)
                        |
                        v
+----------------------------------------------+
| 9. RELEASE LOCKS                             |
|    release IX lock on collection and DB      |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| 10. RESPONSE TO CLIENT                       |
|     { acknowledged: true,                    |
|       insertedId: ObjectId("...") }          |
+-----------------------+----------------------+

Secondaries pull the oplog entry and replay it
to keep their data in sync (replication).

3. Step-by-Step Explanation

Step 1 — The client builds the command
mongosh (like every driver) turns insertOne() or insertMany() into an insert command document that includes the collection name and the array of documents. It sends this document to mongod as a BSON message over TCP. Unlike find(), the insert is sent to the server immediately — there is no cursor involved.

Step 2 — mongod receives the request
The network layer of mongod accepts the message and creates an operation context with an opid. You can see active inserts by running db.currentOp().

Step 3 — Authorization check
If authorization is enabled, MongoDB checks that the roles of the authenticated user include the insert action on myFirstDB.students. If the privilege is missing, the command stops immediately with a not authorized error.

Step 4 — Document validation
MongoDB validates each document before writing it. This step includes several checks:

  • Generate _id: If the document does not have an _id field, MongoDB (or the driver) adds one automatically using a new ObjectId. The ObjectId is a 12-byte value that contains a timestamp, a random value, and a counter, making it globally unique.
  • BSON size check: A single document cannot exceed 16 MB. If it does, the insert fails with a BSONObjectTooLarge error.
  • Field name rules: Field names cannot start with $ (reserved for operators) and cannot contain a dot . (reserved for nested field paths). Starting from MongoDB 5.0, dots and dollars are allowed in field names under certain conditions, but it is still best to avoid them.
  • Schema validation: If the collection has validation rules defined with $jsonSchema or other validators, MongoDB checks the document against those rules. If validation fails and the validationAction is error (the default), the insert stops. If validationAction is warn, the insert proceeds but a warning is logged.
  • Unique index check (pre-check): MongoDB also verifies that the _id value (and any other unique index values) does not already exist. If a duplicate is found, the insert fails with a DuplicateKey error (error code 11000).

Step 5 — Take locks
MongoDB takes intent-exclusive (IX) locks on the database and the collection. At the document level, it takes an exclusive lock. The IX locks allow other operations on different documents to proceed concurrently. This is different from the intent-shared locks used by find() — writes need exclusive access to the documents they are modifying.

Step 6 — The storage engine writes the data
WiredTiger performs three operations (explained in detail in Section 4):

  1. Insert the document into the collection B-tree.
  2. Insert entries into every index B-tree on the collection (including the default _id index).
  3. Write the changes to the journal (write-ahead log / WAL) for crash recovery.

Step 7 — Write the oplog entry (replica set only)
In a replica set, MongoDB writes an entry to the oplog (local.oplog.rs). The oplog is a capped collection that records every write operation. Secondary members continuously pull (tail) the oplog from the primary and replay the operations to keep their data in sync. For insertOne(), one oplog entry is created. For insertMany(), one oplog entry per document is created.

Step 8 — Wait for write concern
The write concern determines when the server considers the write “done”:

  • w: 1 (default): The primary has written the data. The command returns immediately without waiting for secondaries.
  • w: "majority": The primary waits until a majority of the replica set members (including the primary) have acknowledged the write. This provides durability even if the primary fails.
  • w: 0: Fire-and-forget. The driver does not wait for any acknowledgment. Fastest but no confirmation of success.
  • j: true: Combined with any w value, this ensures the journal has been flushed to disk before acknowledging. With w: 1 alone, the journal may be in memory briefly (up to the journal commit interval, typically 100 ms by default in WiredTiger).

Step 9 — Release locks
After the write is complete and the write concern is satisfied, MongoDB releases the IX locks on the collection and database. The document is now visible to other operations (subject to their read concern).

Step 10 — Send the response to the client
The response goes back to the client as a BSON message. For insertOne(), the response includes acknowledged: true and the insertedId. For insertMany(), it includes insertedIds — an object mapping the array index to each _id. If the operation was slower than slowms (100 ms by default), MongoDB writes an entry to the slow-query log.

4. How the Storage Engine Writes the Data

This is Step 6 in detail. WiredTiger uses a write-ahead log (journal) and checkpoints to balance performance and durability.


+----------------------------------------------+
| Application calls insertOne()                |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| WiredTiger: Write to IN-MEMORY BUFFER        |
|   a) Insert document into collection B-tree  |
|      (in the WiredTiger cache)               |
|   b) Insert index entries into each index    |
|      B-tree (in the WiredTiger cache)        |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| WiredTiger: Write to JOURNAL (WAL)           |   every 100 ms (default) or
|   journal entry = redo record for the change |   immediately if j:true
|   flushed to disk (fsync)                    |
+-----------------------+----------------------+
                        |
                        v
+----------------------------------------------+
| CHECKPOINT (every 60 seconds by default)     |
|   dirty pages in cache are written to the    |
|   data files on disk                         |
|   old journal entries can be removed         |
+-----------------------+----------------------+

If mongod crashes:
  - On restart, WiredTiger replays the journal
    from the last checkpoint to recover the data.

Key points:

  • In-memory first: The document is written to the WiredTiger cache (in-memory B-tree pages), not directly to the data files. This makes writes fast.
  • Journal (WAL): The journal is a sequential write-ahead log. WiredTiger flushes the journal to disk every 100 ms by default (configurable with storage.journal.commitIntervalMs). If the server crashes, the journal is replayed from the last checkpoint to recover any writes that were in memory but not yet in the data files.
  • Checkpoint: Every 60 seconds (by default), WiredTiger writes all dirty pages from the cache to the data files on disk. After a checkpoint, the journal entries before the checkpoint are no longer needed.
  • Compression: WiredTiger compresses both the collection data (using Snappy by default) and the indexes (using prefix compression) when writing to disk.
  • If j: true is set in the write concern: WiredTiger immediately flushes the journal to disk before acknowledging the write. This guarantees that even if the server crashes right after the acknowledgment, the data is recoverable.

Comparison with Oracle:

MongoDB (WiredTiger)Oracle
Write to WiredTiger cache (in-memory B-tree)Write to buffer cache (in-memory data blocks)
Journal (WAL) — sequential log on diskRedo log — sequential log on disk
Checkpoint every 60 secondsCheckpoint (DBWR writes dirty blocks, controlled by incremental checkpoints)
Crash recovery: replay journal from last checkpointCrash recovery: replay redo logs from last checkpoint

5. Replica Set Write Path

In a replica set, all writes go to the primary. Secondaries replicate the data by tailing the oplog.


+----------------+          +-------------------+         +-------------------+
|    CLIENT      |          |   PRIMARY (mongod) |         | SECONDARY (mongod)|
+-------+--------+          +--------+----------+         +--------+----------+
        |                            |                              |
        |  insertOne()               |                              |
        +--────────────────────▶     |                              |
        |                            |                              |
        |                    4. Validate document                   |
        |                    5. Take IX lock                        |
        |                    6. Write to WT cache + journal         |
        |                    7. Write oplog entry                   |
        |                            |                              |
        |                            |     oplog tailing            |
        |                            + ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶|
        |                            |                              |
        |                            |                     Replay the insert:
        |                            |                     write to WT cache
        |                            |                     + journal + oplog
        |                            |                              |
        |                            |◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─+
        |                            |     acknowledgment           |
        |                            |                              |
        |    8. Write concern         |                              |
        |    w:1 ── ack immediately  |                              |
        |    w:majority ── wait for  |                              |
        |       enough acks          |                              |
        |                            |                              |
        |◀───────────────────────────+                              |
        |    10. Response to client  |                              |
        |                            |                              |

Key points:

  • The oplog (local.oplog.rs) is a capped collection. Each write operation creates an oplog entry with a timestamp.
  • Secondaries continuously pull new oplog entries from the primary (or from another secondary that is closer in the replication chain).
  • Each secondary replays the operations in the same order, so the data eventually matches the primary.
  • With w: "majority", the primary waits until a majority of members have written the oplog entry before acknowledging. This protects against data loss if the primary fails.
  • The replication lag is the delay between the primary writing the oplog entry and a secondary replaying it. You can check this with rs.printReplicationInfo() and rs.printSecondaryReplicationInfo().

6. Sharded Cluster Write Path

In a sharded cluster, the client connects to mongos (the router), which determines which shard owns the document.


+----------------+         +-------------------+
|    CLIENT      |         |      mongos       |
+-------+--------+         +--------+----------+
        |                            |
        |  insertOne()               |
        +──────────────────────▶     |
        |                            |
        |                  Which shard?
        |                  Look at the shard key
        |                  in the document.
        |                            |
        |                            v
        |               +──────────────────────+
        |               |                      |
        |               v                      v
        |     +-------------------+   +-------------------+
        |     |  Shard A (primary)|   |  Shard B (primary)|
        |     |  owns this chunk  |   |  not involved     |
        |     +-------------------+   +-------------------+
        |               |
        |       Steps 4-9 run
        |       on Shard A's primary
        |               |
        |◀──────────────+
        |   Response

Key points:

  • mongos uses the shard key value in the document to determine the target shard. If the document does not contain the shard key, the insert fails.
  • For insertMany(), mongos groups the documents by their target shard and sends each group to the correct shard in parallel.
  • Each shard is itself a replica set, so Steps 5–9 (lock, write, oplog, write concern) happen on that shard’s primary.
  • The config servers store the chunk-to-shard mapping. mongos caches this metadata and refreshes it when chunks are split or migrated.

7. insertOne vs insertMany Internals

Both commands follow the same flowchart, but there are important differences:

AspectinsertOne()insertMany()
Documents per callExactly 1Up to 100,000 (limited by max BSON message size of 48 MB)
Network round trips1 per document1 for the entire batch (much more efficient)
Oplog entries1 entry1 entry per document (not one entry for the whole batch)
Default orderN/AOrdered: true (default). Inserts stop at the first error.
Unordered modeN/A{ ordered: false }. MongoDB continues inserting remaining documents even if some fail. Faster because MongoDB can insert in parallel.
Error behaviorThrows an error if the insert failsOrdered: stops at first error, documents after the error are not inserted.
Unordered: inserts all valid documents, reports all errors at the end.

Example — ordered vs unordered:

// Ordered (default) — stops at the first duplicate _id
db.students.insertMany([
  { _id: 1, name: "Ravi" },
  { _id: 1, name: "Duplicate" },   // ← error here, stop
  { _id: 2, name: "Sita" }         // ← never inserted
])

// Unordered — continues past errors
db.students.insertMany([
  { _id: 1, name: "Ravi" },
  { _id: 1, name: "Duplicate" },   // ← error, but continue
  { _id: 2, name: "Sita" }         // ← inserted successfully
], { ordered: false })

8. See It Yourself

Unlike find(), there is no explain() for insert operations. But you can observe what happens using these tools:

// 1. Insert a document and see the response
var result = db.students.insertOne({ name: "Ravi", age: 22, grade: "A" })
printjson(result)
// { acknowledged: true, insertedId: ObjectId("...") }

// 2. Check the oplog entry (replica set)
use local
db.oplog.rs.find({ ns: "myFirstDB.students" }).sort({ ts: -1 }).limit(1)

// 3. Check the current operations while inserting a large batch
// In another mongosh session:
db.currentOp({ "command.insert": "students" })

// 4. Check the server status for write metrics
db.serverStatus().opcounters
// { insert: 1523, query: 4821, update: 312, ... }

// 5. Enable profiling to see slow inserts
db.setProfilingLevel(2)    // log all operations
db.students.insertOne({ name: "Test", age: 99, grade: "X" })
db.system.profile.find({ op: "insert" }).sort({ ts: -1 }).limit(1)
db.setProfilingLevel(0)    // turn off profiling

// 6. Check write concern response with w:majority
db.students.insertOne(
  { name: "Kiran", age: 23, grade: "A" },
  { writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)

Key fields in the oplog entry:

FieldWhat It Tells You
tsTimestamp of the operation (used for replication ordering)
op"i" for insert
nsNamespace: myFirstDB.students
oThe full document that was inserted
wallWall clock time when the operation occurred

9. Oracle Terms Comparison

For DBAs coming from Oracle, here is a side-by-side mapping of every insert step:

MongoDBOracle
Client sends the insert commandClient sends the INSERT SQL statement
Authorization check (insert action)Privilege check (INSERT on the table)
Auto-generate _id (ObjectId)Sequence / identity column for primary key
Schema validation ($jsonSchema)Constraints (NOT NULL, CHECK, data types)
Unique index check (duplicate _id)Primary key / unique constraint violation
16 MB document size limitRow size limit (~8 KB for non-LOB columns in a block; LOBs stored separately)
Intent-exclusive (IX) lockRow-level exclusive lock (TX) + table-level row exclusive (RX/SX)
Write to WiredTiger cache (in-memory B-tree)Write to buffer cache (in-memory data block)
Update every index B-treeMaintain every index (insert into each index block)
Journal (WAL) — crash recoveryRedo log — crash recovery
No undo needed (no rollback for single-doc writes outside transactions)Undo segment — needed for rollback and read consistency
Oplog (local.oplog.rs)Archive redo log / Data Guard redo transport
Secondaries replay the oplogStandby applies the redo logs (MRP / Redo Apply)
Auto-commit (each write is its own “transaction”)Explicit COMMIT required
Write concern w: "majority"Data Guard SYNC transport (log write must reach standby before commit)
Write concern j: trueLGWR flush redo to disk at commit (always happens in Oracle)
Checkpoint every 60 secondsIncremental checkpoint (DBWR writes dirty blocks continuously)
insertMany() with ordered: falseINSERT ALL ... SELECT or FORALL with SAVE EXCEPTIONS
Collection auto-created on first insertTable must exist (CREATE TABLE required first)
db.serverStatus().opcounters.insertV$SYSSTAT — db block changes, redo entries
Slow-query log (slowms)AWR / ASH / V$SQL with elapsed time
Caution: Your use of any information or materials on this website is entirely at your own risk. It is provided for educational purposes only. It has been tested internally, however, we do not guarantee that it will work for you. Ensure that you run it in your test environment before using.
Thank you
Rajasekhar Amudala