mongo/jstests/sharding/query/allow_partial_results_nshar...

150 lines
6.3 KiB
JavaScript

/**
* Confirms that, for a query with 'allowPartialResults' enabled, the 'nShards' log entry reflects
* the number of shards that were actually available during each find or getMore operation.
* @tags: [
* requires_replication,
* requires_sharding,
* ]
*/
import {ShardingTest} from "jstests/libs/shardingtest.js";
// This test looks for exact matches in log output, which does not account for implicit sessions.
TestData.disableImplicitSessions = true;
// Prevent the mongo shell from gossiping its cluster time, since this will increase the amount
// of data logged for each op. For some of the testcases below, including the cluster time would
// cause them to be truncated at the 512-byte RamLog limit, and some of the fields we need to
// check would be lost.
TestData.skipGossipingClusterTime = true;
// Skip the following checks across the cluster at the end, since the test shuts down a shard.
TestData.skipCheckingUUIDsConsistentAcrossCluster = true;
TestData.skipCheckingIndexesConsistentAcrossCluster = true;
TestData.skipCheckShardFilteringMetadata = true;
// Set up a 2-shard single-node replicaset cluster.
const st = new ShardingTest({name: jsTestName(), shards: 2, rs: {nodes: 1}});
// Obtain a connection to the test database on mongoS, and to the test collection.
const mongosDB = st.s.getDB(jsTestName());
const testColl = mongosDB.test;
// Enable sharding on the the test database and ensure that the primary is on shard0.
assert.commandWorked(mongosDB.adminCommand({enableSharding: mongosDB.getName(), primaryShard: st.rs1.getURL()}));
// Shard the collection on _id, split at {_id:0}, and move the upper chunk to the second shard.
st.shardColl(testColl, {_id: 1}, {_id: 0}, {_id: 1}, mongosDB.getName(), true);
// Insert 10 documents on each shard, in the range [-10, 10).
for (let i = -10; i < 10; ++i) {
assert.commandWorked(testColl.insert({_id: i}));
}
// Set the slowms threshold to -1 on mongoS, so that all operations will be logged.
assert.commandWorked(mongosDB.adminCommand({profile: 0, slowms: -1}));
// Helper to find a logline containing all the specified fields. Throws if no such line exists.
function assertMatchingLogLineExists(fields) {
function escapeRegex(input) {
return typeof input === "string" ? input.replace(/[\^\$\\\.\*\+\?\(\)\[\]\{\}]/g, "\\$&") : input;
}
function lineMatches(line, fields) {
const fieldNames = Object.keys(fields);
return fieldNames.every((fieldName) => {
const fieldValue = fields[fieldName];
let regex;
const regexDecimal =
'"' +
escapeRegex(fieldName) +
'":? ?(' +
escapeRegex(checkLog.formatAsJsonLogLine(fieldValue, false, true)) +
"|" +
escapeRegex(checkLog.formatAsJsonLogLine(fieldValue, true, true)) +
")";
const regexNoDecimal =
'"' +
escapeRegex(fieldName) +
'":? ?(' +
escapeRegex(checkLog.formatAsJsonLogLine(fieldValue, false, false)) +
"|" +
escapeRegex(checkLog.formatAsJsonLogLine(fieldValue, true, false)) +
")";
const matchDecimal = line.match(regexDecimal);
const matchNoDecimal = line.match(regexNoDecimal);
return (matchDecimal && matchDecimal[0]) || (matchNoDecimal && matchNoDecimal[0]);
});
}
const globalLog = assert.commandWorked(mongosDB.adminCommand({getLog: "global"}));
assert(
globalLog.log.find((line) => lineMatches(line, fields)),
"failed to find log line ",
);
}
// Issue a query with {allowPartialResults:true} on the collection. We sort by {_id:1} so that all
// results from shard0 will be returned before any from shard1. We also set a small batchSize so
// that not all results are returned at once.
const findCmd = {
find: testColl.getName(),
filter: {},
sort: {_id: 1},
allowPartialResults: true,
comment: "allow_partial_results_find_nshards_2",
batchSize: 2,
};
let findRes = assert.commandWorked(mongosDB.runCommand(findCmd));
// Confirm that the cursor did not report partial results, and that the command logs {nShards:2}.
assertMatchingLogLineExists(Object.assign({nShards: 2}, findCmd));
assert.eq(findRes.cursor.partialResultsReturned, undefined);
// Issue a getMore with the same batchSize...
const getMoreCmd = {
getMore: findRes.cursor.id,
collection: testColl.getName(),
comment: "allow_partial_results_getmore_nshards_2",
batchSize: 2,
};
let getMoreRes = assert.commandWorked(mongosDB.runCommand(getMoreCmd));
// ... and confirm that nShards is still 2.
assertMatchingLogLineExists(Object.assign({nShards: 2}, getMoreCmd));
assert.eq(getMoreRes.cursor.partialResultsReturned, undefined);
// Now stop shard0.
st.rs1.stopSet();
// Issue another getMore with a higher batchSize.
getMoreCmd.comment = "allow_partial_results_getmore_nshards_2_again";
getMoreCmd.batchSize = 4;
getMoreRes = assert.commandWorked(mongosDB.runCommand(getMoreCmd));
// We record the number of shards at the outset of the getMore, so this should again result in a
// logline with nShards:2. But the larger batchSize burns through all results that mongoS buffered
// from shard0 before it went down, and when we attempt to acquire a new batch from the shard we
// discover that it is no longer available. We therefore return {partialResultsReturned:true}.
assertMatchingLogLineExists(Object.assign({nShards: 2}, getMoreCmd));
assert.eq(getMoreRes.cursor.partialResultsReturned, true);
// When we issue the next getMore, shard0 has already been marked as unavailable...
getMoreCmd.comment = "allow_partial_results_getmore_nshards_1";
getMoreRes = assert.commandWorked(mongosDB.runCommand(getMoreCmd));
// ... so nShards is now 1. The 'partialResultsReturned' cursor field remains true.
assertMatchingLogLineExists(Object.assign({nShards: 1}, getMoreCmd));
assert.eq(getMoreRes.cursor.partialResultsReturned, true);
// Finally, issue the original 'find' command again...
findCmd.comment = "allow_partial_results_find_nshards_1";
findRes = assert.commandWorked(mongosDB.runCommand(findCmd));
// ... and confirm that nShards is 1 now that one shard is down.
assertMatchingLogLineExists(Object.assign({nShards: 1}, findCmd));
assert.eq(findRes.cursor.partialResultsReturned, true);
st.stop();