MongoDBBulk Write Operations

Bulk Write Operations

When you need to apply many different writes at once — some inserts, some updates, some deletes — sending each as its own round trip is slow. bulkWrite() batches a mix of operations into a single call to the server, cutting network overhead dramatically.

Basic bulkWrite()

JS
db.products.bulkWrite([
  { insertOne: { document: { sku: "A1", price: 9.99 } } },
  { updateOne: {
      filter: { sku: "B2" },
      update: { $set: { price: 14.99 } }
  } },
  { updateMany: {
      filter: { category: "clearance" },
      update: { $mul: { price: 0.8 } }
  } },
  { deleteOne: { filter: { sku: "C3" } } },
  { replaceOne: {
      filter: { sku: "D4" },
      replacement: { sku: "D4", price: 19.99, name: "New Name" }
  } }
])
The Five Operation Types

Operation

Shape

insertOne

{ insertOne: { document: {...} } }

updateOne

{ updateOne: { filter, update, upsert? } }

updateMany

{ updateMany: { filter, update, upsert? } }

deleteOne

{ deleteOne: { filter } }

deleteMany

{ deleteMany: { filter } }

replaceOne

{ replaceOne: { filter, replacement, upsert? } }

Ordered vs Unordered

By default, bulkWrite() is ordered: operations run in the array's order, and the first failure stops all remaining operations. Pass { ordered: false } to run every operation independently — MongoDB attempts them all (in undefined order, in parallel where possible) and reports every failure together.

JS
// Ordered (default) — stops at the first error
db.products.bulkWrite(operations)

// Unordered — every operation is attempted regardless of earlier failures
db.products.bulkWrite(operations, { ordered: false })

Ordered

Unordered

On error

Stops immediately

Continues with remaining ops

Execution

Strictly sequential

May run in parallel internally

Performance

Slightly slower (sequential)

Faster for large independent batches

Use when

Later ops depend on earlier ones succeeding

Operations are independent of each other

Tip
Default to ordered: false for large, independent batch jobs (bulk imports, sync jobs) — you get better throughput and a complete picture of every failure instead of stopping at the first.
Write Results

JS
const result = await db.products.bulkWrite(operations)
console.log(result)
{
  insertedCount: 1,
  matchedCount: 12,
  modifiedCount: 12,
  deletedCount: 1,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: { '0': ObjectId("...") }
}
Handling Partial Failures — BulkWriteError

With ordered: false, some operations can fail (e.g. a duplicate key) while others succeed. The driver throws a BulkWriteError that contains both the successes so far and the details of every failure.

JS
try {
  await db.products.bulkWrite(operations, { ordered: false })
} catch (err) {
  if (err.name === "MongoBulkWriteError") {
    console.log("Succeeded before error:", err.result.insertedCount)
    console.log("Write errors:", err.writeErrors.map(e => ({
      index: e.index,
      code: e.code,
      message: e.errmsg
    })))
  } else {
    throw err
  }
}
Warning
With ordered: false, a failed operation does NOT roll back the ones that already succeeded — bulkWrite is not a transaction. If you need all-or-nothing semantics across multiple writes, use a session-based transaction instead (see the Node.js Driver page).
Performance vs Individual Operations
  • Each individual insertOne/updateOne call is a separate round trip to the server — network latency dominates for large batches.

  • bulkWrite() sends the whole batch (up to the 100,000-operation / 48MB command size limit) in far fewer round trips.

  • For pure inserts with no per-document logic, insertMany() is simpler and just as fast as an all-insertOne bulkWrite.

  • bulkWrite() earns its keep when the batch is a MIX of operation types, or needs different filters/updates per document.

Worked Example: Syncing a Price List

JS
// Given an array of { sku, price } from an external feed, upsert each
const priceUpdates = [
  { sku: "A1", price: 12.5 },
  { sku: "B2", price: 8.0 },
  { sku: "NEW-SKU", price: 20.0 }
]

const ops = priceUpdates.map(({ sku, price }) => ({
  updateOne: {
    filter: { sku },
    update: { $set: { price } },
    upsert: true   // insert if the SKU doesn't exist yet
  }
}))

const result = await db.products.bulkWrite(ops, { ordered: false })
console.log(`Matched: ${result.matchedCount}, Upserted: ${result.upsertedCount}`)
Summary
  • bulkWrite() batches insertOne/updateOne/updateMany/deleteOne/deleteMany/replaceOne into one server round trip.

  • Ordered (default): stops on first error, guarantees sequence. Unordered: attempts everything, better throughput.

  • Write results report counts per operation type; a partial failure with unordered writes throws BulkWriteError with details for each failed op.

  • bulkWrite is not atomic across operations — use transactions when you need all-or-nothing.