diff options
author | jannaerin <golden.janna@gmail.com> | 2019-04-18 11:40:56 -0400 |
---|---|---|
committer | jannaerin <golden.janna@gmail.com> | 2019-04-18 15:02:16 -0400 |
commit | 724615ae608b123bb2e82dc0731f8e61fab17a82 (patch) | |
tree | 88a80f902bb4dca2b6095f83db7a8f63bb5593e9 /jstests/sharding/update_shard_key_conflicting_writes.js | |
parent | 5ad20825e2c02b14f1aeb5bfb4dcc0b4dce927b2 (diff) | |
download | mongo-724615ae608b123bb2e82dc0731f8e61fab17a82.tar.gz |
SERVER-40655 Test that a retryable write that changes the shard key behaves correctly with conflicting writes
Diffstat (limited to 'jstests/sharding/update_shard_key_conflicting_writes.js')
-rw-r--r-- | jstests/sharding/update_shard_key_conflicting_writes.js | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/jstests/sharding/update_shard_key_conflicting_writes.js b/jstests/sharding/update_shard_key_conflicting_writes.js index f0d6942d1c0..3fc3fb9f416 100644 --- a/jstests/sharding/update_shard_key_conflicting_writes.js +++ b/jstests/sharding/update_shard_key_conflicting_writes.js @@ -341,6 +341,108 @@ assert.eq(0, db.foo.find({"x": -500}).itcount()); })(); + /** + * Test scenarios where a user sends an update as a retryable write that changes the shard key + * and there is a concurrent update/delete that mutates the same document which completes after + * the change to the shard key throws WouldChangeOwningShard the first time, but before mongos + * starts a transaction to change the shard key. + * + * The scenario looks like: + * 1. user sends db.foo.update({shardKey: x}, {shardKey: new x}) + * 2. shard throws WCOS for this update + * 3. user sends db.foo.update({shardKey: x}, {otherFieldInDoc: y}) on a different thread, this + * write completes successfully + * 4. mongos starts a transaction and resends the update on line 1 + * 5. mongos deletes the old doc, inserts a doc with the updated shard key, and commits the txn + */ + + // Assert that if the concurrent update modifies the document so that the update which changes + // the shard key no longer matches the doc, it does not modify the doc. + (() => { + let codeToRunInParallelShell = `{ + let session = db.getMongo().startSession({retryWrites : true}); + let sessionDB = session.getDatabase("db"); + let res = sessionDB.foo.update({"x": -150, "a" : 15}, {$set: {"x": 1000}}); + assert.commandWorked(res); + assert.eq(0, res.nMatched); + assert.eq(0, res.nModified); + }`; + let awaitShell = setFailPointAndSendUpdateToShardKeyInParallelShell( + "hangAfterThrowWouldChangeOwningShardRetryableWrite", + "alwaysOn", + st.s, + codeToRunInParallelShell); + // Send update that changes "a" so that the original update will no longer match this doc. + // Turn off the failpoint so the server stops hanging. + assert.commandWorked(sessionDB2.foo.update({"x": -150}, {$set: {"a": 3000}})); + assert.commandWorked(st.s.adminCommand({ + configureFailPoint: "hangAfterThrowWouldChangeOwningShardRetryableWrite", + mode: "off", + })); + awaitShell(); + assert.eq(1, db.foo.find({"x": -150, "a": 3000}).itcount()); + assert.eq(0, db.foo.find({"a": 15}).itcount()); + assert.eq(0, db.foo.find({"x": 1000}).itcount()); + })(); + + // Assert that if the concurrent update modifies the document and the update which changes the + // shard key still matches the doc, the final document reflects both updates. + (() => { + let codeToRunInParallelShell = `{ + let session = db.getMongo().startSession({retryWrites : true}); + let sessionDB = session.getDatabase("db"); + let res = sessionDB.foo.update({"x": 150}, {$set: {"x": -1000}}); + assert.commandWorked(res); + assert.eq(1, res.nMatched); + assert.eq(1, res.nModified); + }`; + let awaitShell = setFailPointAndSendUpdateToShardKeyInParallelShell( + "hangAfterThrowWouldChangeOwningShardRetryableWrite", + "alwaysOn", + st.s, + codeToRunInParallelShell); + // Send update that changes "a". The original update will still match this doc because it + // queries only on the shard key. Turn off the failpoint so the server stops hanging. + assert.commandWorked(sessionDB2.foo.update({"x": 150}, {$set: {"a": -200}})); + assert.commandWorked(st.s.adminCommand({ + configureFailPoint: "hangAfterThrowWouldChangeOwningShardRetryableWrite", + mode: "off", + })); + awaitShell(); + assert.eq(1, db.foo.find({"x": -1000, "a": -200}).itcount()); + assert.eq(0, db.foo.find({"a": 20}).itcount()); + assert.eq(0, db.foo.find({"x": 150}).itcount()); + })(); + + // Assert that if a concurrent delete removes the same document that the original update + // attempts to modify the shard key for, we don't match any docs. + (() => { + let codeToRunInParallelShell = `{ + let session = db.getMongo().startSession({retryWrites : true}); + let sessionDB = session.getDatabase("db"); + let res = sessionDB.foo.update({"x": -150}, {$set: {"x": 1000}}); + assert.commandWorked(res); + assert.eq(0, res.nMatched); + assert.eq(0, res.nModified); + }`; + let awaitShell = setFailPointAndSendUpdateToShardKeyInParallelShell( + "hangAfterThrowWouldChangeOwningShardRetryableWrite", + "alwaysOn", + st.s, + codeToRunInParallelShell); + // Remove this doc so that the original update will no longer match any doc. + // Turn off the failpoint so the server stops hanging. + assert.commandWorked(sessionDB2.foo.remove({"x": -150})); + assert.commandWorked(st.s.adminCommand({ + configureFailPoint: "hangAfterThrowWouldChangeOwningShardRetryableWrite", + mode: "off", + })); + awaitShell(); + assert.eq(0, db.foo.find({"x": -150}).itcount()); + assert.eq(0, db.foo.find({"a": 3000}).itcount()); + assert.eq(0, db.foo.find({"x": 1000}).itcount()); + })(); + st.stop(); }()); |