Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-36405

Update should be smarter about recognizing noop updates by comparing original and "updated" documents

    • Type: Icon: Improvement Improvement
    • Resolution: Unresolved
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: None
    • Component/s: Write Ops
    • Query Optimization

      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.

            Assignee:
            backlog-query-optimization [DO NOT USE] Backlog - Query Optimization
            Reporter:
            jmikola@mongodb.com Jeremy Mikola
            Votes:
            3 Vote for this issue
            Watchers:
            18 Start watching this issue

              Created:
              Updated: