mirror of https://github.com/mongodb/mongo
267 lines
10 KiB
JavaScript
267 lines
10 KiB
JavaScript
/**
|
|
* Test that chunks and documents are moved correctly after zone changes.
|
|
*/
|
|
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";
|
|
import {
|
|
assertChunksOnShards,
|
|
assertDocsOnShards,
|
|
assertShardTags,
|
|
moveZoneToShard,
|
|
runBalancer,
|
|
} from "jstests/sharding/libs/zone_changes_util.js";
|
|
|
|
/**
|
|
* Adds each shard to the corresponding zone in zoneTags, and makes the zone range equal
|
|
* to the chunk range of the shard. Assumes that there are no chunk holes on each shard.
|
|
*/
|
|
function addShardsToZonesAndAssignZoneRanges(st, ns, shardChunkBounds, shardTags) {
|
|
let zoneChunks = {};
|
|
for (let [shardName, chunkBounds] of Object.entries(shardChunkBounds)) {
|
|
let zoneName = shardTags[shardName][0];
|
|
let rangeMin = {x: MaxKey};
|
|
let rangeMax = {x: MinKey};
|
|
for (let bounds of chunkBounds) {
|
|
if (chunkBoundsUtil.lt(bounds[0], rangeMin)) {
|
|
rangeMin = bounds[0];
|
|
}
|
|
if (chunkBoundsUtil.gte(bounds[1], rangeMax)) {
|
|
rangeMax = bounds[1];
|
|
}
|
|
}
|
|
zoneChunks[zoneName] = chunkBounds;
|
|
assert.commandWorked(st.s.adminCommand({addShardToZone: shardName, zone: zoneName}));
|
|
assert.commandWorked(st.s.adminCommand({updateZoneKeyRange: ns, min: rangeMin, max: rangeMax, zone: zoneName}));
|
|
}
|
|
return zoneChunks;
|
|
}
|
|
|
|
/**
|
|
* Returns the highest chunk bounds out of the given chunk bounds. Assumes that the
|
|
* chunks do not overlap.
|
|
*/
|
|
function findHighestChunkBounds(chunkBounds) {
|
|
let highestBounds = chunkBounds[0];
|
|
for (let i = 1; i < chunkBounds.length; i++) {
|
|
if (chunkBoundsUtil.lt(highestBounds, chunkBounds[i])) {
|
|
highestBounds = chunkBounds[i];
|
|
}
|
|
}
|
|
return highestBounds;
|
|
}
|
|
|
|
const st = new ShardingTest({shards: 3, other: {chunkSize: 1}});
|
|
let primaryShard = st.shard0;
|
|
let dbName = "test";
|
|
let testDB = st.s.getDB(dbName);
|
|
let configDB = st.s.getDB("config");
|
|
let coll = testDB.hashed;
|
|
let ns = coll.getFullName();
|
|
let shardKey = {x: "hashed"};
|
|
|
|
assert.commandWorked(st.s.adminCommand({enableSharding: dbName, primaryShard: primaryShard.shardName}));
|
|
|
|
jsTest.log("Shard the collection. The command creates one chunk on each of the shards by default.");
|
|
assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: shardKey}));
|
|
|
|
jsTest.log("Insert docs (one per chunk) and check that they end up on the right shards.");
|
|
const bigString = "X".repeat(1024 * 1024); // 1MB
|
|
let docs = [
|
|
{x: -25, s: bigString},
|
|
{x: -18, s: bigString},
|
|
{x: -5, s: bigString},
|
|
{x: -1, s: bigString},
|
|
{x: 5, s: bigString},
|
|
{x: 10, s: bigString},
|
|
];
|
|
|
|
// Make sure that there is one chunk dedicated for each inserted document
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: convertShardKeyToHashed(docs[1].x)}}));
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: convertShardKeyToHashed(docs[3].x)}}));
|
|
assert.commandWorked(st.s.adminCommand({split: ns, middle: {x: convertShardKeyToHashed(docs[5].x)}}));
|
|
|
|
assert.commandWorked(coll.insert(docs));
|
|
|
|
let chunkDocs = findChunksUtil.findChunksByNs(configDB, ns).sort({min: 1}).toArray();
|
|
let shardChunkBounds = chunkBoundsUtil.findShardChunkBounds(chunkDocs);
|
|
|
|
let docChunkBounds = [];
|
|
let minHash = MaxKey;
|
|
docs.forEach(function (doc) {
|
|
let hash = convertShardKeyToHashed(doc.x);
|
|
let {shard, bounds} = chunkBoundsUtil.findShardAndChunkBoundsForShardKey(st, shardChunkBounds, {x: hash});
|
|
assert.eq(1, shard.getCollection(ns).count(doc));
|
|
docChunkBounds.push(bounds);
|
|
if (bsonWoCompare(hash, minHash) < 0) {
|
|
minHash = hash;
|
|
}
|
|
});
|
|
assert.eq(docs.length, new Set(docChunkBounds).size);
|
|
assert.eq(docs.length, findChunksUtil.countChunksForNs(configDB, ns));
|
|
|
|
jsTest.log(
|
|
"Assign each shard a zone, make each zone range equal to the chunk range for the shard, " +
|
|
"and store the chunks for each zone.",
|
|
);
|
|
let shardTags = {
|
|
[st.shard0.shardName]: ["zoneA"],
|
|
[st.shard1.shardName]: ["zoneB"],
|
|
[st.shard2.shardName]: ["zoneC"],
|
|
};
|
|
let zoneChunkBounds = addShardsToZonesAndAssignZoneRanges(st, ns, shardChunkBounds, shardTags);
|
|
assertShardTags(configDB, shardTags);
|
|
|
|
jsTest.log("Test shard's zone changes...");
|
|
|
|
jsTest.log(
|
|
"Check that removing a zone from a shard causes its chunks and documents to move to other" +
|
|
" shards that the zone belongs to.",
|
|
);
|
|
moveZoneToShard(st, "zoneA", st.shard0, st.shard1);
|
|
shardTags = {
|
|
[st.shard0.shardName]: [],
|
|
[st.shard1.shardName]: ["zoneB", "zoneA"],
|
|
[st.shard2.shardName]: ["zoneC"],
|
|
};
|
|
assertShardTags(configDB, shardTags);
|
|
|
|
runBalancer(st, zoneChunkBounds["zoneA"].length);
|
|
shardChunkBounds = {
|
|
[st.shard0.shardName]: [],
|
|
[st.shard1.shardName]: [...zoneChunkBounds["zoneB"], ...zoneChunkBounds["zoneA"]],
|
|
[st.shard2.shardName]: zoneChunkBounds["zoneC"],
|
|
};
|
|
assertChunksOnShards(configDB, ns, shardChunkBounds);
|
|
assertDocsOnShards(st, ns, shardChunkBounds, docs, shardKey);
|
|
|
|
jsTest.log("Check that the balancer balances chunks within zones.");
|
|
assert.commandWorked(st.s.adminCommand({addShardToZone: st.shard0.shardName, zone: "zoneB"}));
|
|
shardTags = {
|
|
[st.shard0.shardName]: ["zoneB"],
|
|
[st.shard1.shardName]: ["zoneB", "zoneA"],
|
|
[st.shard2.shardName]: ["zoneC"],
|
|
};
|
|
assertShardTags(configDB, shardTags);
|
|
|
|
const numChunksToMove = zoneChunkBounds["zoneB"].length - 1;
|
|
runBalancer(st, numChunksToMove);
|
|
shardChunkBounds = {
|
|
[st.shard0.shardName]: zoneChunkBounds["zoneB"].slice(0, numChunksToMove),
|
|
[st.shard1.shardName]: [
|
|
...zoneChunkBounds["zoneA"],
|
|
...zoneChunkBounds["zoneB"].slice(numChunksToMove, zoneChunkBounds["zoneB"].length),
|
|
],
|
|
[st.shard2.shardName]: zoneChunkBounds["zoneC"],
|
|
};
|
|
assertChunksOnShards(configDB, ns, shardChunkBounds);
|
|
assertDocsOnShards(st, ns, shardChunkBounds, docs, shardKey);
|
|
|
|
jsTest.log("Make another zone change, and check that the chunks and docs are on the right shards.");
|
|
assert.commandWorked(st.s.adminCommand({removeShardFromZone: st.shard0.shardName, zone: "zoneB"}));
|
|
moveZoneToShard(st, "zoneC", st.shard2, st.shard0);
|
|
moveZoneToShard(st, "zoneA", st.shard1, st.shard2);
|
|
shardTags = {
|
|
[st.shard0.shardName]: ["zoneC"],
|
|
[st.shard1.shardName]: ["zoneB"],
|
|
[st.shard2.shardName]: ["zoneA"],
|
|
};
|
|
assertShardTags(configDB, shardTags);
|
|
|
|
runBalancer(st, numChunksToMove + zoneChunkBounds["zoneA"].length + zoneChunkBounds["zoneC"].length);
|
|
shardChunkBounds = {
|
|
[st.shard0.shardName]: zoneChunkBounds["zoneC"],
|
|
[st.shard1.shardName]: zoneChunkBounds["zoneB"],
|
|
[st.shard2.shardName]: zoneChunkBounds["zoneA"],
|
|
};
|
|
assertChunksOnShards(configDB, ns, shardChunkBounds);
|
|
assertDocsOnShards(st, ns, shardChunkBounds, docs, shardKey);
|
|
|
|
jsTest.log("Test chunk's zone changes...");
|
|
|
|
// Find the chunk with the highest bounds in zoneA.
|
|
let originalZoneARange = chunkBoundsUtil.computeRange(zoneChunkBounds["zoneA"]);
|
|
let targetChunkBounds = findHighestChunkBounds(zoneChunkBounds["zoneA"]);
|
|
assert(chunkBoundsUtil.containsKey(targetChunkBounds[0], ...originalZoneARange));
|
|
assert(chunkBoundsUtil.eq(targetChunkBounds[1], originalZoneARange[1]));
|
|
let remainingZoneAChunkBounds = zoneChunkBounds["zoneA"].filter(
|
|
(chunkBounds) => !chunkBoundsUtil.eq(targetChunkBounds, chunkBounds),
|
|
);
|
|
|
|
jsTest.log("Change the zone ranges so that the chunk that used to belong to zoneA now belongs to zoneB.");
|
|
assert.commandWorked(
|
|
st.s.adminCommand({updateZoneKeyRange: ns, min: originalZoneARange[0], max: originalZoneARange[1], zone: null}),
|
|
);
|
|
assert.commandWorked(
|
|
st.s.adminCommand({
|
|
updateZoneKeyRange: ns,
|
|
min: originalZoneARange[0],
|
|
max: targetChunkBounds[0],
|
|
zone: "zoneA",
|
|
}),
|
|
);
|
|
assert.commandWorked(
|
|
st.s.adminCommand({
|
|
updateZoneKeyRange: ns,
|
|
min: targetChunkBounds[0],
|
|
max: originalZoneARange[1],
|
|
zone: "zoneB",
|
|
}),
|
|
);
|
|
|
|
jsTest.log("Check that the chunk moves from zoneA to zoneB after the zone range change.");
|
|
runBalancer(st, 1);
|
|
shardChunkBounds = {
|
|
[st.shard0.shardName]: zoneChunkBounds["zoneC"],
|
|
[st.shard1.shardName]: [targetChunkBounds, ...zoneChunkBounds["zoneB"]],
|
|
[st.shard2.shardName]: remainingZoneAChunkBounds,
|
|
};
|
|
assertChunksOnShards(configDB, ns, shardChunkBounds);
|
|
assertDocsOnShards(st, ns, shardChunkBounds, docs, shardKey);
|
|
|
|
jsTest.log("Change the zone ranges so that the chunk that used to belong to zoneB now belongs to zoneC.");
|
|
assert.commandWorked(
|
|
st.s.adminCommand({updateZoneKeyRange: ns, min: targetChunkBounds[0], max: targetChunkBounds[1], zone: null}),
|
|
);
|
|
assert.commandWorked(
|
|
st.s.adminCommand({updateZoneKeyRange: ns, min: targetChunkBounds[0], max: targetChunkBounds[1], zone: "zoneC"}),
|
|
);
|
|
|
|
jsTest.log("Check that the chunk moves from zoneB to zoneC after the zone range change.");
|
|
runBalancer(st, 1);
|
|
shardChunkBounds = {
|
|
[st.shard0.shardName]: [targetChunkBounds, ...zoneChunkBounds["zoneC"]],
|
|
[st.shard1.shardName]: zoneChunkBounds["zoneB"],
|
|
[st.shard2.shardName]: remainingZoneAChunkBounds,
|
|
};
|
|
assertChunksOnShards(configDB, ns, shardChunkBounds);
|
|
assertDocsOnShards(st, ns, shardChunkBounds, docs, shardKey);
|
|
|
|
jsTest.log("Make the chunk not aligned with zone ranges.");
|
|
let splitPoint =
|
|
targetChunkBounds[1].x === MaxKey
|
|
? {x: NumberLong(targetChunkBounds[0].x + 5000)}
|
|
: {x: NumberLong(targetChunkBounds[1].x - 5000)};
|
|
assert(chunkBoundsUtil.containsKey(splitPoint, ...targetChunkBounds));
|
|
assert.commandWorked(
|
|
st.s.adminCommand({updateZoneKeyRange: ns, min: targetChunkBounds[0], max: targetChunkBounds[1], zone: null}),
|
|
);
|
|
assert.commandWorked(
|
|
st.s.adminCommand({updateZoneKeyRange: ns, min: targetChunkBounds[0], max: splitPoint, zone: "zoneC"}),
|
|
);
|
|
assert.commandWorked(
|
|
st.s.adminCommand({updateZoneKeyRange: ns, min: splitPoint, max: targetChunkBounds[1], zone: "zoneA"}),
|
|
);
|
|
|
|
jsTest.log("Check that the balancer splits the chunk and that all chunks and docs are on the right shards.");
|
|
runBalancer(st, 1);
|
|
shardChunkBounds = {
|
|
[st.shard0.shardName]: [[targetChunkBounds[0], splitPoint], ...zoneChunkBounds["zoneC"]],
|
|
[st.shard1.shardName]: zoneChunkBounds["zoneB"],
|
|
[st.shard2.shardName]: [[splitPoint, targetChunkBounds[1]], ...remainingZoneAChunkBounds],
|
|
};
|
|
assertChunksOnShards(configDB, ns, shardChunkBounds);
|
|
assertDocsOnShards(st, ns, shardChunkBounds, docs, shardKey);
|
|
|
|
st.stop();
|