SERVER-110892 ConfigSvrMoveRange doesn't pick a new chunk after StaleConfig (#41391)

Co-authored-by: Enrico Golfieri <enrico.golfieri@mongodb.com>
GitOrigin-RevId: f760bd4e544bd20980f2320835f8aa89eec03c93
This commit is contained in:
Silvia Surroca 2025-09-22 10:11:14 +02:00 committed by MongoDB Bot
parent 1f686facd7
commit 86e3331617
6 changed files with 165 additions and 88 deletions

View File

@ -96,6 +96,9 @@ export const $config = extendWorkload($partialConfig, function ($config, $super)
errorMsg.includes("Location51008") ||
errorMsg.includes("Location6718402") ||
errorMsg.includes("Location16977") ||
// This error can occur when a different migration commits and splits the chunk
// being moved by the current migration.
errorMsg.includes("Location11089203") ||
// When running with the balancer, manual chunk migrations might conflict with the
// balancer issued splits.
(TestData.runningWithBalancer && err.code == 656452)

View File

@ -553,11 +553,13 @@ let viewsCommandTests = {
},
modifySearchIndex: {skip: "present in v6.3 but renamed to updateSearchIndex in v7.0"},
moveChunk: {
command: {moveChunk: "test.view", find: {}, to: "a"},
command: function (conn) {
const shardName = conn.adminCommand({listShards: 1}).shards[0]._id;
const cmd = {moveChunk: "test.view", find: {}, to: shardName};
assert.commandFailedWithCode(conn.adminCommand(cmd), ErrorCodes.NamespaceNotSharded);
},
skipStandalone: true,
isAdminCommand: true,
expectFailure: true,
expectedErrorCode: ErrorCodes.NamespaceNotSharded,
},
moveCollection: {
command: {moveCollection: "test.view", toShard: "move_collection-rs"},

View File

@ -283,9 +283,10 @@ if (runningOnMongos) {
assertFailsWithInvalidNamespacesForField("split", {split: "", find: {}}, isFullyQualified, isAdminCommand);
// Test moveChunk fails with an invalid collection name.
const shardName = db.adminCommand({listShards: 1}).shards[0]._id;
assertFailsWithInvalidNamespacesForField(
"moveChunk",
{moveChunk: "", find: {}, to: "commands_namespace_parsing_out"},
{moveChunk: "", find: {}, to: shardName},
isNotFullyQualified,
isAdminCommand,
);

View File

@ -89,21 +89,26 @@ function testMoveRangeWithBigChunk(mongos, ns, skPattern, minBound) {
);
}
function test(collName, skPattern) {
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.commandFailed(
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.commandFailed(mongos.adminCommand({moveRange: ns, min: aChunk.min, max: aChunk.max, toShard: "WrongShard"}));
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");
@ -148,15 +153,37 @@ function test(collName, skPattern) {
}
// Test running running moveRange on an unsplittable collection will fail
const collName = "unsplittable_collection";
const ns = kDbName + "." + collName;
{
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,
);
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});

View File

@ -782,6 +782,20 @@ void Balancer::moveRange(OperationContext* opCtx,
<< cm.getShardKeyPattern().toBSON(),
!maxBound.has_value() || cm.getShardKeyPattern().isShardKey(*maxBound));
// If both min and max are provided, check that the given range is covered by a single
// chunk.
if (minBound && maxBound) {
const auto chunkRange =
ChunkRange(cm.getShardKeyPattern().normalizeShardKey(*minBound),
cm.getShardKeyPattern().normalizeShardKey(*maxBound));
const auto& chunkWithMin = cm.findIntersectingChunkWithSimpleCollation(*minBound);
uassert(11089203,
str::stream() << "Range with bounds " << chunkRange.toString()
<< " is not contained within a single chunk.",
chunkWithMin.getRange().covers(chunkRange));
}
// Get the donor shard.
const auto fromShardId = [&]() {
if (minBound.has_value()) {

View File

@ -53,6 +53,7 @@
#include "mongo/db/sharding_environment/client/shard.h"
#include "mongo/db/sharding_environment/cluster_commands_gen.h"
#include "mongo/db/sharding_environment/grid.h"
#include "mongo/db/sharding_environment/mongod_and_mongos_server_parameters_gen.h"
#include "mongo/db/topology/shard_registry.h"
#include "mongo/db/versioning_protocol/shard_version.h"
#include "mongo/logv2/log.h"
@ -128,13 +129,6 @@ public:
Timer t;
const auto chunkManager =
getRefreshedCollectionRoutingInfoAssertSharded_DEPRECATED(opCtx, ns())
.getChunkManager();
uassert(ErrorCodes::NamespaceNotSharded,
str::stream() << "Can't execute " << Request::kCommandName
<< " on unsharded collection " << ns().toStringForErrorMsg(),
chunkManager.isSharded());
uassert(ErrorCodes::InvalidOptions,
"bounds can only have exactly 2 elements",
@ -163,86 +157,122 @@ public:
const auto to = toStatus.getValue();
auto find = request().getFind();
auto bounds = request().getBounds();
const auto find = request().getFind();
const auto bounds = request().getBounds();
auto runMoveRange = [&](const Chunk& chunk) {
MoveRangeRequestBase moveRangeReq;
moveRangeReq.setToShard(to->getId());
moveRangeReq.setMin(chunk.getMin());
moveRangeReq.setMax(chunk.getMax());
moveRangeReq.setWaitForDelete(request().getWaitForDelete().value_or(false) ||
request().get_waitForDelete().value_or(false));
boost::optional<Chunk> chunk;
ConfigsvrMoveRange configsvrRequest(ns());
configsvrRequest.setDbName(DatabaseName::kAdmin);
configsvrRequest.setMoveRangeRequestBase(moveRangeReq);
const auto secondaryThrottle = uassertStatusOK(
MigrationSecondaryThrottleOptions::createFromCommand(request().toBSON()));
configsvrRequest.setSecondaryThrottle(secondaryThrottle);
configsvrRequest.setForceJumbo(request().getForceJumbo() ? ForceJumbo::kForceManual
: ForceJumbo::kDoNotForce);
generic_argument_util::setMajorityWriteConcern(configsvrRequest);
auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard();
auto commandResponse = configShard->runCommandWithIndefiniteRetries(
opCtx,
ReadPreferenceSetting{ReadPreference::PrimaryOnly},
DatabaseName::kAdmin,
configsvrRequest.toBSON(),
Shard::RetryPolicy::kIdempotent);
uassertStatusOK(
Shard::CommandResponse::getEffectiveStatus(std::move(commandResponse)));
Grid::get(opCtx)->catalogCache()->onStaleCollectionVersion(ns(), boost::none);
BSONObjBuilder resultbson;
resultbson.append("millis", t.millis());
result->getBodyBuilder().appendElements(resultbson.obj());
};
if (find) {
// find
BSONObj shardKey = uassertStatusOK(extractShardKeyFromBasicQuery(
opCtx, ns(), chunkManager.getShardKeyPattern(), *find));
const auto maxNumAttempts = gMaxNumStaleVersionRetries.load();
auto numAttempts = 0;
while (numAttempts < maxNumAttempts) {
const auto chunkManager =
getRefreshedCollectionRoutingInfoAssertSharded_DEPRECATED(opCtx, ns())
.getChunkManager();
uassert(ErrorCodes::NamespaceNotSharded,
str::stream()
<< "Can't execute " << Request::kCommandName
<< " on unsharded collection " << ns().toStringForErrorMsg(),
chunkManager.isSharded());
uassert(656450,
str::stream() << "no shard key found in chunk query " << *find,
!shardKey.isEmpty());
BSONObj shardKey = uassertStatusOK(extractShardKeyFromBasicQuery(
opCtx, ns(), chunkManager.getShardKeyPattern(), *find));
if (find && chunkManager.getShardKeyPattern().isHashedPattern()) {
LOGV2_WARNING(7065400,
"bounds should be used instead of query for hashed shard keys");
uassert(656450,
str::stream() << "no shard key found in chunk query " << *find,
!shardKey.isEmpty());
if (find && chunkManager.getShardKeyPattern().isHashedPattern()) {
LOGV2_WARNING(
7065400,
"bounds should be used instead of query for hashed shard keys");
}
const auto chunk =
chunkManager.findIntersectingChunkWithSimpleCollation(shardKey);
try {
runMoveRange(chunk);
} catch (const DBException& e) {
if (e.code() == 11089203) {
// We should retry the operation if the computed min and max don't match
// a specific chunk.
numAttempts++;
continue;
}
throw;
}
break;
}
chunk.emplace(chunkManager.findIntersectingChunkWithSimpleCollation(shardKey));
} else {
auto minBound = bounds->front();
auto maxBound = bounds->back();
uassert(656451,
str::stream() << "shard key bounds "
<< "[" << minBound << "," << maxBound << ")"
<< " are not valid for shard key pattern "
<< chunkManager.getShardKeyPattern().toBSON(),
chunkManager.getShardKeyPattern().isShardKey(minBound) &&
chunkManager.getShardKeyPattern().isShardKey(maxBound));
BSONObj minKey = chunkManager.getShardKeyPattern().normalizeShardKey(minBound);
BSONObj maxKey = chunkManager.getShardKeyPattern().normalizeShardKey(maxBound);
chunk.emplace(chunkManager.findIntersectingChunkWithSimpleCollation(minKey));
uassert(656452,
str::stream() << "no chunk found with the shard key bounds "
<< "[" << minKey << "," << maxKey << ")",
chunk->getMin().woCompare(minKey) == 0 &&
chunk->getMax().woCompare(maxKey) == 0);
return;
}
const auto chunkManager =
getRefreshedCollectionRoutingInfoAssertSharded_DEPRECATED(opCtx, ns())
.getChunkManager();
uassert(ErrorCodes::NamespaceNotSharded,
str::stream() << "Can't execute " << Request::kCommandName
<< " on unsharded collection " << ns().toStringForErrorMsg(),
chunkManager.isSharded());
MoveRangeRequestBase moveRangeReq;
moveRangeReq.setToShard(to->getId());
moveRangeReq.setMin(chunk->getMin());
moveRangeReq.setMax(chunk->getMax());
moveRangeReq.setWaitForDelete(request().getWaitForDelete().value_or(false) ||
request().get_waitForDelete().value_or(false));
auto minBound = bounds->front();
auto maxBound = bounds->back();
uassert(656451,
str::stream() << "shard key bounds "
<< "[" << minBound << "," << maxBound << ")"
<< " are not valid for shard key pattern "
<< chunkManager.getShardKeyPattern().toBSON(),
chunkManager.getShardKeyPattern().isShardKey(minBound) &&
chunkManager.getShardKeyPattern().isShardKey(maxBound));
BSONObj minKey = chunkManager.getShardKeyPattern().normalizeShardKey(minBound);
BSONObj maxKey = chunkManager.getShardKeyPattern().normalizeShardKey(maxBound);
ConfigsvrMoveRange configsvrRequest(ns());
configsvrRequest.setDbName(DatabaseName::kAdmin);
configsvrRequest.setMoveRangeRequestBase(moveRangeReq);
const auto chunk = chunkManager.findIntersectingChunkWithSimpleCollation(minKey);
uassert(656452,
str::stream() << "no chunk found with the shard key bounds "
<< "[" << minKey << "," << maxKey << ")",
chunk.getMin().woCompare(minKey) == 0 && chunk.getMax().woCompare(maxKey) == 0);
const auto secondaryThrottle = uassertStatusOK(
MigrationSecondaryThrottleOptions::createFromCommand(request().toBSON()));
configsvrRequest.setSecondaryThrottle(secondaryThrottle);
configsvrRequest.setForceJumbo(request().getForceJumbo() ? ForceJumbo::kForceManual
: ForceJumbo::kDoNotForce);
generic_argument_util::setMajorityWriteConcern(configsvrRequest);
auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard();
auto commandResponse = configShard->runCommandWithIndefiniteRetries(
opCtx,
ReadPreferenceSetting{ReadPreference::PrimaryOnly},
DatabaseName::kAdmin,
configsvrRequest.toBSON(),
Shard::RetryPolicy::kIdempotent);
uassertStatusOK(Shard::CommandResponse::getEffectiveStatus(std::move(commandResponse)));
Grid::get(opCtx)->catalogCache()->onStaleCollectionVersion(ns(), boost::none);
BSONObjBuilder resultbson;
resultbson.append("millis", t.millis());
result->getBodyBuilder().appendElements(resultbson.obj());
runMoveRange(chunk);
}
};
};