[SERVER-36405] Update should be smarter about recognizing noop updates by comparing original and "updated" documents Created: 01/Aug/18  Updated: 06/Dec/22

Status: Backlog
Project: Core Server
Component/s: Write Ops
Affects Version/s: None
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Jeremy Mikola Assignee: Backlog - Query Optimization
Resolution: Unresolved Votes: 3
Labels: asya
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
related to SERVER-47140 Use diffing for full document replace... Backlog
is related to SERVER-21738 Definitively detect no-op updates Backlog
Assigned Teams:
Query Optimization
Participants:

 Description   

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.



 Comments   
Comment by Asya Kamsky [ 26/Nov/18 ]

Perhaps this would be a good ticket to re-purpose for all updates detecting no-ops to avoid creating an oplog entry.

Comment by David Storch [ 10/Aug/18 ]

Changing from Bug to Improvement, since there are several cases in which the update subsystem may fail to detect a no-op.

Generated at Thu Feb 08 04:43:00 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.