When any update results in modified document that's identical to original document, the update system should perform no update, create no oplog entry and indicate nModified:0.
Original summary: replaceOne with no logical change may create oplog entry and report modifiedCount=1
Original description: When a replacement is performed via the update command, a logical no-op replacement may report a modifiedCount of zero or one depending on whether the replacement document included the _id field or not, respectively.
Consider:
rs:PRIMARY> db.foo.drop() false rs:PRIMARY> db.foo.replaceOne({_id:1},{_id:1, x:11},{upsert:true}) { "acknowledged" : true, "matchedCount" : 0, "modifiedCount" : 0, "upsertedId" : 1 } rs:PRIMARY> db.foo.replaceOne({_id:1},{x:11},{upsert:true}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } rs:PRIMARY> db.foo.replaceOne({_id:1},{_id:1, x:11},{upsert:true}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 }
Upon further inspection, it appears that modifiedCount is merely correlating with whether or not the update resulted in creation of an oplog entry. After running the above four commands, I observed the following sequence of oplog entries:
rs:PRIMARY> db.oplog.rs.find({op:{$ne:"n"}}).sort({ts:-1}).limit(4).pretty() { "ts" : Timestamp(1533161874, 1), "t" : NumberLong(1), "h" : NumberLong("-7130161787299552975"), "v" : 2, "op" : "u", "ns" : "test.foo", "ui" : UUID("c689af9d-8f23-4897-8e3f-2b055e83cc20"), "o2" : { "_id" : 1 }, "wall" : ISODate("2018-08-01T22:17:54.422Z"), "o" : { "_id" : 1, "x" : 11 } } { "ts" : Timestamp(1533161867, 3), "t" : NumberLong(1), "h" : NumberLong("-1692372989673358044"), "v" : 2, "op" : "i", "ns" : "test.foo", "ui" : UUID("c689af9d-8f23-4897-8e3f-2b055e83cc20"), "wall" : ISODate("2018-08-01T22:17:47.779Z"), "o" : { "_id" : 1, "x" : 11 } } { "ts" : Timestamp(1533161867, 2), "t" : NumberLong(1), "h" : NumberLong("-7615294753132425657"), "v" : 2, "op" : "c", "ns" : "test.$cmd", "ui" : UUID("c689af9d-8f23-4897-8e3f-2b055e83cc20"), "wall" : ISODate("2018-08-01T22:17:47.779Z"), "o" : { "create" : "foo", "idIndex" : { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.foo" } } } { "ts" : Timestamp(1533161867, 1), "t" : NumberLong(1), "h" : NumberLong("-7489916703840618831"), "v" : 2, "op" : "c", "ns" : "test.$cmd", "ui" : UUID("8cdadcb6-b47f-41aa-80d2-b353c0da5117"), "wall" : ISODate("2018-08-01T22:17:47.125Z"), "o" : { "drop" : "foo" } }
The more general question (or bug if there is one) may be: why does a replacement that doesn't logically alter the document result in an oplog entry being created?
If we contrast this with an atomic update that does not alter the matched document, we can see that both of the following examples produce no oplog entry:
rs:PRIMARY> db.foo.updateOne({_id:1},{$set:{x:11}},{upsert:true}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 } rs:PRIMARY> db.foo.updateOne({_id:1},{$set:{_id:1,x:11}},{upsert:true}) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 }
This was observed on a single-member replica set running MongoDB 4.0.0; however, I fully expect older server versions also demonstrate this behavior.
- is related to
-
SERVER-21738 Definitively detect no-op updates
- Backlog
- related to
-
SERVER-47140 Use diffing for full document replacement updates
- Backlog