diff options
author | Justin Seyster <justin.seyster@mongodb.com> | 2017-09-14 12:14:54 -0400 |
---|---|---|
committer | Justin Seyster <justin.seyster@mongodb.com> | 2017-09-14 12:14:54 -0400 |
commit | 390e5f47f00dcf133f361e3f9027e4da7d08d628 (patch) | |
tree | 901690cdc5bc1b681e1e2b95c30061784a15187e /jstests | |
parent | 5767ee2421fa6c7934a90e9083f07743a83dcf71 (diff) | |
download | mongo-390e5f47f00dcf133f361e3f9027e4da7d08d628.tar.gz |
SERVER-30705 Add $v field for update semantics in oplog updates.
With the new UpdateNodes class hierarchy, there are two code paths for
applying an update to a document that have slightly different
semantics. The order of fields in the resulting document can vary
depending on which code path is used to apply an update. A difference
in ordering between documents in a replica set is considered a
"mismatch," so we need to ensure that secondaries always apply updates
using the same update system that the primary uses.
When an update executes as part of the application of an oplog entry,
the update is now allowed to have a $v field, which allows it to
specify which semantics were used by the operation that we are
replicating by applying the entry. When the primary uses the new
semantics (because it is a 3.6 mongod with featureCompatibilityVersion
set to 3.6), it includes {$v: 1} in the oplog's update document to
indicate that the secondary should apply with the newer 'UpdateNode'
semantics.
There are two other places where we need this behavior:
1) In role_graph_update.cpp, where the handleOplogUpdate observer
needs to update its in-memory BSON representation of a role to
reflect an update in the admin database and
2) in the applyOps command, which is used for testing how oplog
entries get applied.
Both these code paths set the fromOplogApplication flag, which
replaces the old fromReplication flag, and they also gain behavior
that used to be exclusive to oplog applications from
replication. (Specifically, they skip update validation checks, which
should have already passed before the oplog entry was created.)
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/concurrency/fsm_all_simultaneous.js | 2 | ||||
-rw-r--r-- | jstests/concurrency/fsm_workloads/toggle_feature_compatibility.js | 64 | ||||
-rw-r--r-- | jstests/core/apply_ops1.js | 39 | ||||
-rw-r--r-- | jstests/multiVersion/updates_in_heterogeneous_repl_set.js | 75 | ||||
-rw-r--r-- | jstests/replsets/oplog_format.js | 34 |
5 files changed, 197 insertions, 17 deletions
diff --git a/jstests/concurrency/fsm_all_simultaneous.js b/jstests/concurrency/fsm_all_simultaneous.js index bf4fdfe3d02..f390f262e97 100644 --- a/jstests/concurrency/fsm_all_simultaneous.js +++ b/jstests/concurrency/fsm_all_simultaneous.js @@ -14,6 +14,8 @@ var blacklist = [ 'agg_group_external.js', // uses >100MB of data, which can overwhelm test hosts 'agg_sort_external.js', // uses >100MB of data, which can overwhelm test hosts + + 'toggle_feature_compatibility.js', // Sets FCV to 3.4, which could interefere with other tests. ].map(function(file) { return dir + '/' + file; }); diff --git a/jstests/concurrency/fsm_workloads/toggle_feature_compatibility.js b/jstests/concurrency/fsm_workloads/toggle_feature_compatibility.js new file mode 100644 index 00000000000..0c0ed9297fd --- /dev/null +++ b/jstests/concurrency/fsm_workloads/toggle_feature_compatibility.js @@ -0,0 +1,64 @@ +"use strict"; + +/** + * toggle_feature_compatibility.js + * + * Adds and updates documents in some threads while rapidly toggling the feature + * compatibility version between 3.4 and 3.6 in other threads, triggering the + * failure in SERVER-30705. + */ +var $config = (function() { + + var states = (function() { + + function init(db, collName) { + } + + function featureCompatibilityVersion34(db, collName) { + assert.commandWorked(db.adminCommand({setFeatureCompatibilityVersion: "3.4"})); + } + + function featureCompatibilityVersion36(db, collName) { + assert.commandWorked(db.adminCommand({setFeatureCompatibilityVersion: "3.6"})); + } + + function insertAndUpdate(db, collName) { + let insertID = Random.randInt(1000000000); + let res = db[collName].insert({_id: insertID}); + + // Fail the test on any write error, except for a duplicate key error, which can + // (rarely) happen when we accidentally choose the same random key more than once. + assert(!res.hasWriteError() || res.getWriteError().code == ErrorCodes.DuplicateKey); + assert.writeOK(db[collName].update({_id: insertID}, {$set: {b: 1, a: 1}})); + } + + return { + init: init, + featureCompatibilityVersion34: featureCompatibilityVersion34, + featureCompatibilityVersion36: featureCompatibilityVersion36, + insertAndUpdate: insertAndUpdate + }; + + })(); + + var transitions = { + init: {featureCompatibilityVersion34: 0.5, insertAndUpdate: 0.5}, + featureCompatibilityVersion34: {featureCompatibilityVersion36: 1}, + featureCompatibilityVersion36: {featureCompatibilityVersion34: 1}, + insertAndUpdate: {insertAndUpdate: 1} + }; + + function teardown(db, collName, cluster) { + assert.commandWorked(db.adminCommand({setFeatureCompatibilityVersion: "3.6"})); + assertWhenOwnColl(db[collName].drop()); + } + + return { + threadCount: 8, + iterations: 1000, + data: null, + states: states, + transitions: transitions, + teardown: teardown + }; +})(); diff --git a/jstests/core/apply_ops1.js b/jstests/core/apply_ops1.js index d5729f616cc..cb8ea228501 100644 --- a/jstests/core/apply_ops1.js +++ b/jstests/core/apply_ops1.js @@ -450,4 +450,43 @@ spec = GetIndexHelpers.findByName(allIndexes, "c_1"); assert.neq(null, spec, "Foreground index 'c_1' not found: " + tojson(allIndexes)); assert.eq(2, spec.v, "Expected v=2 index to be built"); + + // When applying a "u" (update) op, we default to 'ModifierInterface' update semantics, and + // $set operations get performed in user order. + res = assert.commandWorked(db.adminCommand({ + applyOps: [ + {"op": "i", "ns": t.getFullName(), "o": {_id: 6}}, + {"op": "u", "ns": t.getFullName(), "o2": {_id: 6}, "o": {$set: {z: 1, a: 2}}} + ] + })); + assert.eq(t.findOne({_id: 6}), {_id: 6, z: 1, a: 2}); + + // When we explicitly specify {$v: 0}, we should also get 'ModifierInterface' update semantics. + res = assert.commandWorked(db.adminCommand({ + applyOps: [ + {"op": "i", "ns": t.getFullName(), "o": {_id: 7}}, + { + "op": "u", + "ns": t.getFullName(), + "o2": {_id: 7}, + "o": {$v: NumberLong(0), $set: {z: 1, a: 2}} + } + ] + })); + assert.eq(t.findOne({_id: 7}), {_id: 7, z: 1, a: 2}); + + // When we explicitly specify {$v: 1}, we should get 'UpdateNode' update semantics, and $set + // operations get performed in lexicographic order. + res = assert.commandWorked(db.adminCommand({ + applyOps: [ + {"op": "i", "ns": t.getFullName(), "o": {_id: 8}}, + { + "op": "u", + "ns": t.getFullName(), + "o2": {_id: 8}, + "o": {$v: NumberLong(1), $set: {z: 1, a: 2}} + } + ] + })); + assert.eq(t.findOne({_id: 8}), {_id: 8, a: 2, z: 1}); // Note: 'a' and 'z' have been sorted. })(); diff --git a/jstests/multiVersion/updates_in_heterogeneous_repl_set.js b/jstests/multiVersion/updates_in_heterogeneous_repl_set.js new file mode 100644 index 00000000000..1f4530889aa --- /dev/null +++ b/jstests/multiVersion/updates_in_heterogeneous_repl_set.js @@ -0,0 +1,75 @@ +// Create a replica set with feature compatibility version set to 3.4, add a +// binVersion 3.4 member, and then update documents with each member as the +// primary. Finally, upgrade the 3.4 member to 3.6, upgrade the replica set to +// feature compatibility version 3.6, and again update documents with each +// member as the primary. + +const testName = "updates_in_heterogeneous_repl_set"; + +(function() { + "use strict"; + + // Initialize the binVersion 3.6 versions of the replica set. + let replTest = + new ReplSetTest({name: testName, nodes: [{binVersion: "latest"}, {binVersion: "latest"}]}); + + replTest.startSet(); + replTest.initiate(); + + let primary = replTest.getPrimary(); + + // Set the feature compatibility version to 3.4. + assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "3.4"})); + + // Add the binVersion 3.4 member fo the replica set. + let binVersion34Node = replTest.add({binVersion: "3.4"}); + replTest.reInitiate(); + replTest.awaitSecondaryNodes(); + + // Give each member a chance to be primary while updating documents. + let collIndex = 0; + replTest.nodes.forEach(function(node) { + replTest.awaitReplication(); + replTest.stepUp(node); + + let coll = node.getDB("test")["coll" + (collIndex++)]; + + for (let id = 0; id < 1000; id++) { + assert.writeOK(coll.insert({_id: id})); + assert.writeOK(coll.update({_id: id}, {$set: {z: 1, a: 2}})); + + // Because we are using the update system from earlier MongodDB + // versions (as a result of using feature compatibility version + // 3.4), we expect to see the new 'z' and 'a' fields to get added in + // the same order as they appeared in the update document. + assert.eq(coll.findOne({_id: id}), {_id: id, z: 1, a: 2}); + } + }); + + // Upgrade the binVersion 3.4 member to binVersion 3.6. + replTest.restart(binVersion34Node, {binVersion: "latest"}); + replTest.awaitSecondaryNodes(); + + // Set the replica set feature compatibility version to 3.6. + primary = replTest.getPrimary(); + assert.commandWorked(primary.adminCommand({setFeatureCompatibilityVersion: "3.6"})); + + // Again, give each member a chance to be primary while updating documents. + replTest.nodes.forEach(function(node) { + replTest.stepUp(node); + + let coll = node.getDB("test")["coll" + (collIndex++)]; + + for (let id = 0; id < 1000; id++) { + assert.writeOK(coll.insert({_id: id})); + assert.writeOK(coll.update({_id: id}, {$set: {z: 1, a: 2}})); + + // We can tell that we are using the new 3.6 update system, because + // it inserts new fields in lexicographic order, causing the 'a' and + // 'z' fields to be swapped. + assert.eq(coll.findOne({_id: id}), {_id: id, a: 2, z: 1}); + } + }); + + replTest.stopSet(); +})(); diff --git a/jstests/replsets/oplog_format.js b/jstests/replsets/oplog_format.js index 5604ae1278b..2fd87e2d665 100644 --- a/jstests/replsets/oplog_format.js +++ b/jstests/replsets/oplog_format.js @@ -37,7 +37,7 @@ assertLastOplog({_id: 1, a: 2}, {_id: 1}, "save " + msg); var res = assert.writeOK(coll.update({}, {$inc: {a: 1}, $set: {b: 2}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: 3, b: 2}, coll.findOne({}), msg); -assertLastOplog({$set: {a: 3, b: 2}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: 3, b: 2}}, {_id: 1}, msg); var msg = "IncRewriteNonExistingField: $inc $set"; coll.save({_id: 1, c: 0}); @@ -45,7 +45,7 @@ assertLastOplog({_id: 1, c: 0}, {_id: 1}, "save " + msg); res = assert.writeOK(coll.update({}, {$inc: {a: 1}, $set: {b: 2}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, c: 0, a: 1, b: 2}, coll.findOne({}), msg); -assertLastOplog({$set: {a: 1, b: 2}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: 1, b: 2}}, {_id: 1}, msg); var msg = "TwoNestedPulls: two $pull"; coll.save({_id: 1, a: {b: [1, 2], c: [1, 2]}}); @@ -53,7 +53,7 @@ assertLastOplog({_id: 1, a: {b: [1, 2], c: [1, 2]}}, {_id: 1}, "save " + msg); res = assert.writeOK(coll.update({}, {$pull: {'a.b': 2, 'a.c': 2}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: {b: [1], c: [1]}}, coll.findOne({}), msg); -assertLastOplog({$set: {'a.b': [1], 'a.c': [1]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {'a.b': [1], 'a.c': [1]}}, {_id: 1}, msg); var msg = "MultiSets: two $set"; coll.save({_id: 1, a: 1, b: 1}); @@ -61,7 +61,7 @@ assertLastOplog({_id: 1, a: 1, b: 1}, {_id: 1}, "save " + msg); res = assert.writeOK(coll.update({}, {$set: {a: 2, b: 2}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: 2, b: 2}, coll.findOne({}), msg); -assertLastOplog({$set: {a: 2, b: 2}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: 2, b: 2}}, {_id: 1}, msg); // More tests to validate the oplog format and correct excution @@ -71,19 +71,19 @@ assertLastOplog({_id: 1, a: 1}, {_id: 1}, "save " + msg); res = assert.writeOK(coll.update({}, {$set: {a: 2}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: 2}, coll.findOne({}), msg); -assertLastOplog({$set: {a: 2}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: 2}}, {_id: 1}, msg); var msg = "bad single $inc"; res = assert.writeOK(coll.update({}, {$inc: {a: 1}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: 3}, coll.findOne({}), msg); -assertLastOplog({$set: {a: 3}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: 3}}, {_id: 1}, msg); var msg = "bad double $set"; res = assert.writeOK(coll.update({}, {$set: {a: 2, b: 2}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: 2, b: 2}, coll.findOne({}), msg); -assertLastOplog({$set: {a: 2, b: 2}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: 2, b: 2}}, {_id: 1}, msg); var msg = "bad save"; assert.writeOK(coll.save({_id: 1, a: [2]})); @@ -94,14 +94,14 @@ var msg = "bad array $inc"; res = assert.writeOK(coll.update({}, {$inc: {"a.0": 1}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: [3]}, coll.findOne({}), msg); -var lastTS = assertLastOplog({$set: {"a.0": 3}}, {_id: 1}, msg); +var lastTS = assertLastOplog({$v: 1, $set: {"a.0": 3}}, {_id: 1}, msg); var msg = "bad $setOnInsert"; res = assert.writeOK(coll.update({}, {$setOnInsert: {a: -1}})); assert.eq(res.nMatched, 1, "update failed for '" + msg + "': " + res.toString()); -assert.docEq({_id: 1, a: [3]}, coll.findOne({}), msg); // No-op -var otherTS = assertLastOplog({$set: {"a.0": 3}}, {_id: 1}, msg); // Nothing new -assert.eq(lastTS, otherTS, "new oplog was not expected -- " + msg); // No new oplog entry +assert.docEq({_id: 1, a: [3]}, coll.findOne({}), msg); // No-op +var otherTS = assertLastOplog({$v: 1, $set: {"a.0": 3}}, {_id: 1}, msg); // Nothing new +assert.eq(lastTS, otherTS, "new oplog was not expected -- " + msg); // No new oplog entry coll.remove({}); assert.eq(coll.find().itcount(), 0, "collection not empty"); @@ -134,14 +134,14 @@ coll.save({_id: 1, a: "foo"}); res = assert.writeOK(coll.update({}, {$push: {c: 18}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: "foo", c: [18]}, coll.findOne({}), msg); -assertLastOplog({$set: {"c": [18]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {"c": [18]}}, {_id: 1}, msg); var msg = "bad array $push $slice"; coll.save({_id: 1, a: {b: [18]}}); res = assert.writeOK(coll.update({_id: {$gt: 0}}, {$push: {"a.b": {$each: [1, 2], $slice: -2}}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: {b: [1, 2]}}, coll.findOne({}), msg); -assertLastOplog({$set: {"a.b": [1, 2]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {"a.b": [1, 2]}}, {_id: 1}, msg); var msg = "bad array $push $sort ($slice -100)"; coll.save({_id: 1, a: {b: [{c: 2}, {c: 1}]}}); @@ -149,7 +149,7 @@ res = assert.writeOK( coll.update({}, {$push: {"a.b": {$each: [{c: -1}], $sort: {c: 1}, $slice: -100}}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: {b: [{c: -1}, {c: 1}, {c: 2}]}}, coll.findOne({}), msg); -assertLastOplog({$set: {"a.b": [{c: -1}, {c: 1}, {c: 2}]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {"a.b": [{c: -1}, {c: 1}, {c: 2}]}}, {_id: 1}, msg); var msg = "bad array $push $slice $sort"; coll.save({_id: 1, a: [{b: 2}, {b: 1}]}); @@ -157,7 +157,7 @@ res = assert.writeOK( coll.update({_id: {$gt: 0}}, {$push: {a: {$each: [{b: -1}], $slice: -2, $sort: {b: 1}}}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: [{b: 1}, {b: 2}]}, coll.findOne({}), msg); -assertLastOplog({$set: {a: [{b: 1}, {b: 2}]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {a: [{b: 1}, {b: 2}]}}, {_id: 1}, msg); var msg = "bad array $push $slice $sort first two"; coll.save({_id: 1, a: {b: [{c: 2}, {c: 1}]}}); @@ -165,7 +165,7 @@ res = assert.writeOK( coll.update({_id: {$gt: 0}}, {$push: {"a.b": {$each: [{c: -1}], $slice: -2, $sort: {c: 1}}}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: {b: [{c: 1}, {c: 2}]}}, coll.findOne({}), msg); -assertLastOplog({$set: {"a.b": [{c: 1}, {c: 2}]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {"a.b": [{c: 1}, {c: 2}]}}, {_id: 1}, msg); var msg = "bad array $push $slice $sort reversed first two"; coll.save({_id: 1, a: {b: [{c: 1}, {c: 2}]}}); @@ -173,6 +173,6 @@ res = assert.writeOK( coll.update({_id: {$gt: 0}}, {$push: {"a.b": {$each: [{c: -1}], $slice: -2, $sort: {c: -1}}}})); assert.eq(res.nModified, 1, "update failed for '" + msg + "': " + res.toString()); assert.docEq({_id: 1, a: {b: [{c: 1}, {c: -1}]}}, coll.findOne({}), msg); -assertLastOplog({$set: {"a.b": [{c: 1}, {c: -1}]}}, {_id: 1}, msg); +assertLastOplog({$v: 1, $set: {"a.b": [{c: 1}, {c: -1}]}}, {_id: 1}, msg); replTest.stopSet(); |