Skip to main content

Bulk Writes

Bulk write operations let you send multiple insert, update, replace, and delete commands to MongoDB in a single network round trip. This is significantly more efficient than executing each operation individually when you need to modify many documents at once.

WriteCommand types

The WriteCommand sealed trait has the following variants:

CommandDescription
WriteCommand.InsertOne(document)Insert a single document
WriteCommand.UpdateOne(filter, update)Update the first matching document
WriteCommand.UpdateMany(filter, update)Update all matching documents
WriteCommand.ReplaceOne(filter, replacement)Replace the first matching document
WriteCommand.DeleteOne(filter)Delete the first matching document
WriteCommand.DeleteMany(filter)Delete all matching documents

All variants accept an optional options argument for upsert, collation, etc.

Basic example

import cats.effect.IO
import mongo4cats.bson.Document
import mongo4cats.bson.syntax._
import mongo4cats.client.MongoClient
import mongo4cats.models.collection.{BulkWriteOptions, WriteCommand}
import mongo4cats.operations.{Filter, Update}

MongoClient.fromConnectionString[IO]("mongodb://localhost:27017").use { client =>
for {
db <- client.getDatabase("mydb")
coll <- db.getCollection("products")

// Seed some data first
_ <- coll.insertMany((1 to 10).map(i => Document("name" := s"item-$i", "qty" := i * 10)))

commands = List(
WriteCommand.InsertOne(Document("name" := "item-11", "qty" := 110)),
WriteCommand.UpdateOne(Filter.eq("name", "item-1"), Update.set("qty", 999)),
WriteCommand.DeleteOne(Filter.eq("name", "item-2")),
WriteCommand.DeleteMany(Filter.gt("qty", 50) && Filter.lt("qty", 80)),
WriteCommand.ReplaceOne(Filter.eq("name", "item-10"), Document("name" := "item-10", "qty" := 0))
)

result <- coll.bulkWrite(commands)
_ <- IO.println(s"Inserted: ${result.getInsertedCount}")
_ <- IO.println(s"Modified: ${result.getModifiedCount}")
_ <- IO.println(s"Deleted: ${result.getDeletedCount}")
} yield ()
}

Bulk write options

import mongo4cats.models.collection.BulkWriteOptions

// Ordered (default) — stop on first error
coll.bulkWrite(commands, BulkWriteOptions(ordered = true))

// Unordered — continue processing even if some commands fail
coll.bulkWrite(commands, BulkWriteOptions(ordered = false))

With ordered = true (the default), MongoDB executes commands in sequence and stops at the first error. With ordered = false, MongoDB may execute commands in parallel and reports all errors at the end; this is generally faster for independent writes.

Upsert on UpdateOne / UpdateMany

import mongo4cats.models.collection.{UpdateOptions, WriteCommand}
import mongo4cats.operations.{Filter, Update}

val commands = List(
WriteCommand.UpdateOne(
Filter.eq("name", "new-item"),
Update.set("qty", 0),
UpdateOptions().upsert(true) // insert if not found
)
)

coll.bulkWrite(commands)

Inspecting BulkWriteResult

import com.mongodb.bulk.BulkWriteResult

coll.bulkWrite(commands).map { result =>
println(s"Acknowledged: ${result.wasAcknowledged()}")
println(s"Inserted count: ${result.getInsertedCount}")
println(s"Matched count: ${result.getMatchedCount}")
println(s"Modified count: ${result.getModifiedCount}")
println(s"Deleted count: ${result.getDeletedCount}")
println(s"Upserted count: ${result.getUpserts.size}")
}

Using with a client session (transactions)

client.startSession.use { session =>
for {
_ <- session.startTransaction
_ <- coll.bulkWrite(session, commands, BulkWriteOptions(ordered = true))
_ <- session.commitTransaction
} yield ()
}