diff options
author | William Schultz <william.schultz@mongodb.com> | 2018-12-21 16:33:36 -0500 |
---|---|---|
committer | William Schultz <william.schultz@mongodb.com> | 2018-12-21 16:43:58 -0500 |
commit | 435200964a1fc9de566e74bd579e99c308551670 (patch) | |
tree | f6d4e2723f794200b900c914818c8d9645df50f0 /jstests | |
parent | f15556ae1ba4f78d2823d54e38d7025c7e9ca4fb (diff) | |
download | mongo-435200964a1fc9de566e74bd579e99c308551670.tar.gz |
SERVER-37560 Allow change streams to work with speculative majority reads
This patch allows change stream queries to use speculative majority reads so that they can be used even when enableMajorityReadConcern:false. Change stream aggregation commands are allowed to use speculative majority reads as well as 'find' commands that specify a special flag. This commit also enables all change streams tests and suites on the enableMajorityReadConcern:false Evergreen variant. No optimizations for speculative majority change streams are included in this commit.
Diffstat (limited to 'jstests')
9 files changed, 538 insertions, 0 deletions
diff --git a/jstests/replsets/change_stream_speculative_majority.js b/jstests/replsets/change_stream_speculative_majority.js new file mode 100644 index 00000000000..abd08675bee --- /dev/null +++ b/jstests/replsets/change_stream_speculative_majority.js @@ -0,0 +1,84 @@ +/** + * Test basic, steady-state replication change stream functionality with speculative majority reads. + */ +(function() { + "use strict"; + + load("jstests/libs/write_concern_util.js"); // for [stop|restart]ServerReplication. + + const name = "change_stream_speculative_majority"; + const replTest = new ReplSetTest({ + name: name, + nodes: [{}, {rsConfig: {priority: 0}}], + nodeOptions: {enableMajorityReadConcern: 'false'} + }); + replTest.startSet(); + replTest.initiate(); + + const dbName = name; + const collName = "coll"; + + let primary = replTest.getPrimary(); + let secondary = replTest.getSecondary(); + let primaryDB = primary.getDB(dbName); + let primaryColl = primaryDB[collName]; + + // Open a change stream. + let res = primaryDB.runCommand( + {aggregate: collName, pipeline: [{$changeStream: {}}], cursor: {}, maxTimeMS: 5000}); + assert.commandWorked(res); + let cursorId = res.cursor.id; + + // Insert a document on primary and let it majority commit. + assert.commandWorked(primaryColl.insert({_id: 1}, {writeConcern: {w: "majority"}})); + + // Receive the first change event. + res = primary.getDB(dbName).runCommand({getMore: cursorId, collection: collName}); + let changes = res.cursor.nextBatch; + assert.eq(changes.length, 1); + assert.eq(changes[0]["fullDocument"], {_id: 1}); + assert.eq(changes[0]["operationType"], "insert"); + + // Save the resume token. + let resumeToken = changes[0]["_id"]; + + // This query should time out waiting for new results and return an empty batch. + res = primary.getDB(dbName).runCommand( + {getMore: cursorId, collection: collName, maxTimeMS: 5000}); + assert.eq(res.cursor.nextBatch, []); + + // Pause replication on the secondary so that writes won't majority commit. + stopServerReplication(secondary); + + // Do a new write on primary. + assert.commandWorked(primaryColl.insert({_id: 2})); + + // The change stream query should time out waiting for the new result to majority commit. + res = primary.getDB(dbName).runCommand( + {getMore: cursorId, collection: collName, maxTimeMS: 5000}); + assert.commandFailedWithCode(res, ErrorCodes.MaxTimeMSExpired); + + // An aggregate trying to resume a stream that includes the change should also time out. + res = primaryDB.runCommand({ + aggregate: collName, + pipeline: [{$changeStream: {resumeAfter: resumeToken}}], + cursor: {}, + maxTimeMS: 5000 + }); + assert.commandFailedWithCode(res, ErrorCodes.MaxTimeMSExpired); + + // Resume the stream after restarting replication. We should now be able to see the new event. + restartServerReplication(secondary); + replTest.awaitReplication(); + + // Re-open the stream, and receive the new event. + res = primaryDB.runCommand( + {aggregate: collName, pipeline: [{$changeStream: {resumeAfter: resumeToken}}], cursor: {}}); + assert.commandWorked(res); + changes = res.cursor.firstBatch; + assert.eq(changes.length, 1); + assert.eq(changes[0]["fullDocument"], {_id: 2}); + assert.eq(changes[0]["operationType"], "insert"); + + replTest.stopSet(); +})();
\ No newline at end of file diff --git a/jstests/replsets/change_stream_speculative_majority_latest_oplog_timestamp.js b/jstests/replsets/change_stream_speculative_majority_latest_oplog_timestamp.js new file mode 100644 index 00000000000..01359b1caa9 --- /dev/null +++ b/jstests/replsets/change_stream_speculative_majority_latest_oplog_timestamp.js @@ -0,0 +1,89 @@ +/** + * Test that change streams using speculative majority wait for the latest observed oplog timestamp + * to majority commit. + * + * If a change stream query returns a batch containing oplog entries no newer than timestamp T, the + * server may still report the latest majority committed oplog timestamp that it observed while + * scanning the oplog, which may be greater than T. A mongoS will use this timestamp as a guarantee + * that no new change events will occur at a lesser timestamp. This guarantee is only valid if the + * timestamp is actually majority committed, so we need to make sure that guarantee holds, even when + * using speculative majority. + */ +(function() { + "use strict"; + + load("jstests/libs/write_concern_util.js"); // for [stop|restart]ServerReplication. + + const name = "change_stream_speculative_majority_latest_oplog_timestamp"; + const replTest = new ReplSetTest({ + name: name, + nodes: [{}, {rsConfig: {priority: 0}}], + nodeOptions: {enableMajorityReadConcern: 'false'} + }); + replTest.startSet(); + replTest.initiate(); + + const dbName = name; + const collName = "coll"; + const otherCollName = "coll_other"; + + const primary = replTest.getPrimary(); + const secondary = replTest.getSecondary(); + + const primaryDB = primary.getDB(dbName); + const primaryColl = primaryDB[collName]; + + assert.commandWorked(primaryColl.insert({_id: 0}, {writeConcern: {w: "majority"}})); + + let res = primaryDB.runCommand({ + aggregate: collName, + pipeline: [{$changeStream: {}}], + cursor: {}, + maxTimeMS: 5000, + needsMerge: true, + fromMongos: true + }); + + assert.commandWorked(res); + let cursorId = res.cursor.id; + + // Insert a document on primary and let it majority commit. + assert.commandWorked(primaryColl.insert({_id: 1}, {writeConcern: {w: "majority"}})); + + // Receive the first change event. + res = primary.getDB(dbName).runCommand({getMore: cursorId, collection: collName}); + let changes = res.cursor.nextBatch; + assert.eq(changes.length, 1); + assert.eq(changes[0]["fullDocument"], {_id: 1}); + assert.eq(changes[0]["operationType"], "insert"); + + // Pause replication on the secondary so that writes won't majority commit. + jsTestLog("Stopping replication to secondary."); + stopServerReplication(secondary); + + // Do a write on a collection that we are not watching changes for. + let otherWriteRes = primaryDB.runCommand({insert: otherCollName, documents: [{_id: 1}]}); + let otherWriteOpTime = otherWriteRes.operationTime; + + // Replication to the secondary is paused, so the write to 'otherCollName' cannot majority + // commit. A change stream getMore is expected to return the "latest oplog timestamp" which it + // scanned and this timestamp must be majority committed. So, this getMore should time out + // waiting for the previous write to majority commit, even though it's on a collection that is + // not being watched. + res = primary.getDB(dbName).runCommand( + {getMore: cursorId, collection: collName, maxTimeMS: 5000}); + assert.commandFailedWithCode(res, ErrorCodes.MaxTimeMSExpired); + + jsTestLog("Restarting replication to secondary."); + restartServerReplication(secondary); + replTest.awaitReplication(); + + // Now that writes can replicate again, the previous operation should have majority committed, + // making it safe to return as the latest oplog timestamp. + res = primary.getDB(dbName).runCommand( + {getMore: cursorId, collection: collName, maxTimeMS: 5000}); + assert.eq(res.cursor.nextBatch, []); + assert.eq(otherWriteOpTime, res.$_internalLatestOplogTimestamp); + + replTest.stopSet(); +})();
\ No newline at end of file diff --git a/jstests/replsets/change_stream_speculative_majority_rollback.js b/jstests/replsets/change_stream_speculative_majority_rollback.js new file mode 100644 index 00000000000..104269ff8a3 --- /dev/null +++ b/jstests/replsets/change_stream_speculative_majority_rollback.js @@ -0,0 +1,104 @@ +/** + * Test change stream behavior with speculative majority reads in the face of replication rollback. + */ +(function() { + 'use strict'; + + load("jstests/replsets/libs/rollback_test.js"); // for RollbackTest. + + // Disable implicit sessions so it's easy to run commands from different threads. + TestData.disableImplicitSessions = true; + + const name = "change_stream_speculative_majority_rollback"; + const dbName = name; + const collName = "coll"; + + // Set up a replica set for use in RollbackTest. We disable majority reads on all nodes so we + // will utilize speculative majority reads for change streams. + const replTest = new ReplSetTest( + {name, nodes: 3, useBridge: true, nodeOptions: {enableMajorityReadConcern: "false"}}); + replTest.startSet(); + const nodes = replTest.nodeList(); + replTest.initiate({ + _id: name, + members: [ + {_id: 0, host: nodes[0]}, + {_id: 1, host: nodes[1]}, + {_id: 2, host: nodes[2], arbiterOnly: true} + ] + }); + + const rollbackTest = new RollbackTest(name, replTest); + const primary = rollbackTest.getPrimary(); + const primaryDB = primary.getDB(dbName); + let coll = primaryDB[collName]; + + // Create a collection. + assert.commandWorked(coll.insert({_id: 0}, {writeConcern: {w: "majority"}})); + + // Open a change stream on the initial primary. + let res = + primaryDB.runCommand({aggregate: collName, pipeline: [{$changeStream: {}}], cursor: {}}); + assert.commandWorked(res); + let cursorId = res.cursor.id; + + // Receive an initial change event and save the resume token. + assert.commandWorked(coll.insert({_id: 1}, {writeConcern: {w: "majority"}})); + res = primaryDB.runCommand({getMore: cursorId, collection: collName}); + let changes = res.cursor.nextBatch; + assert.eq(changes.length, 1); + assert.eq(changes[0]["fullDocument"], {_id: 1}); + assert.eq(changes[0]["operationType"], "insert"); + let resumeToken = changes[0]["_id"]; + + let rollbackNode = rollbackTest.transitionToRollbackOperations(); + assert.eq(rollbackNode, primary); + + // Insert a few items that will be rolled back. + assert.commandWorked(coll.insert({_id: 2})); + assert.commandWorked(coll.insert({_id: 3})); + assert.commandWorked(coll.insert({_id: 4})); + + let getChangeEvent = new ScopedThread(function(host, cursorId, dbName, collName) { + jsTestLog("Trying to receive change event from divergent primary."); + const nodeDB = new Mongo(host).getDB(dbName); + try { + return nodeDB.runCommand({getMore: eval(cursorId), collection: collName}); + } catch (e) { + return isNetworkError(e); + } + }, rollbackNode.host, tojson(cursorId), dbName, collName); + getChangeEvent.start(); + + // Make sure the change stream query started. + assert.soon(() => primaryDB.currentOp({"command.getMore": cursorId}).inprog.length === 1); + + // Do some operations on the new primary that we can receive in a resumed stream. + let syncSource = rollbackTest.transitionToSyncSourceOperationsBeforeRollback(); + coll = syncSource.getDB(dbName)[collName]; + assert.commandWorked(coll.insert({_id: 5})); + assert.commandWorked(coll.insert({_id: 6})); + assert.commandWorked(coll.insert({_id: 7})); + + // Let rollback begin and complete. + rollbackTest.transitionToSyncSourceOperationsDuringRollback(); + rollbackTest.transitionToSteadyStateOperations(); + + // The change stream query should have failed when the node entered rollback. + assert(getChangeEvent.returnData()); + + jsTestLog("Resuming change stream against new primary."); + res = syncSource.getDB(dbName).runCommand( + {aggregate: collName, pipeline: [{$changeStream: {resumeAfter: resumeToken}}], cursor: {}}); + changes = res.cursor.firstBatch; + assert.eq(changes.length, 3); + assert.eq(changes[0]["fullDocument"], {_id: 5}); + assert.eq(changes[0]["operationType"], "insert"); + assert.eq(changes[1]["fullDocument"], {_id: 6}); + assert.eq(changes[1]["operationType"], "insert"); + assert.eq(changes[2]["fullDocument"], {_id: 7}); + assert.eq(changes[2]["operationType"], "insert"); + + rollbackTest.stop(); + +})(); diff --git a/jstests/replsets/speculative_majority_find.js b/jstests/replsets/speculative_majority_find.js new file mode 100644 index 00000000000..e6c3a41ef88 --- /dev/null +++ b/jstests/replsets/speculative_majority_find.js @@ -0,0 +1,155 @@ +/** + * Test speculative majority reads using the 'find' command. + * + * Speculative majority reads allow the server to provide "majority" read guarantees without storage + * engine support for reading from a historical snapshot. Instead of reading historical, majority + * committed data, we just read the newest data available on a node, and then, before returning to a + * client, block until we know the data has become majority committed. Currently this is an internal + * feature used only by change streams. + */ +(function() { + "use strict"; + + load("jstests/libs/write_concern_util.js"); // for [stop|restart]ServerReplication. + load("jstests/libs/parallelTester.js"); // for ScopedThread. + + let name = "speculative_majority_find"; + let replTest = new ReplSetTest({ + name: name, + nodes: [{}, {rsConfig: {priority: 0}}], + nodeOptions: {enableMajorityReadConcern: 'false'} + }); + replTest.startSet(); + replTest.initiate(); + + let dbName = name; + let collName = "coll"; + + let primary = replTest.getPrimary(); + let secondary = replTest.getSecondary(); + + let primaryDB = primary.getDB(dbName); + let secondaryDB = secondary.getDB(dbName); + let primaryColl = primaryDB[collName]; + // Create a collection. + assert.commandWorked(primaryColl.insert({}, {writeConcern: {w: "majority"}})); + + // + // Test basic reads with speculative majority. + // + + // Pause replication on the secondary so that writes won't majority commit. + stopServerReplication(secondary); + assert.commandWorked(primaryColl.insert({_id: 1})); + + jsTestLog("Do a speculative majority read that should time out."); + let res = primaryDB.runCommand({ + find: collName, + readConcern: {level: "majority"}, + filter: {_id: 1}, + allowSpeculativeMajorityRead: true, + maxTimeMS: 5000 + }); + assert.commandFailedWithCode(res, ErrorCodes.MaxTimeMSExpired); + + restartServerReplication(secondary); + replTest.awaitReplication(); + + jsTestLog("Do a speculative majority read that should succeed."); + res = primaryDB.runCommand({ + find: collName, + readConcern: {level: "majority"}, + filter: {_id: 1}, + allowSpeculativeMajorityRead: true + }); + assert.commandWorked(res); + assert.eq(res.cursor.firstBatch.length, 1); + assert.eq(res.cursor.firstBatch[0], {_id: 1}); + + // + // Test that blocked reads can succeed when a write majority commits. + // + + // Pause replication on the secondary so that writes won't majority commit. + stopServerReplication(secondary); + assert.commandWorked(primaryColl.insert({_id: 2})); + + jsTestLog("Do a speculative majority that should block until write commits."); + let speculativeRead = new ScopedThread(function(host, dbName, collName) { + const nodeDB = new Mongo(host).getDB(dbName); + return nodeDB.runCommand({ + find: collName, + readConcern: {level: "majority"}, + filter: {_id: 2}, + allowSpeculativeMajorityRead: true + }); + }, primary.host, dbName, collName); + speculativeRead.start(); + + // Wait for the read to start on the server. + assert.soon(() => primaryDB.currentOp({ns: primaryColl.getFullName(), "command.find": collName}) + .inprog.length === 1); + + // Let the previous write commit. + restartServerReplication(secondary); + assert.commandWorked( + primaryColl.insert({_id: "commit_last_write"}, {writeConcern: {w: "majority"}})); + + // Make sure the read finished and returned correct results. + speculativeRead.join(); + res = speculativeRead.returnData(); + assert.commandWorked(res); + assert.eq(res.cursor.firstBatch.length, 1); + assert.eq(res.cursor.firstBatch[0], {_id: 2}); + + // + // Test 'afterClusterTime' reads with speculative majority. + // + stopServerReplication(secondary); + + // Insert a document on the primary and record the response. + let writeRes = primaryDB.runCommand({insert: collName, documents: [{_id: 3}]}); + assert.commandWorked(writeRes); + + jsTestLog( + "Do a speculative majority read on primary with 'afterClusterTime' that should time out."); + res = primaryDB.runCommand({ + find: collName, + readConcern: {level: "majority", afterClusterTime: writeRes.operationTime}, + filter: {_id: 3}, + $clusterTime: writeRes.$clusterTime, + allowSpeculativeMajorityRead: true, + maxTimeMS: 5000 + }); + assert.commandFailedWithCode(res, ErrorCodes.MaxTimeMSExpired); + + jsTestLog( + "Do a speculative majority read on secondary with 'afterClusterTime' that should time out."); + res = secondaryDB.runCommand({ + find: collName, + readConcern: {level: "majority", afterClusterTime: writeRes.operationTime}, + filter: {_id: 3}, + $clusterTime: writeRes.$clusterTime, + allowSpeculativeMajorityRead: true, + maxTimeMS: 5000 + }); + assert.commandFailedWithCode(res, ErrorCodes.MaxTimeMSExpired); + + // Let the previous write majority commit. + restartServerReplication(secondary); + replTest.awaitReplication(); + + jsTestLog("Do a speculative majority read with 'afterClusterTime' that should succeed."); + res = primaryDB.runCommand({ + find: collName, + readConcern: {level: "majority", afterClusterTime: writeRes.operationTime}, + filter: {_id: 3}, + $clusterTime: res.$clusterTime, + allowSpeculativeMajorityRead: true + }); + assert.commandWorked(res); + assert.eq(res.cursor.firstBatch.length, 1); + assert.eq(res.cursor.firstBatch[0], {_id: 3}); + + replTest.stopSet(); +})();
\ No newline at end of file diff --git a/jstests/replsets/speculative_majority_supported_commands.js b/jstests/replsets/speculative_majority_supported_commands.js new file mode 100644 index 00000000000..ad8d898dbf9 --- /dev/null +++ b/jstests/replsets/speculative_majority_supported_commands.js @@ -0,0 +1,74 @@ +/** + * Verify that speculative majority is only allowed on supported commands. + * + * Currently, only change stream aggregation commands and the 'find' command with the + * 'allowSpeculativeMajorityRead' flag are permitted. + */ +(function() { + "use strict"; + + let name = "speculative_majority_supported_commands"; + let replTest = + new ReplSetTest({name: name, nodes: 1, nodeOptions: {enableMajorityReadConcern: 'false'}}); + replTest.startSet(); + replTest.initiate(); + + let dbName = name; + let collName = "coll"; + + let primary = replTest.getPrimary(); + let primaryDB = primary.getDB(dbName); + + // Create a collection. + assert.commandWorked(primaryDB[collName].insert({_id: 0}, {writeConcern: {w: "majority"}})); + + /** + * Allowed commands. + */ + + // Change stream aggregation is allowed. + let res = primaryDB.runCommand({ + aggregate: collName, + pipeline: [{$changeStream: {}}], + cursor: {}, + readConcern: {level: "majority"} + }); + assert.commandWorked(res); + + // Find query with speculative flag is allowed. + res = primaryDB.runCommand( + {find: collName, readConcern: {level: "majority"}, allowSpeculativeMajorityRead: true}); + assert.commandWorked(res); + + /** + * Disallowed commands. + */ + + // A non change stream aggregation is not allowed. + res = primaryDB.runCommand({ + aggregate: collName, + pipeline: [{$project: {}}], + cursor: {}, + readConcern: {level: "majority"} + }); + assert.commandFailedWithCode(res, ErrorCodes.ReadConcernMajorityNotEnabled); + + // The 'find' command without requisite flag is unsupported. + res = primaryDB.runCommand({find: collName, readConcern: {level: "majority"}}); + assert.commandFailedWithCode(res, ErrorCodes.ReadConcernMajorityNotEnabled); + + res = primaryDB.runCommand( + {find: collName, readConcern: {level: "majority"}, allowSpeculativeMajorityRead: false}); + assert.commandFailedWithCode(res, ErrorCodes.ReadConcernMajorityNotEnabled); + + // Another basic read command. We don't exhaustively check all commands. + res = primaryDB.runCommand({count: collName, readConcern: {level: "majority"}}); + assert.commandFailedWithCode(res, ErrorCodes.ReadConcernMajorityNotEnabled); + + // Speculative flag is only allowed on find commands. + res = primaryDB.runCommand( + {count: collName, readConcern: {level: "majority"}, allowSpeculativeMajorityRead: true}); + assert.commandFailedWithCode(res, ErrorCodes.ReadConcernMajorityNotEnabled); + + replTest.stopSet(); +})();
\ No newline at end of file diff --git a/jstests/sharding/change_stream_lookup_single_shard_cluster.js b/jstests/sharding/change_stream_lookup_single_shard_cluster.js index ac00ea33d34..60ded0f352d 100644 --- a/jstests/sharding/change_stream_lookup_single_shard_cluster.js +++ b/jstests/sharding/change_stream_lookup_single_shard_cluster.js @@ -8,6 +8,14 @@ // For supportsMajorityReadConcern. load('jstests/multiVersion/libs/causal_consistency_helpers.js'); + // TODO (SERVER-38673): Remove this once BACKPORT-3428, BACKPORT-3429 are completed. + if (!jsTestOptions().enableMajorityReadConcern && + jsTestOptions().mongosBinVersion === 'last-stable') { + jsTestLog( + "Skipping test since 'last-stable' mongos doesn't support speculative majority update lookup queries."); + return; + } + // This test only works on storage engines that support committed reads, skip it if the // configured engine doesn't support it. if (!supportsMajorityReadConcern()) { diff --git a/jstests/sharding/change_stream_read_preference.js b/jstests/sharding/change_stream_read_preference.js index 25a7b5ef061..4f35b42424a 100644 --- a/jstests/sharding/change_stream_read_preference.js +++ b/jstests/sharding/change_stream_read_preference.js @@ -9,6 +9,14 @@ // For supportsMajorityReadConcern. load('jstests/multiVersion/libs/causal_consistency_helpers.js'); + // TODO (SERVER-38673): Remove this once BACKPORT-3428, BACKPORT-3429 are completed. + if (!jsTestOptions().enableMajorityReadConcern && + jsTestOptions().mongosBinVersion === 'last-stable') { + jsTestLog( + "Skipping test since 'last-stable' mongos doesn't support speculative majority update lookup queries."); + return; + } + // This test only works on storage engines that support committed reads, skip it if the // configured engine doesn't support it. if (!supportsMajorityReadConcern()) { diff --git a/jstests/sharding/change_stream_update_lookup_collation.js b/jstests/sharding/change_stream_update_lookup_collation.js index 6c57aba30e0..eefff9d463f 100644 --- a/jstests/sharding/change_stream_update_lookup_collation.js +++ b/jstests/sharding/change_stream_update_lookup_collation.js @@ -9,6 +9,14 @@ // For supportsMajorityReadConcern(). load("jstests/multiVersion/libs/causal_consistency_helpers.js"); + // TODO (SERVER-38673): Remove this once BACKPORT-3428, BACKPORT-3429 are completed. + if (!jsTestOptions().enableMajorityReadConcern && + jsTestOptions().mongosBinVersion === 'last-stable') { + jsTestLog( + "Skipping test since 'last-stable' mongos doesn't support speculative majority update lookup queries."); + return; + } + if (!supportsMajorityReadConcern()) { jsTestLog("Skipping test since storage engine doesn't support majority read concern."); return; diff --git a/jstests/sharding/change_streams_primary_shard_unaware.js b/jstests/sharding/change_streams_primary_shard_unaware.js index 0dade0d553c..1fdb86564ae 100644 --- a/jstests/sharding/change_streams_primary_shard_unaware.js +++ b/jstests/sharding/change_streams_primary_shard_unaware.js @@ -12,6 +12,14 @@ // For supportsMajorityReadConcern(). load("jstests/multiVersion/libs/causal_consistency_helpers.js"); + // TODO (SERVER-38673): Remove this once BACKPORT-3428, BACKPORT-3429 are completed. + if (!jsTestOptions().enableMajorityReadConcern && + jsTestOptions().mongosBinVersion === 'last-stable') { + jsTestLog( + "Skipping test since 'last-stable' mongos doesn't support speculative majority update lookup queries."); + return; + } + if (!supportsMajorityReadConcern()) { jsTestLog("Skipping test since storage engine doesn't support majority read concern."); return; |