mirror of https://github.com/mongodb/mongo
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:
parent
1f686facd7
commit
86e3331617
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue