diff options
author | Josef Ahmad <josef.ahmad@mongodb.com> | 2022-09-28 14:48:05 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-09-28 15:13:56 +0000 |
commit | cbc2fa05af5f8ffe0f2c8f3e37bdd9ac34b3ac85 (patch) | |
tree | 1f822ba933ffe05e2c1b4ff174b7962c148f41c3 /jstests | |
parent | a2bde0ebc259bf57218f16baa9f8d2a4cbb789e4 (diff) | |
download | mongo-cbc2fa05af5f8ffe0f2c8f3e37bdd9ac34b3ac85.tar.gz |
SERVER-67538 Make multi-doc txns return WCE on index catalog changes
Background: SERVER-47866 stopped bumping the collection's minimum
visibility timestamp on catalog changes related to an index; only the
index's minimum visibility snapshot continues to be updated. One side
effect of this change is that a multi-document transaction can read a
at a snapshot where the index is not yet ready and commit at a
timestamp when the index is ready, which not intended behaviour and
can open the opportunity for a race to happen.
This patch introduces a check for the indices' minimum visible timestamp.
Attempting to write to an index entry while reading at an incompatible
timestamp returns a write conflict exception. Locking rules guarantee that
we see a consistent in-memory view of the indices' minimum visible
snapshot.
(cherry picked from commit a4bd3ce3607d2c3020d7efa3501240ae4b1a1b03)
(cherry picked from commit 4e80712214658e3c70cecef42680618068448e7f)
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/noPassthrough/txn_index_catalog_changes.js | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/jstests/noPassthrough/txn_index_catalog_changes.js b/jstests/noPassthrough/txn_index_catalog_changes.js new file mode 100644 index 00000000000..b29a8129a8b --- /dev/null +++ b/jstests/noPassthrough/txn_index_catalog_changes.js @@ -0,0 +1,80 @@ +/** + * Verifies that a multi-document transaction aborts with WriteConflictError if an index build has + * committed since the transaction's read snapshot. + * + * @tags: [ + * requires_replication, + * ] + */ +(function() { +'use strict'; + +const replTest = new ReplSetTest({nodes: 2}); +replTest.startSet(); +replTest.initiate(); + +const primary = replTest.getPrimary(); +const db = primary.getDB('test'); + +// Transaction inserting an index key. +{ + assert.commandWorked(db['c'].insertOne({_id: 0, num: 0})); + + const s0 = db.getMongo().startSession(); + s0.startTransaction(); + assert.commandWorked(s0.getDatabase('test')['c'].deleteOne({_id: 0})); + s0.commitTransaction(); + + const clusterTime = s0.getClusterTime().clusterTime; + + assert.commandWorked(db['c'].createIndex({num: 1})); + + // Start a transaction whose snapshot predates the completion of the index build, and which + // reserves an oplog entry after the index build commits. + try { + const s1 = db.getMongo().startSession(); + s1.startTransaction({readConcern: {level: "snapshot", atClusterTime: clusterTime}}); + s1.getDatabase('test').c.insertOne({_id: 1, num: 1}); + + // Transaction should have failed. + assert(0); + } catch (e) { + assert(e.hasOwnProperty("errorLabels"), tojson(e)); + assert.contains("TransientTransactionError", e.errorLabels, tojson(e)); + assert.eq(e["code"], ErrorCodes.WriteConflict, tojson(e)); + } +} + +db.c.drop(); + +// Transaction deleting an index key. +{ + assert.commandWorked(db.createCollection('c')); + + const s0 = db.getMongo().startSession(); + s0.startTransaction(); + assert.commandWorked(s0.getDatabase('test')['c'].insertOne({_id: 0, num: 0})); + s0.commitTransaction(); + + const clusterTime = s0.getClusterTime().clusterTime; + + assert.commandWorked(db['c'].createIndex({num: 1})); + + // Start a transaction whose snapshot predates the completion of the index build, and which + // reserves an oplog entry after the index build commits. + try { + const s1 = db.getMongo().startSession(); + s1.startTransaction({readConcern: {level: "snapshot", atClusterTime: clusterTime}}); + s1.getDatabase('test').c.deleteOne({_id: 0}); + + // Transaction should have failed. + assert(0); + } catch (e) { + assert(e.hasOwnProperty("errorLabels"), tojson(e)); + assert.contains("TransientTransactionError", e.errorLabels, tojson(e)); + assert.eq(e["code"], ErrorCodes.WriteConflict, tojson(e)); + } +} + +replTest.stopSet(); +})(); |