mirror of https://github.com/mongodb/mongo
195 lines
8.1 KiB
JavaScript
195 lines
8.1 KiB
JavaScript
/*
|
|
* Basic tests for moveRange.
|
|
*
|
|
* @tags: [
|
|
* assumes_balancer_off
|
|
* ]
|
|
*/
|
|
import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
import {chunkBoundsUtil} from "jstests/sharding/libs/chunk_bounds_util.js";
|
|
import {findChunksUtil} from "jstests/sharding/libs/find_chunks_util.js";
|
|
|
|
const st = new ShardingTest({mongos: 1, shards: 2, chunkSize: 1});
|
|
const kDbName = "db";
|
|
|
|
const mongos = st.s0;
|
|
const shard0 = st.shard0.shardName;
|
|
const shard1 = st.shard1.shardName;
|
|
|
|
assert.commandWorked(mongos.adminCommand({enableSharding: kDbName, primaryShard: shard0}));
|
|
|
|
function getRandomShardKeyValue(ns, skPattern, filter) {
|
|
const isHashedShardKey = Object.values(skPattern).includes("hashed");
|
|
const coll = mongos.getCollection(ns);
|
|
|
|
// Get a random document from the collection
|
|
let doc = coll.aggregate([{$match: filter}, {$sample: {size: 1}}]).next();
|
|
|
|
// Delete fields not making part of the shard key
|
|
for (let key in doc) {
|
|
if (!(key in skPattern)) {
|
|
delete doc[key];
|
|
}
|
|
}
|
|
|
|
if (isHashedShardKey) {
|
|
doc.a = convertShardKeyToHashed(doc.a);
|
|
}
|
|
|
|
return doc;
|
|
}
|
|
|
|
// Tests for moveRange that will call move&split. We call moveRange with minBound on the positive
|
|
// values and moveRange with maxBound on negative values to ensure that the chunk chosen is big
|
|
// enough to be split by moveRange.
|
|
function testMoveRangeWithBigChunk(mongos, ns, skPattern, minBound) {
|
|
// Get a random existing shard key value, `moveRange` will be called on the owning chunk
|
|
let filter = minBound ? {a: {$gte: 0}} : {a: {$lt: 0}};
|
|
let randomSK = getRandomShardKeyValue(ns, skPattern, filter);
|
|
|
|
// Get bounds and shard of the chunk owning `randomSK`
|
|
const chunksBefore = findChunksUtil.findChunksByNs(mongos.getDB("config"), ns).toArray();
|
|
const shardChunkBounds = chunkBoundsUtil.findShardChunkBounds(chunksBefore);
|
|
const {shard, bounds} = chunkBoundsUtil.findShardAndChunkBoundsForShardKey(st, shardChunkBounds, randomSK);
|
|
|
|
const donor = shard.shardName;
|
|
const recipient = donor == shard0 ? shard1 : shard0;
|
|
|
|
// Count chunks belonging to donor and recipient shards BEFORE moveRange
|
|
const nChunksOnDonorBefore = chunksBefore.filter((chunk) => chunk.shard == donor).length;
|
|
const nChunksOnRecipientBefore = chunksBefore.filter((chunk) => chunk.shard == recipient).length;
|
|
|
|
if (minBound) {
|
|
assert.commandWorked(mongos.adminCommand({moveRange: ns, min: randomSK, toShard: recipient}));
|
|
} else {
|
|
assert.commandWorked(mongos.adminCommand({moveRange: ns, max: randomSK, toShard: recipient}));
|
|
}
|
|
|
|
// Count chunks belonging to donor and recipient shards AFTER moveRange
|
|
const chunksAfter = findChunksUtil.findChunksByNs(mongos.getDB("config"), ns).toArray();
|
|
const nChunksOnDonorAfter = chunksAfter.filter((chunk) => chunk.shard == donor).length;
|
|
const nChunksOnRecipientAfter = chunksAfter.filter((chunk) => chunk.shard == recipient).length;
|
|
|
|
let nExpectedChunksOnRecipientAfter = nChunksOnRecipientBefore + 1;
|
|
// For moveRange with a maxBound, the number of chunks on recipient and donor doesn't change
|
|
// if a shardKey that is a lower-bound of a pre-existing chunk was selected.
|
|
if (!minBound && chunkBoundsUtil.eq(bounds[0], randomSK)) {
|
|
nExpectedChunksOnRecipientAfter = nChunksOnRecipientBefore;
|
|
}
|
|
|
|
assert.eq(
|
|
nExpectedChunksOnRecipientAfter,
|
|
nChunksOnRecipientAfter,
|
|
"The number of chunks on the recipient shard did not increase following a moveRange",
|
|
);
|
|
assert(
|
|
nChunksOnDonorAfter == nChunksOnDonorBefore || nChunksOnDonorAfter == nChunksOnDonorBefore + 1,
|
|
"Unexpected number of chunks on the donor shard after triggering a split + move",
|
|
);
|
|
}
|
|
|
|
function test(collName, skPattern, splitpoint) {
|
|
const ns = kDbName + "." + collName;
|
|
|
|
assert.commandWorked(mongos.adminCommand({shardCollection: ns, key: skPattern}));
|
|
|
|
let aChunk = findChunksUtil.findOneChunkByNs(mongos.getDB("config"), ns, {shard: shard0});
|
|
assert(aChunk);
|
|
|
|
jsTest.log("Testing invalid commands");
|
|
// Fail if one of the bounds is not a valid shard key
|
|
assert.commandFailedWithCode(
|
|
mongos.adminCommand({moveRange: ns, min: aChunk.min, max: {invalidShardKey: 10}, toShard: shard1}),
|
|
ErrorCodes.InvalidOptions,
|
|
);
|
|
|
|
// Fail if the `to` shard does not exists
|
|
assert.commandFailedWithCode(
|
|
mongos.adminCommand({moveRange: ns, min: aChunk.min, max: aChunk.max, toShard: "WrongShard"}),
|
|
ErrorCodes.ShardNotFound,
|
|
);
|
|
|
|
// Test that `moveRange` with min & max bounds works
|
|
jsTest.log("Testing moveRange with both bounds");
|
|
assert.commandWorked(mongos.adminCommand({moveRange: ns, min: aChunk.min, max: aChunk.max, toShard: shard1}));
|
|
|
|
assert.eq(0, mongos.getDB("config").chunks.countDocuments({_id: aChunk._id, shard: shard0}));
|
|
assert.eq(1, mongos.getDB("config").chunks.countDocuments({_id: aChunk._id, shard: shard1}));
|
|
|
|
// Test that `moveRange` only with min bound works (translates to `moveChunk` because chunk too
|
|
// small to be split)
|
|
jsTest.log("Testing moveRange with only min bound");
|
|
assert.commandWorked(mongos.adminCommand({moveRange: ns, min: aChunk.min, toShard: shard0}));
|
|
|
|
assert.eq(1, mongos.getDB("config").chunks.countDocuments({_id: aChunk._id, shard: shard0}));
|
|
assert.eq(0, mongos.getDB("config").chunks.countDocuments({_id: aChunk._id, shard: shard1}));
|
|
|
|
// Test that `moveRange` only with max bound works (translates to `moveChunk` because chunk too
|
|
// small to be split)
|
|
jsTest.log("Testing moveRange with only max bound");
|
|
assert.commandWorked(mongos.adminCommand({moveRange: ns, max: aChunk.max, toShard: shard1}));
|
|
|
|
assert.eq(0, mongos.getDB("config").chunks.countDocuments({_id: aChunk._id, shard: shard0}));
|
|
assert.eq(1, mongos.getDB("config").chunks.countDocuments({_id: aChunk._id, shard: shard1}));
|
|
|
|
// Insert 10MB >0 and <0 in order to create multiple big chunks (chunkSize is set to 1MB)
|
|
jsTest.log("Inserting data to create large chunks");
|
|
const bigString = "X".repeat((1024 * 1024) / 4); // 1 MB
|
|
const coll = mongos.getCollection(ns);
|
|
let bulk = coll.initializeUnorderedBulkOp();
|
|
for (let i = -10; i < 10; i++) {
|
|
bulk.insert({a: i, b: i, str: bigString});
|
|
}
|
|
assert.commandWorked(bulk.execute());
|
|
|
|
// Test moving large chunk with only min bound
|
|
jsTest.log("Testing moveChunk with only min bound and large chunk");
|
|
testMoveRangeWithBigChunk(mongos, ns, skPattern, true /* minBound */);
|
|
|
|
// Test moving large chunk with only max bound
|
|
jsTest.log("Testing moveChunk with only max bound and large chunk");
|
|
testMoveRangeWithBigChunk(mongos, ns, skPattern, false /* maxBound */);
|
|
}
|
|
|
|
// Test running running moveRange on an unsplittable collection will fail
|
|
{
|
|
const collName = "unsplittable_collection";
|
|
const ns = kDbName + "." + collName;
|
|
|
|
jsTest.log("Testing on unsplittable namespace");
|
|
assert.commandWorked(mongos.getDB(kDbName).runCommand({createUnsplittableCollection: collName}));
|
|
assert.commandFailedWithCode(
|
|
mongos.adminCommand({moveRange: ns, min: {_id: 0}, toShard: shard0}),
|
|
ErrorCodes.NamespaceNotSharded,
|
|
);
|
|
}
|
|
|
|
// Test that moveRange should fail if both min and max are provided and they are not covered by a
|
|
// single chunk.
|
|
// TODO (SERVER-97588): Remove version check from tests when 9.0 becomes last LTS.
|
|
if (MongoRunner.compareBinVersions(jsTestOptions().mongosBinVersion, "8.3") >= 0) {
|
|
assert.commandWorked(mongos.adminCommand({shardCollection: kDbName + ".collA", key: {a: 1}}));
|
|
assert.commandWorked(mongos.adminCommand({split: kDbName + ".collA", middle: {a: 0}}));
|
|
|
|
assert.commandFailedWithCode(
|
|
mongos.adminCommand({
|
|
moveRange: kDbName + ".collA",
|
|
min: {a: MinKey()},
|
|
max: {a: MaxKey()},
|
|
toShard: shard1,
|
|
}),
|
|
11089203,
|
|
);
|
|
|
|
mongos.getCollection(kDbName + ".collA").drop();
|
|
}
|
|
|
|
test("nonHashedShardKey", {a: 1});
|
|
|
|
test("nonHashedCompundShardKey", {a: 1, b: 1});
|
|
|
|
test("hashedShardKey", {a: "hashed"});
|
|
|
|
st.stop();
|