diff options
author | Louis Williams <louis.williams@mongodb.com> | 2022-03-29 13:36:33 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-04-20 16:07:06 +0000 |
commit | 5c4ea8bae7c97955962fa4f825a0ed9ce00d77ed (patch) | |
tree | 9a92e98b6ad74093c70967263da7a1dd4123321b | |
parent | 43253c80d557e82f96c175d01c95953d506cceda (diff) | |
download | mongo-5c4ea8bae7c97955962fa4f825a0ed9ce00d77ed.tar.gz |
SERVER-63254 Add index feature usage stats to serverStatus
(cherry picked from commit 0fa0170045ccab649621111ec86c80e798bcac23)
(cherry picked from commit 45def0471022bb6e7a56acb354e35817211a25d8)
-rw-r--r-- | jstests/noPassthrough/serverstatus_index_stats.js | 222 | ||||
-rw-r--r-- | src/mongo/db/SConscript | 6 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_build_block.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_impl.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/collection_index_usage_tracker.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/collection_index_usage_tracker.h | 24 | ||||
-rw-r--r-- | src/mongo/db/collection_index_usage_tracker_test.cpp | 154 | ||||
-rw-r--r-- | src/mongo/db/global_index_usage_tracker.cpp | 223 | ||||
-rw-r--r-- | src/mongo/db/global_index_usage_tracker.h | 132 | ||||
-rw-r--r-- | src/mongo/db/query/collection_index_usage_tracker_decoration.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/query/collection_query_info.cpp | 4 |
11 files changed, 792 insertions, 39 deletions
diff --git a/jstests/noPassthrough/serverstatus_index_stats.js b/jstests/noPassthrough/serverstatus_index_stats.js new file mode 100644 index 00000000000..a1fc286e8e1 --- /dev/null +++ b/jstests/noPassthrough/serverstatus_index_stats.js @@ -0,0 +1,222 @@ +/** + * Tests that serverStatus contains an indexStats section. This section reports globally-aggregated + * statistics about features in use by indexes and how often they are used. + * + * @tags: [ + * requires_persistence, + * requires_replication, + * ] + */ +(function() { +"use strict"; + +const assertStats = (db, assertFn) => { + const stats = db.serverStatus().indexStats; + try { + assertFn(stats); + } catch (e) { + print("result: " + tojson(stats)); + throw e; + } +}; + +// If new features are added, they must also be added to this list or the test will fail. +const knownFeatures = [ + "2d", + "2dsphere", + "collation", + "compound", + "hashed", + "id", + "normal", + "partial", + "single", + "sparse", + "text", + "ttl", + "unique", + "wildcard", +]; + +const assertZeroCounts = (db) => { + assertStats(db, (featureStats) => { + assert.eq(featureStats.count, 0); + for (const [feature, stats] of Object.entries(featureStats.features)) { + assert.contains(feature, knownFeatures, "unknown feature reported by indexStats"); + assert.eq(0, stats.count, feature); + } + }); +}; + +const assertZeroAccess = (db) => { + assertStats(db, (featureStats) => { + for (const [feature, stats] of Object.entries(featureStats.features)) { + assert.contains(feature, knownFeatures, "unknown feature reported by indexStats"); + assert.eq(0, stats.accesses, feature); + } + }); +}; + +const assertCountIncrease = (last, current, inc) => { + assert.eq(last.count + inc, current.count, "incorrect index count"); +}; + +const assertFeatureCountIncrease = (last, current, feature, inc) => { + assert.eq(last.features[feature].count + inc, + current.features[feature].count, + "incorrect feature count for " + feature); +}; + +const assertFeatureAccessIncrease = (last, current, feature, inc) => { + assert.eq(last.features[feature].accesses + inc, + current.features[feature].accesses, + "incorrect feature accesses for " + feature); +}; + +const replSet = new ReplSetTest({nodes: 1}); +replSet.startSet(); +replSet.initiate(); + +let primary = replSet.getPrimary(); +let db = primary.getDB('test'); + +assertZeroCounts(db); +assertZeroAccess(db); + +let lastStats = db.serverStatus().indexStats; + +assert.commandWorked(db.testColl.createIndex({twoD: '2d', b: 1}, {unique: true, sparse: true})); +assert.commandWorked(db.testColl.insert({twoD: [0, 0], b: 1})); +assert.eq(1, db.testColl.find({twoD: {$geoNear: [0, 0]}}).itcount()); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 2); + assertFeatureCountIncrease(lastStats, stats, '2d', 1); + assertFeatureCountIncrease(lastStats, stats, 'compound', 1); + // The index build implicitly created the collection, which also builds an _id index. + assertFeatureCountIncrease(lastStats, stats, 'id', 1); + assertFeatureCountIncrease(lastStats, stats, 'sparse', 1); + // Note that the _id index is not included in this unique counter. This is due to a quirk in the + // _id index spec that does not actually have a unique:true property. + assertFeatureCountIncrease(lastStats, stats, 'unique', 1); + + assertFeatureAccessIncrease(lastStats, stats, '2d', 1); + assertFeatureAccessIncrease(lastStats, stats, 'compound', 1); + assertFeatureAccessIncrease(lastStats, stats, 'id', 0); + assertFeatureAccessIncrease(lastStats, stats, 'sparse', 1); + assertFeatureAccessIncrease(lastStats, stats, 'unique', 1); +}); + +lastStats = db.serverStatus().indexStats; + +assert.commandWorked(db.testColl.createIndex({sphere: '2dsphere'})); +assert.commandWorked(db.testColl.insert({sphere: {type: "Point", coordinates: [0, 0]}})); +assert.eq(1, + db.testColl + .aggregate([{ + $geoNear: { + near: {type: "Point", coordinates: [1, 1]}, + key: 'sphere', + distanceField: 'dist', + } + }]) + .itcount()); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 1); + assertFeatureCountIncrease(lastStats, stats, '2dsphere', 1); + assertFeatureCountIncrease(lastStats, stats, 'single', 1); + + assertFeatureAccessIncrease(lastStats, stats, '2dsphere', 1); + assertFeatureAccessIncrease(lastStats, stats, 'single', 1); +}); + +lastStats = db.serverStatus().indexStats; + +assert.commandWorked( + db.testColl.createIndex({hashed: 'hashed', p: 1}, {partialFilterExpression: {p: 1}})); +assert.commandWorked(db.testColl.insert({hashed: 1, p: 1})); +assert.eq(1, db.testColl.find({hashed: 1}).hint({hashed: 'hashed', p: 1}).itcount()); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 1); + assertFeatureCountIncrease(lastStats, stats, 'compound', 1); + assertFeatureCountIncrease(lastStats, stats, 'hashed', 1); + assertFeatureCountIncrease(lastStats, stats, 'partial', 1); + + assertFeatureAccessIncrease(lastStats, stats, 'compound', 1); + assertFeatureAccessIncrease(lastStats, stats, 'hashed', 1); + assertFeatureAccessIncrease(lastStats, stats, 'partial', 1); +}); + +lastStats = db.serverStatus().indexStats; + +assert.commandWorked( + db.testColl.createIndex({a: 1}, {expireAfterSeconds: 3600, collation: {locale: 'en'}})); +let now = new Date(); +assert.commandWorked(db.testColl.insert({a: now})); +assert.eq(1, db.testColl.find({a: now}).itcount()); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 1); + assertFeatureCountIncrease(lastStats, stats, 'collation', 1); + assertFeatureCountIncrease(lastStats, stats, 'normal', 1); + assertFeatureCountIncrease(lastStats, stats, 'single', 1); + assertFeatureCountIncrease(lastStats, stats, 'ttl', 1); + + assertFeatureAccessIncrease(lastStats, stats, 'collation', 1); + assertFeatureAccessIncrease(lastStats, stats, 'normal', 1); + assertFeatureAccessIncrease(lastStats, stats, 'single', 1); + assertFeatureAccessIncrease(lastStats, stats, 'ttl', 1); +}); + +lastStats = db.serverStatus().indexStats; + +assert.commandWorked(db.testColl.createIndex({text: 'text'})); +assert.commandWorked(db.testColl.insert({text: "a string"})); +assert.eq(1, db.testColl.find({$text: {$search: "string"}}).itcount()); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 1); + // Text indexes are internally compound, but that should not be reflected in the stats. + assertFeatureCountIncrease(lastStats, stats, 'compound', 0); + assertFeatureCountIncrease(lastStats, stats, 'text', 1); + + assertFeatureAccessIncrease(lastStats, stats, 'compound', 0); + assertFeatureAccessIncrease(lastStats, stats, 'text', 1); +}); + +lastStats = db.serverStatus().indexStats; + +assert.commandWorked(db.testColl.createIndex({'wild.$**': 1})); +assert.commandWorked(db.testColl.insert({wild: {a: 1}})); +assert.eq(1, db.testColl.find({'wild.a': 1}).itcount()); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 1); + assertFeatureCountIncrease(lastStats, stats, 'single', 1); + assertFeatureCountIncrease(lastStats, stats, 'wildcard', 1); + + assertFeatureAccessIncrease(lastStats, stats, 'single', 1); + assertFeatureAccessIncrease(lastStats, stats, 'wildcard', 1); +}); + +lastStats = db.serverStatus().indexStats; + +// After restarting the server, we expect all of the access counters to reset to zero, but that the +// feature counters remain the same as before startup. +replSet.stopSet(undefined, /* restart */ true); +replSet.startSet({}, /* restart */ true); +primary = replSet.getPrimary(); +db = primary.getDB('test'); + +assertZeroAccess(db); +assertStats(db, (stats) => { + assertCountIncrease(lastStats, stats, 0); + + const features = stats.features; + for (const [feature, _] of Object.entries(features)) { + assert.contains(feature, knownFeatures); + assertFeatureCountIncrease(lastStats, stats, feature, 0); + } +}); + +assert.commandWorked(db.dropDatabase()); +assertZeroCounts(db); + +replSet.stopSet(); +})(); diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 49969c20fc7..be5b6162d8b 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -243,12 +243,16 @@ env.Library( env.Library( target='collection_index_usage_tracker', source=[ - 'collection_index_usage_tracker.cpp' + 'collection_index_usage_tracker.cpp', + 'global_index_usage_tracker.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/commands/server_status_core', ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/index_names', + ] ) # This library exists because some libraries, such as our networking library, need access to server diff --git a/src/mongo/db/catalog/index_build_block.cpp b/src/mongo/db/catalog/index_build_block.cpp index 80a45ad0f56..ac29eccbc29 100644 --- a/src/mongo/db/catalog/index_build_block.cpp +++ b/src/mongo/db/catalog/index_build_block.cpp @@ -82,7 +82,9 @@ void IndexBuildBlock::_completeInit(OperationContext* opCtx, Collection* collect auto desc = getEntry(opCtx, collection)->descriptor(); CollectionQueryInfo::get(collection).rebuildIndexData(opCtx, collection); CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations()) - .registerIndex(desc->indexName(), desc->keyPattern()); + .registerIndex(desc->indexName(), + desc->keyPattern(), + IndexFeatures::make(desc, collection->ns().isOnInternalDb())); } Status IndexBuildBlock::initForResume(OperationContext* opCtx, diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp index a090f88ce14..371680f0af8 100644 --- a/src/mongo/db/catalog/index_catalog_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_impl.cpp @@ -1048,9 +1048,11 @@ namespace { class IndexRemoveChange final : public RecoveryUnit::Change { public: IndexRemoveChange(IndexCatalogEntryContainer* entries, + const NamespaceString& nss, std::shared_ptr<IndexCatalogEntry> entry, SharedCollectionDecorations* collectionDecorations) : _entries(entries), + _nss(nss), _entry(std::move(entry)), _collectionDecorations(collectionDecorations) {} @@ -1064,11 +1066,14 @@ public: // Refresh the CollectionIndexUsageTrackerDecoration's knowledge of what indices are // present as it is shared state across Collection copies. CollectionIndexUsageTrackerDecoration::get(_collectionDecorations) - .registerIndex(indexDescriptor->indexName(), indexDescriptor->keyPattern()); + .registerIndex(indexDescriptor->indexName(), + indexDescriptor->keyPattern(), + IndexFeatures::make(indexDescriptor, _nss.isOnInternalDb())); } private: IndexCatalogEntryContainer* _entries; + const NamespaceString _nss; std::shared_ptr<IndexCatalogEntry> _entry; SharedCollectionDecorations* _collectionDecorations; }; @@ -1087,13 +1092,19 @@ Status IndexCatalogImpl::dropIndexEntry(OperationContext* opCtx, auto released = _readyIndexes.release(entry->descriptor()); if (released) { invariant(released.get() == entry); - opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>( - &_readyIndexes, std::move(released), collection->getSharedDecorations())); + opCtx->recoveryUnit()->registerChange( + std::make_unique<IndexRemoveChange>(&_readyIndexes, + collection->ns(), + std::move(released), + collection->getSharedDecorations())); } else { released = _buildingIndexes.release(entry->descriptor()); invariant(released.get() == entry); - opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>( - &_buildingIndexes, std::move(released), collection->getSharedDecorations())); + opCtx->recoveryUnit()->registerChange( + std::make_unique<IndexRemoveChange>(&_buildingIndexes, + collection->ns(), + std::move(released), + collection->getSharedDecorations())); } CollectionQueryInfo::get(collection).rebuildIndexData(opCtx, collection); @@ -1278,7 +1289,7 @@ const IndexDescriptor* IndexCatalogImpl::refreshEntry(OperationContext* opCtx, auto oldEntry = _readyIndexes.release(oldDesc); invariant(oldEntry); opCtx->recoveryUnit()->registerChange(std::make_unique<IndexRemoveChange>( - &_readyIndexes, std::move(oldEntry), collection->getSharedDecorations())); + &_readyIndexes, collection->ns(), std::move(oldEntry), collection->getSharedDecorations())); CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations()) .unregisterIndex(indexName); @@ -1294,7 +1305,9 @@ const IndexDescriptor* IndexCatalogImpl::refreshEntry(OperationContext* opCtx, invariant(newEntry->isReady(opCtx, collection)); auto desc = newEntry->descriptor(); CollectionIndexUsageTrackerDecoration::get(collection->getSharedDecorations()) - .registerIndex(desc->indexName(), desc->keyPattern()); + .registerIndex(desc->indexName(), + desc->keyPattern(), + IndexFeatures::make(desc, collection->ns().isOnInternalDb())); // Last rebuild index data for CollectionQueryInfo for this Collection. CollectionQueryInfo::get(collection).rebuildIndexData(opCtx, collection); diff --git a/src/mongo/db/collection_index_usage_tracker.cpp b/src/mongo/db/collection_index_usage_tracker.cpp index 32df44e98f2..168e8766be1 100644 --- a/src/mongo/db/collection_index_usage_tracker.cpp +++ b/src/mongo/db/collection_index_usage_tracker.cpp @@ -37,6 +37,7 @@ #include "mongo/base/counter.h" #include "mongo/db/commands/server_status_metric.h" +#include "mongo/db/index/index_descriptor.h" #include "mongo/util/assert_util.h" #include "mongo/util/clock_source.h" @@ -49,10 +50,14 @@ ServerStatusMetricField<Counter64> displayCollectionScans("queryExecutor.collect &collectionScansCounter); ServerStatusMetricField<Counter64> displayCollectionScansNonTailable( "queryExecutor.collectionScans.nonTailable", &collectionScansNonTailableCounter); + } // namespace -CollectionIndexUsageTracker::CollectionIndexUsageTracker(ClockSource* clockSource) - : _indexUsageStatsMap(std::make_shared<CollectionIndexUsageMap>()), _clockSource(clockSource) { +CollectionIndexUsageTracker::CollectionIndexUsageTracker( + GlobalIndexUsageTracker* globalIndexUsageTracker, ClockSource* clockSource) + : _indexUsageStatsMap(std::make_shared<CollectionIndexUsageMap>()), + _clockSource(clockSource), + _globalIndexUsageTracker(globalIndexUsageTracker) { invariant(_clockSource); } @@ -69,6 +74,8 @@ void CollectionIndexUsageTracker::recordIndexAccess(StringData indexName) { return; } + _globalIndexUsageTracker->onAccess(it->second->features); + // Increment the index usage atomic counter. it->second->accesses.fetchAndAdd(1); } @@ -84,7 +91,9 @@ void CollectionIndexUsageTracker::recordCollectionScansNonTailable( collectionScansNonTailableCounter.increment(collectionScansNonTailable); } -void CollectionIndexUsageTracker::registerIndex(StringData indexName, const BSONObj& indexKey) { +void CollectionIndexUsageTracker::registerIndex(StringData indexName, + const BSONObj& indexKey, + const IndexFeatures& features) { invariant(!indexName.empty()); // Create a copy of the map to modify. @@ -95,9 +104,11 @@ void CollectionIndexUsageTracker::registerIndex(StringData indexName, const BSON // Create the map entry. auto inserted = mapCopy->try_emplace( - indexName, make_intrusive<IndexUsageStats>(_clockSource->now(), indexKey)); + indexName, make_intrusive<IndexUsageStats>(_clockSource->now(), indexKey, features)); invariant(inserted.second); + _globalIndexUsageTracker->onRegister(inserted.first->second->features); + // Swap the modified map into place atomically. atomic_store(&_indexUsageStatsMap, std::move(mapCopy)); } @@ -109,11 +120,16 @@ void CollectionIndexUsageTracker::unregisterIndex(StringData indexName) { auto mapSharedPtr = atomic_load(&_indexUsageStatsMap); auto mapCopy = std::make_shared<CollectionIndexUsageMap>(*mapSharedPtr); - // Remove the map entry. - mapCopy->erase(indexName); + auto it = mapCopy->find(indexName); + if (it != mapCopy->end()) { + _globalIndexUsageTracker->onUnregister(it->second->features); - // Swap the modified map into place atomically. - atomic_store(&_indexUsageStatsMap, std::move(mapCopy)); + // Remove the map entry. + mapCopy->erase(it); + + // Swap the modified map into place atomically. + atomic_store(&_indexUsageStatsMap, std::move(mapCopy)); + } } std::shared_ptr<CollectionIndexUsageTracker::CollectionIndexUsageMap> diff --git a/src/mongo/db/collection_index_usage_tracker.h b/src/mongo/db/collection_index_usage_tracker.h index c51ffebdaa5..84ee2230f4f 100644 --- a/src/mongo/db/collection_index_usage_tracker.h +++ b/src/mongo/db/collection_index_usage_tracker.h @@ -33,6 +33,7 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/global_index_usage_tracker.h" #include "mongo/platform/atomic_word.h" #include "mongo/util/intrusive_counter.h" #include "mongo/util/string_map.h" @@ -41,6 +42,7 @@ namespace mongo { class ClockSource; +class ServiceContext; /** * CollectionIndexUsageTracker tracks index usage statistics for a collection. An index is @@ -68,18 +70,20 @@ public: struct IndexUsageStats : public RefCountable { IndexUsageStats() = default; - explicit IndexUsageStats(Date_t now, const BSONObj& key) - : trackerStartTime(now), indexKey(key.getOwned()) {} + explicit IndexUsageStats(Date_t now, const BSONObj& key, const IndexFeatures& idxFeatures) + : trackerStartTime(now), indexKey(key.getOwned()), features(idxFeatures) {} IndexUsageStats(const IndexUsageStats& other) : accesses(other.accesses.load()), trackerStartTime(other.trackerStartTime), - indexKey(other.indexKey) {} + indexKey(other.indexKey), + features(other.features) {} IndexUsageStats& operator=(const IndexUsageStats& other) { accesses.store(other.accesses.load()); trackerStartTime = other.trackerStartTime; indexKey = other.indexKey; + features = other.features; return *this; } @@ -91,6 +95,9 @@ public: // An owned copy of the associated IndexDescriptor's index key. BSONObj indexKey; + + // Features in use by this index for global feature usage tracking. + IndexFeatures features; }; /** @@ -107,7 +114,8 @@ public: * Does not take ownership of 'clockSource'. 'clockSource' must refer to a non-null clock * source that is valid for the lifetime of the constructed CollectionIndexUsageTracker. */ - explicit CollectionIndexUsageTracker(ClockSource* clockSource); + explicit CollectionIndexUsageTracker(GlobalIndexUsageTracker* globalTracker, + ClockSource* clockSource); /** * Record that an operation used index 'indexName'. Safe to be called by multiple threads @@ -121,7 +129,9 @@ public: * Must be called under an exclusive collection lock in order to serialize calls to * registerIndex() and unregisterIndex(). */ - void registerIndex(StringData indexName, const BSONObj& indexKey); + void registerIndex(StringData indexName, + const BSONObj& indexKey, + const IndexFeatures& features); /** * Erase statistics for index 'indexName'. Can be safely called even if indexName is not @@ -167,6 +177,10 @@ private: // be set. ClockSource* _clockSource; + // All CollectionIndexUsageTrackers also update the GlobalIndexUsageTracker to report global + // index statistics for the server. + GlobalIndexUsageTracker* _globalIndexUsageTracker; + AtomicWord<unsigned long long> _collectionScans{0}; AtomicWord<unsigned long long> _collectionScansNonTailable{0}; }; diff --git a/src/mongo/db/collection_index_usage_tracker_test.cpp b/src/mongo/db/collection_index_usage_tracker_test.cpp index 492c8cb4612..a532e91a152 100644 --- a/src/mongo/db/collection_index_usage_tracker_test.cpp +++ b/src/mongo/db/collection_index_usage_tracker_test.cpp @@ -30,6 +30,7 @@ #include "mongo/platform/basic.h" #include "mongo/db/collection_index_usage_tracker.h" +#include "mongo/db/index/index_descriptor.h" #include "mongo/db/jsobj.h" #include "mongo/unittest/unittest.h" #include "mongo/util/clock_source_mock.h" @@ -39,7 +40,7 @@ namespace { class CollectionIndexUsageTrackerTest : public unittest::Test { protected: - CollectionIndexUsageTrackerTest() : _tracker(&_clockSource) {} + CollectionIndexUsageTrackerTest() : _tracker(&_globalIndexUsage, &_clockSource) {} /** * Returns an unowned pointer to the tracker owned by this test fixture. @@ -55,7 +56,12 @@ protected: return &_clockSource; } + GlobalIndexUsageTracker* getGlobalIndexUsage() { + return &_globalIndexUsage; + } + private: + GlobalIndexUsageTracker _globalIndexUsage; ClockSourceMock _clockSource; CollectionIndexUsageTracker _tracker; }; @@ -67,7 +73,7 @@ TEST_F(CollectionIndexUsageTrackerTest, Empty) { // Test that recording of a single index hit is reflected in returned stats map. TEST_F(CollectionIndexUsageTrackerTest, SingleHit) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); auto statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") != statsMap->end()); @@ -76,7 +82,7 @@ TEST_F(CollectionIndexUsageTrackerTest, SingleHit) { // Test that recording of multiple index hits are reflected in stats map. TEST_F(CollectionIndexUsageTrackerTest, MultipleHit) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); getTracker()->recordIndexAccess("foo"); auto statsMap = getTracker()->getUsageStats(); @@ -86,7 +92,7 @@ TEST_F(CollectionIndexUsageTrackerTest, MultipleHit) { // Test that an index is registered correctly with indexKey. TEST_F(CollectionIndexUsageTrackerTest, IndexKey) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); auto statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") != statsMap->end()); ASSERT_BSONOBJ_EQ(BSON("foo" << 1), statsMap->at("foo")->indexKey); @@ -94,16 +100,16 @@ TEST_F(CollectionIndexUsageTrackerTest, IndexKey) { // Test that index registration generates an entry in the stats map. TEST_F(CollectionIndexUsageTrackerTest, Register) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); ASSERT_EQUALS(1U, getTracker()->getUsageStats()->size()); - getTracker()->registerIndex("bar", BSON("bar" << 1)); + getTracker()->registerIndex("bar", BSON("bar" << 1), {}); ASSERT_EQUALS(2U, getTracker()->getUsageStats()->size()); } // Test that index deregistration results in removal of an entry from the stats map. TEST_F(CollectionIndexUsageTrackerTest, Deregister) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); - getTracker()->registerIndex("bar", BSON("bar" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); + getTracker()->registerIndex("bar", BSON("bar" << 1), {}); ASSERT_EQUALS(2U, getTracker()->getUsageStats()->size()); getTracker()->unregisterIndex("foo"); ASSERT_EQUALS(1U, getTracker()->getUsageStats()->size()); @@ -113,7 +119,7 @@ TEST_F(CollectionIndexUsageTrackerTest, Deregister) { // Test that index deregistration results in reset of the usage counter. TEST_F(CollectionIndexUsageTrackerTest, HitAfterDeregister) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); getTracker()->recordIndexAccess("foo"); auto statsMap = getTracker()->getUsageStats(); @@ -124,7 +130,7 @@ TEST_F(CollectionIndexUsageTrackerTest, HitAfterDeregister) { statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") == statsMap->end()); - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") != statsMap->end()); @@ -133,7 +139,7 @@ TEST_F(CollectionIndexUsageTrackerTest, HitAfterDeregister) { // Test that index tracker start date/time is reset on index deregistration/registration. TEST_F(CollectionIndexUsageTrackerTest, DateTimeAfterDeregister) { - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); auto statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") != statsMap->end()); ASSERT_EQUALS(statsMap->at("foo")->trackerStartTime, getClockSource()->now()); @@ -145,7 +151,7 @@ TEST_F(CollectionIndexUsageTrackerTest, DateTimeAfterDeregister) { // Increment clock source so that a new index registration has different start time. getClockSource()->advance(Milliseconds(1)); - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") != statsMap->end()); ASSERT_EQUALS(statsMap->at("foo")->trackerStartTime, getClockSource()->now()); @@ -154,7 +160,7 @@ TEST_F(CollectionIndexUsageTrackerTest, DateTimeAfterDeregister) { // Test that the fetched stats map retains an index after the entry is concurrently removed. TEST_F(CollectionIndexUsageTrackerTest, UsageStatsMapRemainsValidAfterIndexErasure) { // Set up an index in the tracker. - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); getTracker()->recordIndexAccess("foo"); auto statsMap = getTracker()->getUsageStats(); @@ -177,7 +183,7 @@ TEST_F(CollectionIndexUsageTrackerTest, UsageStatsMapRemainsValidAfterIndexErasu // recreates the index. TEST_F(CollectionIndexUsageTrackerTest, StaleUsageStatsMapEntryIsNotUpdatedAfterErasure) { // Set up an index in the tracker. - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); auto statsMap = getTracker()->getUsageStats(); ASSERT(statsMap->find("foo") != statsMap->end()); @@ -189,7 +195,7 @@ TEST_F(CollectionIndexUsageTrackerTest, StaleUsageStatsMapEntryIsNotUpdatedAfter auto staleStatsMap = getTracker()->getUsageStats(); getTracker()->unregisterIndex("foo"); - getTracker()->registerIndex("foo", BSON("foo" << 1)); + getTracker()->registerIndex("foo", BSON("foo" << 1), {}); getTracker()->recordIndexAccess("foo"); getTracker()->recordIndexAccess("foo"); statsMap = getTracker()->getUsageStats(); @@ -200,5 +206,123 @@ TEST_F(CollectionIndexUsageTrackerTest, StaleUsageStatsMapEntryIsNotUpdatedAfter ASSERT_EQUALS(1, staleStatsMap->at("foo")->accesses.loadRelaxed()); } +namespace { +int getFeatureUseCount(GlobalIndexUsageTracker* globalIndexUsage, std::string featureSearch) { + int count = 0; + globalIndexUsage->forEachFeature([&](auto feature, auto& stats) { + if (featureSearch == feature) { + count += stats.count.load(); + } + }); + return count; +} + +int getFeatureAccessCount(GlobalIndexUsageTracker* globalIndexUsage, std::string featureSearch) { + int accesses = 0; + globalIndexUsage->forEachFeature([&](auto feature, auto& stats) { + if (featureSearch == feature) { + accesses += stats.accesses.load(); + } + }); + return accesses; +} +} // namespace + +TEST_F(CollectionIndexUsageTrackerTest, GlobalFeatureUsageBasic) { + auto idSpec = BSON("key" << BSON("_id" << 1) << "unique" << true << "v" << 2); + auto idDesc = IndexDescriptor("", idSpec); + getTracker()->registerIndex("_id_", idSpec, IndexFeatures::make(&idDesc, false /* internal */)); + getTracker()->recordIndexAccess("_id_"); + + ASSERT_EQ(1, getGlobalIndexUsage()->getCount()); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "unique")); + + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(0, getFeatureAccessCount(getGlobalIndexUsage(), "unique")); + + auto spec = BSON("key" << BSON("foo" << 1) << "unique" << true << "sparse" << true << "v" << 2); + auto desc = IndexDescriptor("", spec); + getTracker()->registerIndex("foo", spec, IndexFeatures::make(&desc, false /* internal */)); + getTracker()->recordIndexAccess("foo"); + + ASSERT_EQ(2, getGlobalIndexUsage()->getCount()); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "unique")); + + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique")); + + // Register an internal index and expect nothing to change. + getTracker()->registerIndex("foo2", spec, IndexFeatures::make(&desc, true /* internal */)); + + ASSERT_EQ(2, getGlobalIndexUsage()->getCount()); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "unique")); + + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique")); + + getTracker()->unregisterIndex("foo2"); + ASSERT_EQ(2, getGlobalIndexUsage()->getCount()); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "unique")); + + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique")); + + getTracker()->unregisterIndex("foo"); + ASSERT_EQ(1, getGlobalIndexUsage()->getCount()); + ASSERT_EQ(1, getFeatureUseCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "unique")); + + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique")); + + getTracker()->unregisterIndex("_id_"); + ASSERT_EQ(0, getGlobalIndexUsage()->getCount()); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(0, getFeatureUseCount(getGlobalIndexUsage(), "unique")); + + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "id")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "normal")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "single")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "sparse")); + ASSERT_EQ(1, getFeatureAccessCount(getGlobalIndexUsage(), "unique")); +} + } // namespace } // namespace mongo diff --git a/src/mongo/db/global_index_usage_tracker.cpp b/src/mongo/db/global_index_usage_tracker.cpp new file mode 100644 index 00000000000..85be3b747bc --- /dev/null +++ b/src/mongo/db/global_index_usage_tracker.cpp @@ -0,0 +1,223 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/global_index_usage_tracker.h" + +#include "mongo/db/commands/server_status.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/service_context.h" + +namespace mongo { +namespace { +// List of index feature description strings. +static const std::string k2d = "2d"; +static const std::string k2dSphere = "2dsphere"; +static const std::string kCollation = "collation"; +static const std::string kCompound = "compound"; +static const std::string kHashed = "hashed"; +static const std::string kId = "id"; +static const std::string kNormal = "normal"; +static const std::string kPartial = "partial"; +static const std::string kSingle = "single"; +static const std::string kSparse = "sparse"; +static const std::string kText = "text"; +static const std::string kTTL = "ttl"; +static const std::string kUnique = "unique"; +static const std::string kWildcard = "wildcard"; + +std::map<std::string, IndexFeatureStats> makeFeatureMap() { + std::map<std::string, IndexFeatureStats> map; + map[k2d]; + map[k2dSphere]; + map[kCollation]; + map[kCompound]; + map[kHashed]; + map[kId]; + map[kNormal]; + map[kPartial]; + map[kSingle]; + map[kSparse]; + map[kText]; + map[kTTL]; + map[kUnique]; + map[kWildcard]; + return map; +}; + +const auto getGlobalIndexUsageTracker = + ServiceContext::declareDecoration<GlobalIndexUsageTracker>(); +} // namespace + + +IndexFeatures IndexFeatures::make(const IndexDescriptor* desc, bool internal) { + IndexFeatures features; + + int count = 0; + for (auto it = desc->keyPattern().begin(); it != desc->keyPattern().end(); it++) { + count++; + } + tassert(6325400, "index key pattern must have at least one element", count); + + auto indexType = IndexNames::nameToType(desc->getAccessMethodName()); + features.collation = !desc->collation().isEmpty(); + // Text indexes add an extra field internally, but we don't want to expose that detail. + features.compound = (indexType == IndexType::INDEX_TEXT) ? count > 2 : count > 1; + features.id = desc->isIdIndex(); + features.internal = internal; + features.partial = desc->isPartial(); + features.sparse = desc->isSparse(); + features.ttl = desc->infoObj().hasField(IndexDescriptor::kExpireAfterSecondsFieldName); + features.type = indexType; + features.unique = desc->unique(); + return features; +} + +GlobalIndexUsageTracker::GlobalIndexUsageTracker() : _indexFeatureToStats(makeFeatureMap()) {} + +GlobalIndexUsageTracker* GlobalIndexUsageTracker::get(ServiceContext* svcCtx) { + return &getGlobalIndexUsageTracker(svcCtx); +} + +void GlobalIndexUsageTracker::onAccess(const IndexFeatures& features) const { + if (!features.internal) { + _updateStatsForEachFeature(features, [](auto stats) { stats->accesses.fetchAndAdd(1); }); + } +} + +void GlobalIndexUsageTracker::onRegister(const IndexFeatures& features) const { + if (!features.internal) { + _updateStatsForEachFeature(features, [](auto stats) { stats->count.fetchAndAdd(1); }); + _count.fetchAndAdd(1); + } +} + +void GlobalIndexUsageTracker::onUnregister(const IndexFeatures& features) const { + if (!features.internal) { + _updateStatsForEachFeature(features, [](auto stats) { stats->count.fetchAndAdd(-1); }); + _count.fetchAndAdd(-1); + } +} + +void GlobalIndexUsageTracker::_updateStatsForEachFeature(const IndexFeatures& features, + UpdateFn&& update) const { + // Aggregate _id indexes separately so they do not get included with the other features. + if (features.id) { + update(&_indexFeatureToStats.at(kId)); + return; + } + + switch (features.type) { + case INDEX_BTREE: + update(&_indexFeatureToStats.at(kNormal)); + break; + case INDEX_2D: + update(&_indexFeatureToStats.at(k2d)); + break; + case INDEX_HAYSTACK: + // MongoDB does not support haystack indexes. This enum only exists to reject creating + // them. + break; + case INDEX_2DSPHERE: + update(&_indexFeatureToStats.at(k2dSphere)); + break; + case INDEX_TEXT: + update(&_indexFeatureToStats.at(kText)); + break; + case INDEX_HASHED: + update(&_indexFeatureToStats.at(kHashed)); + break; + case INDEX_WILDCARD: + update(&_indexFeatureToStats.at(kWildcard)); + break; + default: + MONGO_UNREACHABLE; + } + + if (features.collation) { + update(&_indexFeatureToStats.at(kCollation)); + } + if (features.compound) { + update(&_indexFeatureToStats.at(kCompound)); + } else { + update(&_indexFeatureToStats.at(kSingle)); + } + if (features.partial) { + update(&_indexFeatureToStats.at(kPartial)); + } + if (features.sparse) { + update(&_indexFeatureToStats.at(kSparse)); + } + if (features.ttl) { + update(&_indexFeatureToStats.at(kTTL)); + } + if (features.unique) { + update(&_indexFeatureToStats.at(kUnique)); + } +} + +void GlobalIndexUsageTracker::forEachFeature(OnFeatureFn&& onFeature) const { + for (auto& [key, value] : _indexFeatureToStats) { + onFeature(key, value); + } +} + +long long GlobalIndexUsageTracker::getCount() const { + return _count.load(); +} + +class IndexStatsSSS : public ServerStatusSection { +public: + IndexStatsSSS() : ServerStatusSection("indexStats") {} + + ~IndexStatsSSS() override = default; + + bool includeByDefault() const override { + return true; + } + + BSONObj generateSection(OperationContext* opCtx, + const BSONElement& configElement) const override { + auto globalFeatures = GlobalIndexUsageTracker::get(opCtx->getServiceContext()); + + BSONObjBuilder builder; + builder.append("count", globalFeatures->getCount()); + + BSONObjBuilder featuresBuilder = builder.subobjStart("features"); + globalFeatures->forEachFeature( + [&featuresBuilder](const std::string& feature, const IndexFeatureStats& stats) { + BSONObjBuilder featureBuilder = featuresBuilder.subobjStart(feature); + featureBuilder.append("count", stats.count.load()); + featureBuilder.append("accesses", stats.accesses.load()); + featureBuilder.done(); + }); + featuresBuilder.done(); + return builder.obj(); + } +} indexStatsSSS; +} // namespace mongo diff --git a/src/mongo/db/global_index_usage_tracker.h b/src/mongo/db/global_index_usage_tracker.h new file mode 100644 index 00000000000..c4711667787 --- /dev/null +++ b/src/mongo/db/global_index_usage_tracker.h @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <map> + +#include "mongo/db/index_names.h" +#include "mongo/platform/atomic_word.h" + +namespace mongo { +class IndexDescriptor; +class ServiceContext; + +/** + * IndexFeatures describes an anonymized set of features about a single index. For example, an index + * can be both compound and unique, and this set of flags would be used to track that information so + * that we can provide aggregated details in the GlobalIndexUsageTracker. + */ +struct IndexFeatures { + /** + * Create an IndexFeatures structure. If 'internal' is true, the statistics for this index and + * its features should not be tracked and aggregated by the GlobalIndexUsageTracker. + */ + static IndexFeatures make(const IndexDescriptor* desc, bool internal); + + IndexType type; + bool collation = false; + bool compound = false; + bool id = false; + bool internal = false; + bool partial = false; + bool regular = false; + bool sparse = false; + bool ttl = false; + bool unique = false; +}; + +/** + * IndexFeatureStats holds statistics about a specific index feature. Its data members are mutable + * atomics to allow itself to be used in a const map safely. + */ +struct IndexFeatureStats { + // Number of indexes that have this feature. + mutable AtomicWord<long long> count{0}; + // Number of operations that have used indexes with this feature. + mutable AtomicWord<long long> accesses{0}; +}; + +/** + * GlobalIndexUsageTracker aggregates usage metrics about features used by indexes. Ignores indexes + * on internal databases. + */ +class GlobalIndexUsageTracker { +public: + static GlobalIndexUsageTracker* get(ServiceContext* svcCtx); + + GlobalIndexUsageTracker(); + + /** + * Updates counters for features used by an index when the index has been accessed. + */ + void onAccess(const IndexFeatures& features) const; + + /** + * Updates counters for indexes using certain features when the index has been created. + */ + void onRegister(const IndexFeatures& features) const; + + /** + * Updates counters for indexes using certain features when the index has been removed. + */ + void onUnregister(const IndexFeatures& features) const; + + /** + * Iterates through each feature being tracked with a call back to OnFeatureFn, which provides + * the string descriptor of the feature and its stats. + */ + using OnFeatureFn = + std::function<void(const std::string& feature, const IndexFeatureStats& stats)>; + void forEachFeature(OnFeatureFn&& onFeature) const; + + /** + * Returns the total number of indexes being tracked. + */ + long long getCount() const; + +private: + /** + * Internal helper to update the global stats for each index feature in 'features'. Call back + * into UpdateFn which is responsible for update the relevant statistic for each of the features + * in use. + */ + using UpdateFn = std::function<void(const IndexFeatureStats* stats)>; + void _updateStatsForEachFeature(const IndexFeatures& features, UpdateFn&& onFeature) const; + + // This maps index feature description strings (e.g. 'compound', 'unique') to the actual global + // metrics counters for those features. It is intentionally ordered to have consistent + // alphabetical ordering when displayed. This map is const because we know all of its entries + // ahead of time and we can avoid the extra cost of additional concurrency control. + const std::map<std::string, IndexFeatureStats> _indexFeatureToStats; + + // Total number of indexes being tracked. + mutable AtomicWord<long long> _count; +}; +} // namespace mongo diff --git a/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp b/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp index 504be0ef934..59ae9bd1c1d 100644 --- a/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp +++ b/src/mongo/db/query/collection_index_usage_tracker_decoration.cpp @@ -51,6 +51,7 @@ CollectionIndexUsageTracker& CollectionIndexUsageTrackerDecoration::get( } CollectionIndexUsageTrackerDecoration::CollectionIndexUsageTrackerDecoration() - : _indexUsageTracker(getGlobalServiceContext()->getPreciseClockSource()) {} + : _indexUsageTracker(GlobalIndexUsageTracker::get(getGlobalServiceContext()), + getGlobalServiceContext()->getPreciseClockSource()) {} } // namespace mongo diff --git a/src/mongo/db/query/collection_query_info.cpp b/src/mongo/db/query/collection_query_info.cpp index e66c23bad92..1a87201bae3 100644 --- a/src/mongo/db/query/collection_query_info.cpp +++ b/src/mongo/db/query/collection_query_info.cpp @@ -234,7 +234,9 @@ void CollectionQueryInfo::init(OperationContext* opCtx, const CollectionPtr& col while (ii->more()) { const IndexDescriptor* desc = ii->next()->descriptor(); CollectionIndexUsageTrackerDecoration::get(coll->getSharedDecorations()) - .registerIndex(desc->indexName(), desc->keyPattern()); + .registerIndex(desc->indexName(), + desc->keyPattern(), + IndexFeatures::make(desc, coll->ns().isOnInternalDb())); } rebuildIndexData(opCtx, coll); |