Update and Delete Operations
Modifying data in a distributed system is fundamentally different from a single-node RDBMS. In MongoDB, you must understand Atomicity and Write Concern to ensure data integrity without sacrificing performance.
1. First Principles: Atomicity
In MongoDB, a write operation is atomic on the level of a single document, even if the operation modifies multiple embedded documents within it.
- Single Document: Atomic. If you update 10 fields in one document, either all 10 update or none do.
- Multiple Documents: Not atomic by default. If
updateMany()hits 100 documents and the server crashes after 50, only 50 are updated. (Multi-document transactions exist but come with a performance cost).
Interactive: Atomicity Visualizer
See how a single document update is “All or Nothing”, while multi-document updates can leave the system in a partial state if interrupted.
2. Updating Documents
The Operators
Instead of sending the full document back to the server (which risks race conditions), we send Update Operators.
$set: Updates the value of a field.$inc: Increments a numerical value.$unset: Deletes a field.$currentDate: Sets a field to the current date.
The Power of Upserts
An Upsert (Update + Insert) is a conditional operation:
- Attempt to find a document matching the filter.
- If found, update it.
- If not found, create a new document using the filter and update operations.
This is critical for idempotent counters and “first-seen” logic.
Interactive: Upsert Logic
Visualize the decision flow of an Upsert operation.
Code Examples
Java
import com.mongodb.client.model.UpdateOptions;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Updates.*;
public void updateDocs(MongoCollection<Document> collection) {
// 1. Atomic Increment
collection.updateOne(eq("name", "Canvas"), inc("stock", -1));
// 2. Update Many
collection.updateMany(lt("stock", 10), set("status", "LOW_STOCK"));
// 3. Upsert: Track page views
UpdateOptions options = new UpdateOptions().upsert(true);
collection.updateOne(
eq("page", "/home"),
inc("views", 1),
options
);
}
Go
func UpdateDocs(ctx context.Context, coll *mongo.Collection) {
// 1. Atomic Increment
_, err := coll.UpdateOne(
ctx,
bson.D{{"name", "Canvas"}},
bson.D{{"$inc", bson.D{{"stock", -1}}}},
)
// 2. Upsert
opts := options.Update().SetUpsert(true)
filter := bson.D{{"page", "/home"}}
update := bson.D{{"$inc", bson.D{{"views", 1}}}}
_, err = coll.UpdateOne(ctx, filter, update, opts)
}
3. Deleting Documents
Deletes are straightforward but permanent. Always verify your filter with a find() (dry run) before executing a deleteMany().
// Java
collection.deleteOne(eq("_id", new ObjectId("...")));
collection.deleteMany(lt("created_at", "2022-01-01"));
// Go
coll.DeleteOne(ctx, bson.D{{"_id", id}})
coll.DeleteMany(ctx, bson.D{{"created_at", bson.D{{"$lt", "2022-01-01"}}}})
4. Under the Hood: Write Concern
When does the application get an “OK” response? This is controlled by Write Concern.
w: 1(Default): Acknowledged by the Primary only. Fast, but data could be lost if the Primary crashes before replicating.w: majority: Acknowledged by the Primary and a majority of Secondaries. High durability, higher latency.j: true: Acknowledged only after writing to the on-disk Journal. Highest durability.
Write Concern Simulator
Visualize the latency cost of durability.
5. Visualizing Replication
sequenceDiagram
participant Client
participant Primary
participant Secondary1
participant Secondary2
Client->>Primary: Insert (w: majority)
Primary->>Primary: Write to Journal
par Replication
Primary->>Secondary1: Replicate (Oplog)
Primary->>Secondary2: Replicate (Oplog)
end
Secondary1-->>Primary: Ack
Note right of Primary: Majority Reached (P + S1)
Primary-->>Client: Success (OK)
Secondary2-->>Primary: Ack
[!IMPORTANT] Use
w: majorityfor critical data. Financial transactions, user registrations, and audit logs should always survive a primary failover.