diff options
author | Luis Osta <luis.osta@mongodb.com> | 2020-07-01 15:08:41 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-08-12 04:56:29 +0000 |
commit | 1dd91dee7c8cb437a893cd7f6ff22d0bd26ee1d4 (patch) | |
tree | 977c5d646b7272adc12154ecca69706f503f5fa9 /jstests/sharding/safe_secondary_reads_causal_consistency.js | |
parent | 4346b20b96f5e4c04d4556c02423562177488e18 (diff) | |
download | mongo-1dd91dee7c8cb437a893cd7f6ff22d0bd26ee1d4.tar.gz |
SERVER-48679 flushRoutingTableCacheUpdates should block on critical section with kWrite, not kRead
(cherry picked from commit 9f4b81e5bdcf38f9b10459203a804ba406528770)
Diffstat (limited to 'jstests/sharding/safe_secondary_reads_causal_consistency.js')
-rw-r--r-- | jstests/sharding/safe_secondary_reads_causal_consistency.js | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/jstests/sharding/safe_secondary_reads_causal_consistency.js b/jstests/sharding/safe_secondary_reads_causal_consistency.js new file mode 100644 index 00000000000..3c1167fffe8 --- /dev/null +++ b/jstests/sharding/safe_secondary_reads_causal_consistency.js @@ -0,0 +1,164 @@ +/** + * Tests that donor shard's secondaries correctly + * block reads and refresh metadata cache when migration occurs. + */ + +(function() { +"use strict"; + +load("jstests/libs/fail_point_util.js"); +load("./jstests/libs/chunk_manipulation_util.js"); + +/** + * @summary This function executes a count command with read preference "secondary" and returns the + * command response + * @param {DBObject} db - The DB connection gotten from the shardingTest object that contains + * the collection the query will be run against. + * @param {String} collectionName - The string name of the collection the read query will be run + * against + * @param {Object} query the fields by which the read command should query by. For example {_id: 1, + * x: 4}, etc + * @param {Object} optionalParams - non-required additional parameters to include into the read + * query. + * * ^ Currently only supports readConcern and maxTimeMS + * @returns {Object} - returns the an object with the total number of documents that matched the + * query on property 'n' + */ +const runReadCmdWithReadPrefSecondary = (db, collectionName, query, {readConcern, maxTimeMS}) => { + return db.runCommand({ + count: collectionName, + query, + $readPreference: {mode: "secondary"}, + readConcern, + maxTimeMS + }); +}; + +/** + * @summary This function does three things + * 1.) Enables sharding for the database, + * 2.) Shard the collection with the given namespace and uses _id as the shardkey, + * 3.) Splits the chunk on the sharded collection based of {_id} as the middle of the chunk split. + * @param {ShardingTest} shardingTest The instance of the initialized shardingTest + * @param {String} dbName The string name of the database in which sharding will be enabled. Also + * the DB that contains the collection that will be sharded + * @param {String} collectionNamespace The namespace (DB Name + Collection Name) of the collection + * that will be sharded. After sharding the namespace will be used for chunk splitting + * @param {String} primaryShardName - Used to ensure the desired shard becomes the primary shard. + */ +const setupShardedCollection = (shardingTest, dbName, collectionNamespace, primaryShardName) => { + assert.commandWorked(shardingTest.s.adminCommand({enableSharding: dbName})); + + shardingTest.ensurePrimaryShard(dbName, primaryShardName); + assert.commandWorked(shardingTest.s.adminCommand({ + shardCollection: collectionNamespace, + key: {_id: 1}, + })); + assert.commandWorked(shardingTest.s.adminCommand({ + split: collectionNamespace, + middle: {_id: 0}, + })); +}; + +const dbName1 = "alpha"; +const dbName2 = "beta"; + +const collName1 = "foo"; +const collName2 = "bar"; + +const ns1 = `${dbName1}.${collName1}`; +const ns2 = `${dbName2}.${collName2}`; + +const preCommitRefreshFailPointName = "hangBeforePostMigrationCommitRefresh"; + +const st = new ShardingTest({ + mongos: 2, + shards: 2, + rs: {nodes: [{}, {rsConfig: {priority: 0}}]}, + causalConsistency: true, +}); + +const mongos0AlphaDB = st.s0.getDB(dbName1); +const mongos1AlphaDB = st.s1.getDB(dbName1); +const mongos1BetaDB = st.s1.getDB(dbName2); + +jsTest.log(`Sharding collection: ${ns1} and ${ns2}.`); +setupShardedCollection(st, dbName1, ns1, st.shard0.shardName); +setupShardedCollection(st, dbName2, ns2, st.shard1.shardName); + +// Starts a migration in a parallel shell to move the chunk [0, maxKey) in ns1 from shard0 to +// shard1. Then it pauses the migration right before it enters the commit phase. + +jsTest.log("Starting migration."); + +const staticMongod = MongoRunner.runMongod({}); +const joinMoveChunk = moveChunkParallel( + staticMongod, st.s.host, {_id: 1}, null, ns1, st.shard1.shardName, true /** expectSuccess */ +); +pauseMoveChunkAtStep(st.shard0, moveChunkStepNames.chunkDataCommitted); +waitForMoveChunkStep(st.shard0, moveChunkStepNames.chunkDataCommitted); + +// Sends a versioned read through mongos1, the second router, to cause a refresh on the secondary of +// the donor shard. Since it runs while the migration is paused on 'chunkDataCommitted' the primary +// is only blocking writes and if the secondary isn't checking for writes being blocked when sending +// 'flushRoutingTableCacheUpdates' the secondary will serve reads for stale mongoses. + +jsTest.log("Sending a read query to donor shard's secondary."); + +assert.commandFailedWithCode( + runReadCmdWithReadPrefSecondary( + mongos1AlphaDB, collName1, {_id: 1}, {maxTimeMS: 10000, readConcern: {level: "local"}}), + [ErrorCodes.MaxTimeMSExpired, ErrorCodes.StaleConfig]); + +// Allow the migration to commit and pause it before the donor shard's primary refreshes from the +// config server. +jsTest.log("Unpausing migration and enabling hangBeforePostMigrationCommitRefresh failpoint."); +const hangBeforeRefreshFP = configureFailPoint(st.shard0, preCommitRefreshFailPointName); +unpauseMoveChunkAtStep(st.shard0, moveChunkStepNames.chunkDataCommitted); +hangBeforeRefreshFP.wait(); + +// Write to the migrated ns1 chunk. Run flushRouterConfig on the mongos to force it to refresh from +// the config server when it does the write since otherwise it will route the request shard0 which +// will be blocked behind the critical section. This insert will be used to test causal consistency +// in the later read. +jsTest.log("Sending insert through mongos0."); +assert.commandWorked(mongos0AlphaDB.adminCommand({flushRouterConfig: 1})); +assert.commandWorked(mongos0AlphaDB.runCommand( + {insert: collName1, documents: [{_id: 2}], writeConcern: {w: 'majority'}})); + +// Bump the clusterTime of mongos1 to at least equal to the operationTime T for the +// above write by writing to shard1. This is required for the afterClusterTime read +// below to work since the 'afterCluterTime' of a command cannot be larger than the +// current clusterTime of the mongod (i.e. shard0's secondary) that executes the +// command. By bumping the clusterTime of mongos1, the clusterTime of shard0's +// secondary will also get bumped to >= T due to clusterTime gossiping when we do +// the afterClusterTime read. + +jsTest.log("Sending insert to other collection to bump cluster time."); +assert.commandWorked(mongos1BetaDB.runCommand({ + insert: collName2, + documents: [{_id: 1}], + writeConcern: {w: "majority"}, +})); +assert.gt(bsonWoCompare(mongos1AlphaDB.getSession().getOperationTime(), + mongos0AlphaDB.getSession().getOperationTime()), + 0); + +// If the secondary doesn't wait behind the critical section like it should, +// this read will find no matching documents because mongos1 will target +// shard0 instead of shard1 (since mongos1's cache is stale). +jsTest.log("Sending read query to secondary of shard0."); +const totalDocsFound = runReadCmdWithReadPrefSecondary(mongos1AlphaDB, collName1, {_id: 2}, { + readConcern: { + afterClusterTime: mongos0AlphaDB.getSession().getOperationTime(), + }, + }).n; +assert.neq(totalDocsFound, 0); + +jsTest.log("Completing chunk migration."); +hangBeforeRefreshFP.off(); +joinMoveChunk(); + +MongoRunner.stopMongod(staticMongod); +st.stop(); +})(); |