Skip to content

Commit 29b4253

Browse files
afuschettoMongoDB Bot
authored andcommitted
SERVER-99277 Avoid full refresh of sharding metadata on shard removal (#31891)
GitOrigin-RevId: fdbf6abdf2a141f250c65017adc75a61b4a0dd72
1 parent e1b874f commit 29b4253

9 files changed

+162
-17
lines changed

etc/backports_required_for_multiversion_tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,8 @@ last-continuous:
579579
ticket: SERVER-100489
580580
- test_file: jstests/sharding/sharded_data_distribution.js
581581
ticket: SERVER-88400
582+
- test_file: jstests/sharding/metadata_refresh_on_shard_removal.js
583+
ticket: SERVER-99277
582584
suites: null
583585
last-lts:
584586
all:
@@ -1210,4 +1212,6 @@ last-lts:
12101212
ticket: SERVER-100489
12111213
- test_file: jstests/sharding/sharded_data_distribution.js
12121214
ticket: SERVER-88400
1215+
- test_file: jstests/sharding/metadata_refresh_on_shard_removal.js
1216+
ticket: SERVER-99277
12131217
suites: null

jstests/sharding/shard_removal_triggers_catalog_cache_invalidation.js renamed to jstests/sharding/metadata_refresh_on_shard_removal.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,30 @@ const dbName = 'TestDB';
2828
* 4. Remove shard0 by sending removeShard through router 0. All data will be migrated to shard1.
2929
* 5. Send a query through router 1 to target the sharded collection. This should correctly target
3030
* shard1.
31+
* 6. Ensure that no full refresh of the routing information is triggered on router 1, as a result
32+
* of the CRUD operations on the sharded collection after the shard0 removal.
3133
*/
3234
(() => {
3335
jsTestLog(
34-
"Test that sharded collections with data on a shard that gets removed are correctly invalidated in a router's catalog cache.");
36+
"Test that metadata of a sharded collection with data on a shard that gets removed is correctly invalidated in a router's catalog cache.");
3537

3638
const shardedCollName = 'Coll';
3739
const shardedCollNs = dbName + '.' + shardedCollName;
3840

39-
var st = new ShardingTest({shards: 2, mongos: 2});
41+
var st = new ShardingTest({
42+
shards: 2,
43+
mongos: 2,
44+
mongosOptions: {setParameter: {noCatalogCachePurgeOnShardRemoval: true}}
45+
});
46+
4047
let router0ShardedColl = st.s0.getDB(dbName)[shardedCollName];
4148
let router1ShardedColl = st.s1.getDB(dbName)[shardedCollName];
4249

4350
assert.commandWorked(st.s0.adminCommand({enableSharding: dbName}));
4451
st.ensurePrimaryShard(dbName, st.shard1.shardName);
4552
assert.commandWorked(st.s0.adminCommand({shardCollection: shardedCollNs, key: {_id: 1}}));
4653

47-
// Make sure data is inserted into shard0
54+
// Make sure data is inserted into shard0.
4855
assert.commandWorked(st.s0.adminCommand({
4956
moveChunk: shardedCollNs,
5057
find: {_id: -1},
@@ -75,6 +82,9 @@ const dbName = 'TestDB';
7582
st.rs0.stopSet();
7683
}
7784

85+
const initCatalogCacheStats =
86+
st.s1.getDB(dbName).serverStatus({}).shardingStatistics.catalogCache;
87+
7888
// Ensure that s1, the router which did not run removeShard, eventually stops targeting chunks
7989
// for the sharded collection which previously resided on a shard that no longer exists.
8090
assert.soon(() => {
@@ -87,6 +97,18 @@ const dbName = 'TestDB';
8797
}
8898
});
8999

100+
// Removing shard0 updates the metadata version in the catalog cache of all nodes. The
101+
// subsequent CRUD operation on router1 triggered a incremental refresh (instead of a full
102+
// refresh) of its catalog cache.
103+
const updatedCatalogCacheStats =
104+
st.s1.getDB(dbName).serverStatus({}).shardingStatistics.catalogCache;
105+
assert.eq(0,
106+
updatedCatalogCacheStats.countFullRefreshesStarted -
107+
initCatalogCacheStats.countFullRefreshesStarted);
108+
assert.eq(1,
109+
updatedCatalogCacheStats.countIncrementalRefreshesStarted -
110+
initCatalogCacheStats.countIncrementalRefreshesStarted);
111+
90112
st.stop();
91113
})();
92114

@@ -99,8 +121,10 @@ const dbName = 'TestDB';
99121
* 3. Ensure both routers have up-to-date routing info.
100122
* 4. movePrimary for the database to shard1.
101123
* 4. Remove shard0 by sending removeShard through router 0.
102-
* 5. Send a query through router 1 to target the sharded and unsharded collections. This should
103-
* correctly target shard1.
124+
* 5. Send a query through router 1 to target the unsharded collection. This should correctly target
125+
* shard1.
126+
* 6. Ensure that no full refresh of the routing information is triggered on router 1, as a result
127+
* of the CRUD operations on the unsharded collection after the shard0 removal.
104128
*/
105129
(() => {
106130
jsTestLog(
@@ -152,6 +176,13 @@ const dbName = 'TestDB';
152176
return false;
153177
}
154178
});
179+
180+
// TODO (SERVER-80724): Implement the following test case when database metadata refresh
181+
// statistics are available.
182+
// Removing shard0 updates the metadata version in the catalog cache of all nodes. The
183+
// subsequent CRUD operation on router1 triggered a incremental refresh (instead of a full
184+
// refresh) of its catalog cache.
185+
155186
st.stop();
156187
})();
157188
})();

src/mongo/db/s/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ collection or database. A full refresh occurs when:
111111
Methods that will mark routing table cache information as stale (sharded collection).
112112

113113
* [invalidateShardOrEntireCollectionEntryForShardedCollection](https://github.com/mongodb/mongo/blob/62d9485657717bf61fbb870cb3d09b52b1a614dd/src/mongo/s/catalog_cache.h#L226-L236)
114-
* [invalidateEntriesThatReferenceShard](https://github.com/mongodb/mongo/blob/62d9485657717bf61fbb870cb3d09b52b1a614dd/src/mongo/s/catalog_cache.h#L270-L274)
115114
* [invalidateCollectionEntry_LINEARIZABLE](https://github.com/mongodb/mongo/blob/32fe49396dec58836033bca67ad1360b1a80f03c/src/mongo/s/catalog_cache.h#L211-L216)
116115

117116
Methods that will mark routing table cache information as stale (database).

src/mongo/db/s/sharding_initialization_mongod.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#include "mongo/s/client/sharding_connection_hook.h"
6868
#include "mongo/s/config_server_catalog_cache_loader.h"
6969
#include "mongo/s/grid.h"
70+
#include "mongo/s/mongod_and_mongos_server_parameters_gen.h"
7071

7172
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding
7273

@@ -641,11 +642,14 @@ void initializeGlobalShardingStateForMongoD(OperationContext* opCtx,
641642
// List of hooks which will be called by the ShardRegistry when it discovers a shard has been
642643
// removed.
643644
std::vector<ShardRegistry::ShardRemovalHook> shardRemovalHooks = {
644-
// Invalidate appropriate entries in the CatalogCache when a shard is removed. It's safe to
645-
// capture the CatalogCache pointer since the Grid (and therefore CatalogCache and
646-
// ShardRegistry) are never destroyed.
645+
// It's safe to capture the CatalogCache pointer since the Grid (and therefore CatalogCache
646+
// and ShardRegistry) are never destroyed.
647647
[catCache = catalogCache.get()](const ShardId& removedShard) {
648-
catCache->invalidateEntriesThatReferenceShard(removedShard);
648+
if (gNoCatalogCachePurgeOnShardRemoval) {
649+
catCache->advanceTimeInStoreForEntriesThatReferenceShard(removedShard);
650+
} else {
651+
catCache->invalidateEntriesThatReferenceShard(removedShard);
652+
}
649653
}};
650654

651655
auto shardRegistry = std::make_unique<ShardRegistry>(

src/mongo/s/catalog_cache.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,56 @@ void CatalogCache::invalidateShardOrEntireCollectionEntryForShardedCollection(
704704
}
705705
}
706706

707+
void CatalogCache::advanceTimeInStoreForEntriesThatReferenceShard(const ShardId& shardId) {
708+
LOGV2_DEBUG(
709+
9927700,
710+
1,
711+
"Advancing the cached version for databases and collections referencing a specific shard",
712+
"shardId"_attr = shardId);
713+
714+
const auto dbEntries = _databaseCache.peekLatestCachedIf(
715+
[&](const std::string&, const DatabaseType& dbt) { return dbt.getPrimary() == shardId; });
716+
for (const auto& dbEntry : dbEntries) {
717+
LOGV2_DEBUG(9927701,
718+
1,
719+
"Advancing the cached version for a database",
720+
"db"_attr = dbEntry->getName());
721+
722+
_databaseCache.advanceTimeInStore(
723+
dbEntry->getName(),
724+
ComparableDatabaseVersion::makeComparableDatabaseVersionForForcedRefresh());
725+
}
726+
727+
const auto collEntries = _collectionCache.peekLatestCachedIf(
728+
[&](const NamespaceString&, const OptionalRoutingTableHistory& ort) {
729+
if (!ort.optRt) {
730+
return false;
731+
}
732+
const auto& rt = *ort.optRt;
733+
734+
std::set<ShardId> shardIds;
735+
rt.getAllShardIds(&shardIds);
736+
737+
return shardIds.find(shardId) != shardIds.end();
738+
});
739+
for (const auto& collEntry : collEntries) {
740+
invariant(collEntry->optRt);
741+
const auto& rt = *collEntry->optRt;
742+
743+
LOGV2_DEBUG(9927702,
744+
1,
745+
"Advancing the cached version for a collection",
746+
"namespace"_attr = rt.nss());
747+
748+
_collectionCache.advanceTimeInStore(
749+
rt.nss(), ComparableChunkVersion::makeComparableChunkVersionForForcedRefresh());
750+
}
751+
752+
LOGV2(9927703,
753+
"Advanced the cached version for databases and collections referencing a specific shard",
754+
"shardId"_attr = shardId);
755+
}
756+
707757
void CatalogCache::invalidateEntriesThatReferenceShard(const ShardId& shardId) {
708758
LOGV2_DEBUG(4997600,
709759
1,

src/mongo/s/catalog_cache.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,13 @@ class CatalogCache {
272272
const boost::optional<ShardVersion>& wantedVersion,
273273
const ShardId& shardId);
274274

275+
/**
276+
* Notifies the cache that there is a (possibly) newer version on the backing store for all the
277+
* entries that reference the passed shard. This will trigger an incremental refresh on the next
278+
* cache access.
279+
*/
280+
void advanceTimeInStoreForEntriesThatReferenceShard(const ShardId& shardId);
281+
275282
/**
276283
* Non-blocking method, which invalidates all namespaces which contain data on the specified
277284
* shard and all databases which have the shard listed as their primary shard.

src/mongo/s/catalog_cache_test.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,20 +261,56 @@ TEST_F(CatalogCacheTest, GetDatabaseDrop) {
261261
ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, swDatabase.getStatus());
262262
}
263263

264-
TEST_F(CatalogCacheTest, InvalidateSingleDbOnShardRemoval) {
264+
TEST_F(CatalogCacheTest, AdvanceTimeInStoreForSingleDbOnShardRemoval) {
265+
// Put initial metadata in the local cache.
265266
const auto dbName = "testDB";
266267
const auto dbVersion = DatabaseVersion(UUID::gen(), Timestamp(1, 1));
267268
loadDatabases({DatabaseType(dbName, kShards[0], dbVersion)});
268269

269-
_catalogCache->invalidateEntriesThatReferenceShard(kShards[0]);
270+
_catalogCache->advanceTimeInStoreForEntriesThatReferenceShard(kShards[0]);
271+
272+
// Put the new metadata in the loader.
270273
_catalogCacheLoader->setDatabaseRefreshReturnValue(DatabaseType(dbName, kShards[1], dbVersion));
274+
275+
// Refresh the local cache.
271276
const auto swDatabase = _catalogCache->getDatabase(operationContext(), dbName);
272277

273278
ASSERT_OK(swDatabase.getStatus());
274-
auto cachedDb = swDatabase.getValue();
279+
const auto cachedDb = swDatabase.getValue();
275280
ASSERT_EQ(cachedDb->getPrimary(), kShards[1]);
276281
}
277282

283+
TEST_F(CatalogCacheTest, AdvanceTimeInStoreForSingleCollectionOnShardRemoval) {
284+
// Put initial metadata in the local cache.
285+
const auto initDbVer = DatabaseVersion(UUID::gen(), Timestamp(1, 1));
286+
const auto initDbEntry = DatabaseType(kNss.db().toString(), kShards[0], initDbVer);
287+
loadDatabases({initDbEntry});
288+
const auto initCollVer =
289+
ShardVersionFactory::make(ChunkVersion({OID::gen(), Timestamp(1, 1)}, {1, 0}),
290+
boost::optional<CollectionIndexes>(boost::none));
291+
loadCollection(initCollVer);
292+
293+
_catalogCache->advanceTimeInStoreForEntriesThatReferenceShard(kShards[0]);
294+
295+
// Put the new metadata in the loader.
296+
const auto newDbEntry = initDbEntry;
297+
auto newCollVer = initCollVer;
298+
newCollVer.placementVersion().incMajor();
299+
const auto newCollEntry = makeCollectionType(newCollVer);
300+
const auto newChunkEntries = makeChunks(newCollVer.placementVersion());
301+
302+
_catalogCacheLoader->setDatabaseRefreshReturnValue(newDbEntry);
303+
_catalogCacheLoader->setCollectionRefreshReturnValue(newCollEntry);
304+
_catalogCacheLoader->setChunkRefreshReturnValue(newChunkEntries);
305+
306+
// Refresh the local cache.
307+
const auto swColl = _catalogCache->getCollectionRoutingInfo(operationContext(), kNss);
308+
309+
ASSERT_OK(swColl.getStatus());
310+
auto cachedColl = swColl.getValue();
311+
ASSERT_EQ(newCollVer, cachedColl.getCollectionVersion());
312+
}
313+
278314
TEST_F(CatalogCacheTest, OnStaleDatabaseVersionNoVersion) {
279315
// onStaleDatabaseVesrsion must invalidate the database entry if invoked with no version
280316
const auto dbVersion = DatabaseVersion(UUID::gen(), Timestamp(1, 1));

src/mongo/s/mongod_and_mongos_server_parameters.idl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ server_parameters:
7979
default: 500
8080
validator:
8181
gte: 0
82+
83+
noCatalogCachePurgeOnShardRemoval:
84+
description: >-
85+
Advances the catalog cache's time in store when a shard is removed, instead of purging the
86+
cache content. This triggers an incremental refresh of the metadata on the next cache access
87+
rather than a full (and more expensive) refresh.
88+
set_at: [ startup ]
89+
cpp_vartype: bool
90+
cpp_varname: "gNoCatalogCachePurgeOnShardRemoval"
91+
default: false

src/mongo/s/mongos_main.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
#include "mongo/s/grid.h"
8282
#include "mongo/s/is_mongos.h"
8383
#include "mongo/s/load_balancer_support.h"
84+
#include "mongo/s/mongod_and_mongos_server_parameters_gen.h"
8485
#include "mongo/s/mongos_options.h"
8586
#include "mongo/s/mongos_server_parameters_gen.h"
8687
#include "mongo/s/mongos_topology_coordinator.h"
@@ -431,11 +432,14 @@ Status initializeSharding(OperationContext* opCtx) {
431432
// List of hooks which will be called by the ShardRegistry when it discovers a shard has been
432433
// removed.
433434
std::vector<ShardRegistry::ShardRemovalHook> shardRemovalHooks = {
434-
// Invalidate appropriate entries in the catalog cache when a shard is removed. It's safe to
435-
// capture the catalog cache pointer since the Grid (and therefore CatalogCache and
436-
// ShardRegistry) are never destroyed.
435+
// It's safe to capture the CatalogCache pointer since the Grid (and therefore CatalogCache
436+
// and ShardRegistry) are never destroyed.
437437
[catCache = catalogCache.get()](const ShardId& removedShard) {
438-
catCache->invalidateEntriesThatReferenceShard(removedShard);
438+
if (gNoCatalogCachePurgeOnShardRemoval) {
439+
catCache->advanceTimeInStoreForEntriesThatReferenceShard(removedShard);
440+
} else {
441+
catCache->invalidateEntriesThatReferenceShard(removedShard);
442+
}
439443
}};
440444

441445
if (!mongosGlobalParams.configdbs) {

0 commit comments

Comments
 (0)