SERVER-125661 Stop bumping the placement version on clearJumboFlag (#52847)

GitOrigin-RevId: 3d1f199270f709bfab13249a783ddd4d7783e33c
This commit is contained in:
Silvia Surroca 2026-05-04 10:05:07 +02:00 committed by MongoDB Bot
parent df7237aaec
commit 4cb4a16096
6 changed files with 65 additions and 73 deletions

View File

@ -45,6 +45,8 @@ last-continuous:
ticket: SERVER-121822
- test_file: jstests/core/index/index_on_incorrect_collection.js
ticket: SERVER-121132
- test_file: jstests/noPassthrough/ddl/clear_jumbo.js
ticket: SERVER-125661
- test_file: jstests/core/ddl/coll_mod_cappedsize_on_view.js
ticket: SERVER-124967
suites: null
@ -730,6 +732,8 @@ last-lts:
ticket: SERVER-123567
- test_file: jstests/core/clustered/clustered_collection_bounded_scan.js
ticket: SERVER-121822
- test_file: jstests/noPassthrough/ddl/clear_jumbo.js
ticket: SERVER-125661
- test_file: jstests/core/ddl/coll_mod_cappedsize_on_view.js
ticket: SERVER-124967
suites: null

View File

@ -1,6 +1,6 @@
// requires_fcv_61 since the balancer in v6.0 is still working on the number of chunks,
// hence the balancer is not triggered and the chunk is not marked as jumbo
// @tags: [requires_fcv_61]
/**
* Tests that the clearJumboFlag command works correctly.
*/
// Cannot run the filtering metadata check on tests that run clearJumboFlag.
TestData.skipCheckShardFilteringMetadata = true;
@ -68,11 +68,11 @@ jumboChunk = findChunksUtil.findOneChunkByNs(configDB, testNs, {min: {x: 0}});
assert(jumboChunk.jumbo, tojson(jumboChunk));
assert.eq(jumboMajorVersionBefore, jumboChunk.lastmod.getTime());
// Target real jumbo chunk should bump version.
// Target real jumbo chunk should clear the flag without bumping the placement version.
assert.commandWorked(adminDB.runCommand({clearJumboFlag: testNs, find: {x: 1}}));
jumboChunk = findChunksUtil.findOneChunkByNs(configDB, testNs, {min: {x: 0}});
assert(!jumboChunk.jumbo, tojson(jumboChunk));
assert.lt(jumboMajorVersionBefore, jumboChunk.lastmod.getTime());
assert.eq(jumboMajorVersionBefore, jumboChunk.lastmod.getTime());
// Delete all documents
assert.commandWorked(testColl.deleteMany({x: 0}));
@ -98,11 +98,11 @@ jumboChunk = findChunksUtil.findOneChunkByNs(configDB, testNs, {min: {x: 0}});
assert(jumboChunk.jumbo, tojson(jumboChunk));
assert.eq(jumboMajorVersionBefore, jumboChunk.lastmod.getTime());
// Target real jumbo chunk should bump version.
// Target real jumbo chunk should clear the flag without bumping the placement version.
assert.commandWorked(adminDB.runCommand({clearJumboFlag: testNs, bounds: [jumboChunk.min, jumboChunk.max]}));
jumboChunk = findChunksUtil.findOneChunkByNs(configDB, testNs, {min: {x: 0}});
assert(!jumboChunk.jumbo, tojson(jumboChunk));
assert.lt(jumboMajorVersionBefore, jumboChunk.lastmod.getTime());
assert.eq(jumboMajorVersionBefore, jumboChunk.lastmod.getTime());
// Ensure clear jumbo flag stores the correct chunk version
assert.eq(undefined, jumboChunk.lastmodEpoch);

View File

@ -138,8 +138,8 @@ BSONObj ChunkInfo::toBSON() const {
return bob.obj();
}
void ChunkInfo::markAsJumbo() {
_jumbo.store(true);
void ChunkInfo::setJumbo(bool jumbo) {
_jumbo.store(jumbo);
}
void Chunk::throwIfMoved() const {

View File

@ -128,10 +128,7 @@ public:
// computed by, say, hashing a given field or projecting to a subset of fields).
bool containsKey(const BSONObj& shardKey) const;
/**
* Marks this chunk as jumbo. Only moves from false to true once and is used by the balancer.
*/
void markAsJumbo();
void setJumbo(bool jumbo);
private:
// IMPORTANT: The order of the members here matters,
@ -212,11 +209,8 @@ public:
return _chunkInfo.containsKey(shardKey);
}
/**
* Marks this chunk as jumbo. Only moves from false to true once and is used by the balancer.
*/
void markAsJumbo() {
_chunkInfo.markAsJumbo();
void setJumbo(bool jumbo) {
_chunkInfo.setJumbo(jumbo);
}
private:

View File

@ -1950,8 +1950,10 @@ void ShardingCatalogManager::clearJumboFlag(OperationContext* opCtx,
// under the exclusive _kChunkOpLock happen on the same term.
opCtx->setAlwaysInterruptAtStepDownOrUp_UNSAFE();
// Take _kChunkOpLock in exclusive mode to prevent concurrent chunk modifications and generate
// strictly monotonously increasing collection placement versions
auto cm = uassertStatusOK(
RoutingInformationCache::get(opCtx)->getCollectionPlacementInfoWithRefresh(opCtx, nss));
// Take _kChunkOpLock in exclusive mode to serialise with concurrent chunk modifications.
Lock::ExclusiveLock lk(opCtx, _kChunkOpLock);
auto findCollResponse = uassertStatusOK(_localConfigShard->exhaustiveFindOnConfig(
@ -1968,6 +1970,18 @@ void ShardingCatalogManager::clearJumboFlag(OperationContext* opCtx,
!findCollResponse.docs.empty());
const CollectionType coll(findCollResponse.docs[0]);
// Check that current collection epoch and timestamp still matches the one sent by the shard.
// This is to spot scenarios in which the collection have been dropped and recreated or had its
// shard key refined since the migration began.
uassert(StaleEpochInfo(nss),
str::stream() << "The epoch of collection '" << nss.toStringForErrorMsg()
<< "' has changed. The config server's collection placement version "
"epoch is now '"
<< coll.getEpoch().toString() << "', but the request's is "
<< collectionEpoch.toString() << "'. Aborting clear jumbo on chunk ("
<< chunk.toString() << ").",
coll.getEpoch() == collectionEpoch);
BSONObj targetChunkQuery =
BSON(ChunkType::min(chunk.getMin())
<< ChunkType::max(chunk.getMax()) << ChunkType::collectionUUID << coll.getUuid());
@ -1994,63 +2008,33 @@ void ShardingCatalogManager::clearJumboFlag(OperationContext* opCtx,
return;
}
const auto allChunksQuery = BSON(ChunkType::collectionUUID << coll.getUuid());
// Must use local read concern because we will perform subsequent writes.
auto findResponse = uassertStatusOK(_localConfigShard->exhaustiveFindOnConfig(
opCtx,
ReadPreferenceSetting{ReadPreference::PrimaryOnly},
repl::ReadConcernLevel::kLocalReadConcern,
NamespaceString::kConfigsvrChunksNamespace,
allChunksQuery,
BSON(ChunkType::lastmod << -1),
1));
const auto chunksVector = std::move(findResponse.docs);
uassert(ErrorCodes::IncompatibleShardingMetadata,
str::stream() << "Tried to find max chunk version for collection '"
<< nss.toStringForErrorMsg() << ", but found no chunks",
!chunksVector.empty());
const auto highestVersionChunk = uassertStatusOK(
ChunkType::parseFromConfigBSON(chunksVector.front(), coll.getEpoch(), coll.getTimestamp()));
const auto currentCollectionPlacementVersion = highestVersionChunk.getVersion();
// Check that current collection epoch and timestamp still matches the one sent by the shard.
// This is to spot scenarios in which the collection have been dropped and recreated or had its
// shard key refined since the migration began.
uassert(StaleEpochInfo(nss),
str::stream() << "The epoch of collection '" << nss.toStringForErrorMsg()
<< "' has changed since the migration began. The config server's "
"collection placement version epoch is now '"
<< currentCollectionPlacementVersion.epoch().toString()
<< "', but the shard's is " << collectionEpoch.toString()
<< "'. Aborting clear jumbo on chunk (" << chunk.toString() << ").",
currentCollectionPlacementVersion.epoch() == collectionEpoch);
ChunkVersion newVersion({currentCollectionPlacementVersion.epoch(),
currentCollectionPlacementVersion.getTimestamp()},
{currentCollectionPlacementVersion.majorVersion() + 1, 0});
// Best-effort update of the in-memory routing cache so the balancer's next iteration observes
// the cleared flag without waiting for a refresh. This mirrors the asymmetric pattern used by
// splitOrMarkJumbo when it sets the flag: the persisted write below intentionally does not
// bump the chunk version, so an incremental routing-cache refresh would not pick up the
// change. The balancer is the only consumer of the jumbo flag, so updating the configsvr's
// own routing entry in place is sufficient. Stale jumbo:true entries on mongos and shard
// routing caches are benign because no router or shard reads the field for routing or
// filtering decisions. If the cache cannot be obtained or doesn't contain the chunk, the
// persisted write below is still correct; the balancer will observe the change on its next
// refresh.
if (cm.isSharded()) {
auto inMemoryChunk = cm.findIntersectingChunkWithSimpleCollation(chunk.getMin());
if (inMemoryChunk.getMin().woCompare(chunk.getMin()) == 0 &&
inMemoryChunk.getMax().woCompare(chunk.getMax()) == 0) {
inMemoryChunk.setJumbo(false);
}
}
BSONObj chunkQuery(BSON(ChunkType::min(chunk.getMin())
<< ChunkType::max(chunk.getMax()) << ChunkType::collectionUUID
<< coll.getUuid()));
BSONObjBuilder updateBuilder;
updateBuilder.append("$unset", BSON(ChunkType::jumbo() << ""));
// Update the newest chunk to have the new (bumped) version
BSONObjBuilder updateVersionClause(updateBuilder.subobjStart("$set"));
updateVersionClause.appendTimestamp(ChunkType::lastmod(), newVersion.toLong());
updateVersionClause.doneFast();
auto chunkUpdate = updateBuilder.obj();
auto didUpdate = uassertStatusOK(
_localCatalogClient->updateConfigDocument(opCtx,
NamespaceString::kConfigsvrChunksNamespace,
chunkQuery,
chunkUpdate,
BSON("$unset" << BSON(ChunkType::jumbo() << "")),
false /* upsert */,
kNoWaitWriteConcern));
@ -2302,7 +2286,6 @@ void ShardingCatalogManager::splitOrMarkJumbo(OperationContext* opCtx,
if (splitPoints.empty()) {
LOGV2(21873, "Marking chunk as jumbo", "chunk"_attr = redact(chunk.toString()));
chunk.markAsJumbo();
// Take _kChunkOpLock in exclusive mode to prevent concurrent chunk modifications. Note
// that the operation below doesn't increment the chunk marked as jumbo's version, which
@ -2325,6 +2308,16 @@ void ShardingCatalogManager::splitOrMarkJumbo(OperationContext* opCtx,
!findCollResponse.docs.empty());
const CollectionType coll(findCollResponse.docs[0]);
// Best-effort update of the in-memory routing cache so the balancer's next iteration
// observes the flag update without waiting for a refresh.
// The persisted write below intentionally does not bump the chunk version, so an
// incremental routing-cache refresh would not pick up the change. The balancer is the
// only consumer of the jumbo flag, so updating the configsvr's own routing entry in
// place is sufficient.
// If the cache cannot be obtained or doesn't contain the chunk, the persisted write
// below is still correct; the balancer will observe the change on its next refresh.
chunk.setJumbo(true);
const auto chunkQuery = BSON(ChunkType::collectionUUID()
<< coll.getUuid() << ChunkType::min(chunk.getMin()));

View File

@ -60,7 +60,7 @@ public:
}
ChunkRange nonJumboChunk() {
return ChunkRange(BSON("x" << 0), BSON("x" << BSONType::maxKey));
return ChunkRange(BSON("x" << 0), BSON("x" << MAXKEY));
}
protected:
@ -101,7 +101,7 @@ protected:
NamespaceString::createNamespaceString_forTest("TestDB.TestColl2");
};
TEST_F(ClearJumboFlagTest, ClearJumboShouldBumpVersion) {
TEST_F(ClearJumboFlagTest, ClearJumboShouldNotBumpVersion) {
auto test = [&](const NamespaceString& nss, const Timestamp& collTimestamp) {
const auto collUuid = UUID::gen();
const auto collEpoch = OID::gen();
@ -113,8 +113,9 @@ TEST_F(ClearJumboFlagTest, ClearJumboShouldBumpVersion) {
auto chunkDoc = uassertStatusOK(getChunkDoc(
operationContext(), collUuid, jumboChunk().getMin(), collEpoch, collTimestamp));
ASSERT_FALSE(chunkDoc.getJumbo());
auto chunkVersion = chunkDoc.getVersion();
ASSERT_EQ(ChunkVersion({collEpoch, collTimestamp}, {15, 0}), chunkVersion);
// The persisted chunk version stays at the original {12, 7} — the clear is observed via
// the in-memory routing cache, not via a placement-version bump.
ASSERT_EQ(ChunkVersion({collEpoch, collTimestamp}, {12, 7}), chunkDoc.getVersion());
};
test(_nss2, Timestamp(42));