diff options
author | Benety Goh <benety@mongodb.com> | 2022-08-26 08:08:11 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-10-10 20:47:25 +0000 |
commit | ba31c2d4c78fe0f0261b0b82c259e511dce9447c (patch) | |
tree | fa302c9500d28a00638af27db6590e5202dca3b4 | |
parent | fbb8779057e34e3a696134f2ff3960f5328c579a (diff) | |
download | mongo-ba31c2d4c78fe0f0261b0b82c259e511dce9447c.tar.gz |
SERVER-68477 include 'expireAfterSeconds' type information when registering TTL indexes with the TTLCollectionCache
(cherry picked from commit cc3ae631bce7943fbda5182ff3b9d93d1125be40)
-rw-r--r-- | src/mongo/db/catalog/coll_mod.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_build_block.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog_impl.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/ttl.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/ttl_collection_cache.cpp | 46 | ||||
-rw-r--r-- | src/mongo/db/ttl_collection_cache.h | 40 | ||||
-rw-r--r-- | src/mongo/db/ttl_collection_cache_test.cpp | 35 |
8 files changed, 162 insertions, 42 deletions
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index f2ba54f7999..a2b4505a04f 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -430,7 +430,8 @@ void _setClusteredExpireAfterSeconds(OperationContext* opCtx, if (!oldExpireAfterSeconds) { auto ttlCache = &TTLCollectionCache::get(opCtx->getServiceContext()); opCtx->recoveryUnit()->onCommit([ttlCache, uuid = coll->uuid()](auto _) { - ttlCache->registerTTLInfo(uuid, TTLCollectionCache::ClusteredId()); + ttlCache->registerTTLInfo(uuid, + TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}); }); } @@ -577,12 +578,39 @@ Status _collModInternal(OperationContext* opCtx, // If this collection was not previously TTL, inform the TTL monitor when we commit. if (oldExpireSecs.eoo()) { auto ttlCache = &TTLCollectionCache::get(opCtx->getServiceContext()); - opCtx->recoveryUnit()->onCommit([ttlCache, uuid = coll->uuid(), &idx](auto _) { - ttlCache->registerTTLInfo(uuid, idx->indexName()); - }); + opCtx->recoveryUnit()->onCommit( + [ttlCache, uuid = coll->uuid(), indexName = idx->indexName()](auto _) { + ttlCache->registerTTLInfo( + uuid, + TTLCollectionCache::Info{indexName, + /*isExpireAfterSecondsNaN=*/false}); + }); } - if (SimpleBSONElementComparator::kInstance.evaluate(oldExpireSecs != - newExpireSecs)) { + + // If the current `expireAfterSeconds` is NaN, it can never be equal to + // 'indexExpireAfterSeconds'. + if (oldExpireSecs.isNaN()) { + // Setting *oldExpireSecs is mostly for informational purposes. + // In 5.0, we leave `oldExpireSecs` unchanged. + // Unlike 6.0+ where 'oldExpireSecs' is declared as an optional long long, + // 'oldExpireSecs' is a BSONElement. It should be fine to keep `oldExpireSecs` + // as NaN in the command result. For the oplog entry, we already use + // BSONElement::safeNumberLong() to coerce the NaN into zero. See the + // IndexCollModInfo struct. + + // Change the value of "expireAfterSeconds" on disk. + coll.getWritableCollection()->updateTTLSetting( + opCtx, idx->indexName(), newExpireSecs.safeNumberLong()); + + // Keep the TTL information maintained by the TTLCollectionCache in sync so that + // we don't try to fix up the TTL index during the next step-up. + auto ttlCache = &TTLCollectionCache::get(opCtx->getServiceContext()); + opCtx->recoveryUnit()->onCommit( + [ttlCache, uuid = coll->uuid(), indexName = idx->indexName()](auto _) { + ttlCache->unsetTTLIndexExpireAfterSecondsNaN(uuid, indexName); + }); + } else if (SimpleBSONElementComparator::kInstance.evaluate(oldExpireSecs != + newExpireSecs)) { // Change the value of "expireAfterSeconds" on disk. coll.getWritableCollection()->updateTTLSetting( opCtx, idx->indexName(), newExpireSecs.safeNumberLong()); diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp index 01e0282d150..f99cb4019b7 100644 --- a/src/mongo/db/catalog/collection_impl.cpp +++ b/src/mongo/db/catalog/collection_impl.cpp @@ -426,11 +426,11 @@ void CollectionImpl::init(OperationContext* opCtx) { if (opCtx->lockState()->inAWriteUnitOfWork()) { opCtx->recoveryUnit()->onCommit([svcCtx, uuid](auto ts) { TTLCollectionCache::get(svcCtx).registerTTLInfo( - uuid, TTLCollectionCache::ClusteredId{}); + uuid, TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}); }); } else { - TTLCollectionCache::get(svcCtx).registerTTLInfo(uuid, - TTLCollectionCache::ClusteredId{}); + TTLCollectionCache::get(svcCtx).registerTTLInfo( + uuid, TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}); } } } diff --git a/src/mongo/db/catalog/index_build_block.cpp b/src/mongo/db/catalog/index_build_block.cpp index ac29eccbc29..d5b410ee03b 100644 --- a/src/mongo/db/catalog/index_build_block.cpp +++ b/src/mongo/db/catalog/index_build_block.cpp @@ -268,7 +268,10 @@ void IndexBuildBlock::success(OperationContext* opCtx, Collection* collection) { // Add the index to the TTLCollectionCache upon successfully committing the index build. // TTL indexes are not compatible with capped collections. if (spec.hasField(IndexDescriptor::kExpireAfterSecondsFieldName) && !coll->isCapped()) { - TTLCollectionCache::get(svcCtx).registerTTLInfo(coll->uuid(), indexName); + TTLCollectionCache::get(svcCtx).registerTTLInfo( + coll->uuid(), + TTLCollectionCache::Info{ + indexName, spec[IndexDescriptor::kExpireAfterSecondsFieldName].isNaN()}); } }); } diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp index 3fccb483ca3..0879f90b354 100644 --- a/src/mongo/db/catalog/index_catalog_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_impl.cpp @@ -159,7 +159,10 @@ Status IndexCatalogImpl::init(OperationContext* opCtx, Collection* collection) { if (spec.hasField(IndexDescriptor::kExpireAfterSecondsFieldName) && !collection->isCapped()) { TTLCollectionCache::get(opCtx->getServiceContext()) - .registerTTLInfo(collection->uuid(), indexName); + .registerTTLInfo( + collection->uuid(), + TTLCollectionCache::Info{ + indexName, spec[IndexDescriptor::kExpireAfterSecondsFieldName].isNaN()}); } bool ready = collection->isIndexReady(indexName); diff --git a/src/mongo/db/ttl.cpp b/src/mongo/db/ttl.cpp index 24d321b7a5f..241c9b5002e 100644 --- a/src/mongo/db/ttl.cpp +++ b/src/mongo/db/ttl.cpp @@ -205,7 +205,11 @@ private: // The collection was dropped. auto nss = collectionCatalog->lookupNSSByUUID(opCtx, uuid); if (!nss) { - ttlCollectionCache.deregisterTTLInfo(uuid, info); + if (info.isClustered()) { + ttlCollectionCache.deregisterTTLClusteredIndex(uuid); + } else { + ttlCollectionCache.deregisterTTLIndexByName(uuid, info.getIndexName()); + } continue; } @@ -285,15 +289,11 @@ private: ResourceConsumption::ScopedMetricsCollector scopedMetrics(opCtx, nss.db().toString()); const auto& collection = coll.getCollection(); - stdx::visit( - visit_helper::Overloaded{ - [&](const TTLCollectionCache::ClusteredId&) { - deleteExpiredWithCollscan(opCtx, ttlCollectionCache, collection); - }, - [&](const TTLCollectionCache::IndexName& indexName) { - deleteExpiredWithIndex(opCtx, ttlCollectionCache, collection, indexName); - }}, - info); + if (info.isClustered()) { + deleteExpiredWithCollscan(opCtx, ttlCollectionCache, collection); + } else { + deleteExpiredWithIndex(opCtx, ttlCollectionCache, collection, info.getIndexName()); + } } /** @@ -326,13 +326,13 @@ private: const CollectionPtr& collection, std::string indexName) { if (!collection->isIndexPresent(indexName)) { - ttlCollectionCache->deregisterTTLInfo(collection->uuid(), indexName); + ttlCollectionCache->deregisterTTLIndexByName(collection->uuid(), indexName); return; } BSONObj spec = collection->getIndexSpec(indexName); if (!spec.hasField(IndexDescriptor::kExpireAfterSecondsFieldName)) { - ttlCollectionCache->deregisterTTLInfo(collection->uuid(), indexName); + ttlCollectionCache->deregisterTTLIndexByName(collection->uuid(), indexName); return; } @@ -455,8 +455,7 @@ private: auto expireAfterSeconds = collOptions.expireAfterSeconds; if (!expireAfterSeconds) { - ttlCollectionCache->deregisterTTLInfo(collection->uuid(), - TTLCollectionCache::ClusteredId{}); + ttlCollectionCache->deregisterTTLClusteredIndex(collection->uuid()); return; } diff --git a/src/mongo/db/ttl_collection_cache.cpp b/src/mongo/db/ttl_collection_cache.cpp index 27d1335510c..86fcc682068 100644 --- a/src/mongo/db/ttl_collection_cache.cpp +++ b/src/mongo/db/ttl_collection_cache.cpp @@ -63,13 +63,30 @@ void TTLCollectionCache::registerTTLInfo(UUID uuid, const Info& info) { } } -void TTLCollectionCache::deregisterTTLInfo(UUID uuid, const Info& info) { +void TTLCollectionCache::_deregisterTTLInfo(UUID uuid, const Info& info) { stdx::lock_guard<Latch> lock(_ttlInfosLock); auto infoIt = _ttlInfos.find(uuid); fassert(5400705, infoIt != _ttlInfos.end()); auto& [_, infoVec] = *infoIt; - auto iter = std::find(infoVec.begin(), infoVec.end(), info); + auto iter = infoVec.begin(); + if (info.isClustered()) { + // For clustered collections, we cannot have more than one clustered info per UUID. + // All we have to do here is ensure that the 'info' to search for is also 'clustered'. + iter = std::find_if(infoVec.begin(), infoVec.end(), [](const auto& infoVecItem) { + return infoVecItem.isClustered(); + }); + } else { + // For TTL indexes, we search non-clustered TTL info items on the index name only. + auto indexName = info.getIndexName(); + iter = std::find_if(infoVec.begin(), infoVec.end(), [&indexName](const auto& infoVecItem) { + if (infoVecItem.isClustered()) { + return false; + } + return indexName == infoVecItem.getIndexName(); + }); + } + fassert(40220, iter != infoVec.end()); infoVec.erase(iter); if (infoVec.empty()) { @@ -77,6 +94,31 @@ void TTLCollectionCache::deregisterTTLInfo(UUID uuid, const Info& info) { } } +void TTLCollectionCache::deregisterTTLIndexByName(UUID uuid, const IndexName& indexName) { + _deregisterTTLInfo(std::move(uuid), TTLCollectionCache::Info{indexName, /*unusedSpec=*/{}}); +} + +void TTLCollectionCache::deregisterTTLClusteredIndex(UUID uuid) { + _deregisterTTLInfo(std::move(uuid), + TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}); +} + +void TTLCollectionCache::unsetTTLIndexExpireAfterSecondsNaN(UUID uuid, const IndexName& indexName) { + stdx::lock_guard<Latch> lock(_ttlInfosLock); + auto infoIt = _ttlInfos.find(uuid); + if (infoIt == _ttlInfos.end()) { + return; + } + + auto&& infoVec = infoIt->second; + for (auto&& info : infoVec) { + if (!info.isClustered() && info.getIndexName() == indexName) { + info.unsetExpireAfterSecondsNaN(); + break; + } + } +} + TTLCollectionCache::InfoMap TTLCollectionCache::getTTLInfos() { stdx::lock_guard<Latch> lock(_ttlInfosLock); return _ttlInfos; diff --git a/src/mongo/db/ttl_collection_cache.h b/src/mongo/db/ttl_collection_cache.h index 053b9fc48a2..b343c6f627d 100644 --- a/src/mongo/db/ttl_collection_cache.h +++ b/src/mongo/db/ttl_collection_cache.h @@ -54,16 +54,52 @@ public: using IndexName = std::string; // Specifies how a collection should expire data with TTL. - using Info = stdx::variant<ClusteredId, IndexName>; + class Info { + public: + explicit Info(ClusteredId) : _isClustered(true), _isExpireAfterSecondsNaN(false) {} + Info(IndexName indexName, bool isExpireAfterSecondsNaN) + : _isClustered(false), + _indexName(std::move(indexName)), + _isExpireAfterSecondsNaN(isExpireAfterSecondsNaN) {} + bool isClustered() const { + return _isClustered; + } + IndexName getIndexName() const { + return _indexName; + } + bool isExpireAfterSecondsNaN() const { + return _isExpireAfterSecondsNaN; + } + void unsetExpireAfterSecondsNaN() { + _isExpireAfterSecondsNaN = false; + } + + private: + bool _isClustered; + IndexName _indexName; + bool _isExpireAfterSecondsNaN; + }; // Caller is responsible for ensuring no duplicates are registered. void registerTTLInfo(UUID uuid, const Info& info); - void deregisterTTLInfo(UUID uuid, const Info& info); + void deregisterTTLIndexByName(UUID uuid, const IndexName& indexName); + void deregisterTTLClusteredIndex(UUID uuid); + + /** + * Resets expireAfterSeconds flag on TTL index. + * For idempotency, this has no effect if index is not found. + */ + void unsetTTLIndexExpireAfterSecondsNaN(UUID uuid, const IndexName& indexName); using InfoMap = stdx::unordered_map<UUID, std::vector<Info>, UUID::Hash>; InfoMap getTTLInfos(); private: + /** + * Shared implementation for deregistering TTL infos. + */ + void _deregisterTTLInfo(UUID uuid, const Info& info); + Mutex _ttlInfosLock = MONGO_MAKE_LATCH("TTLCollectionCache::_ttlInfosLock"); InfoMap _ttlInfos; }; diff --git a/src/mongo/db/ttl_collection_cache_test.cpp b/src/mongo/db/ttl_collection_cache_test.cpp index 4f598b3736c..747d1371887 100644 --- a/src/mongo/db/ttl_collection_cache_test.cpp +++ b/src/mongo/db/ttl_collection_cache_test.cpp @@ -39,9 +39,9 @@ TEST(TTLCollectionCacheTest, Basic) { auto uuidCollA = UUID::gen(); auto uuidCollB = UUID::gen(); - auto infoIndexA1 = TTLCollectionCache::Info{"collA_ttl_1"}; + auto infoIndexA1 = TTLCollectionCache::Info{"collA_ttl_1", /*isExpireAfterSecondsNaN=*/false}; auto infoClusteredA = TTLCollectionCache::Info{TTLCollectionCache::ClusteredId{}}; - auto infoIndexB1 = TTLCollectionCache::Info("collB_ttl_1"); + auto infoIndexB1 = TTLCollectionCache::Info("collB_ttl_1", /*isExpireAfterSecondsNaN=*/true); // Confirm registerTTLInfo() behavior using getTTLInfo(). cache.registerTTLInfo(uuidCollA, infoIndexA1); @@ -52,36 +52,45 @@ TEST(TTLCollectionCacheTest, Basic) { ASSERT_EQ(infos.size(), 2U); ASSERT_EQ(infos.count(uuidCollA), 1U); ASSERT_EQ(infos[uuidCollA].size(), 2U); - auto indexNameA = stdx::get_if<TTLCollectionCache::IndexName>(&infos[uuidCollA][0]); - ASSERT(indexNameA); - ASSERT_EQ(*indexNameA, "collA_ttl_1"); - ASSERT(stdx::get_if<TTLCollectionCache::ClusteredId>(&infos[uuidCollA][1])); + ASSERT_FALSE(infos[uuidCollA][0].isClustered()); + ASSERT_EQ(infos[uuidCollA][0].getIndexName(), "collA_ttl_1"); + ASSERT_FALSE(infos[uuidCollA][0].isExpireAfterSecondsNaN()); + ASSERT(infos[uuidCollA][1].isClustered()); ASSERT_EQ(infos.count(uuidCollB), 1U); ASSERT_EQ(infos[uuidCollB].size(), 1U); - auto indexNameB = stdx::get_if<TTLCollectionCache::IndexName>(&infos[uuidCollB][0]); - ASSERT(indexNameB); - ASSERT_EQ(*indexNameB, "collB_ttl_1"); + ASSERT_FALSE(infos[uuidCollB][0].isClustered()); + ASSERT_EQ(infos[uuidCollB][0].getIndexName(), "collB_ttl_1"); + ASSERT(infos[uuidCollB][0].isExpireAfterSecondsNaN()); + + // Check that we can reset '_isExpireAfterSecondsNaN()' on the TTL index. + cache.unsetTTLIndexExpireAfterSecondsNaN(uuidCollB, infoIndexB1.getIndexName()); + infos = cache.getTTLInfos(); + ASSERT_EQ(infos.size(), 2U); + ASSERT_EQ(infos.count(uuidCollB), 1U); + ASSERT_EQ(infos[uuidCollB].size(), 1U); + ASSERT_EQ(infos[uuidCollB][0].getIndexName(), "collB_ttl_1"); + ASSERT_FALSE(infos[uuidCollB][0].isExpireAfterSecondsNaN()); // Check deregisterTTLInfo(). TTLCollectionCache should clean up // UUIDs that no longer have any TTL infos registered. - cache.deregisterTTLInfo(uuidCollB, infoIndexB1); + cache.deregisterTTLIndexByName(uuidCollB, infoIndexB1.getIndexName()); infos = cache.getTTLInfos(); ASSERT_EQ(infos.size(), 1U); ASSERT_EQ(infos.count(uuidCollA), 1U); ASSERT_EQ(infos[uuidCollA].size(), 2U); // Remove info for TTL index on collection A. - cache.deregisterTTLInfo(uuidCollA, infoIndexA1); + cache.deregisterTTLIndexByName(uuidCollA, infoIndexA1.getIndexName()); infos = cache.getTTLInfos(); ASSERT_EQ(infos.size(), 1U); ASSERT_EQ(infos.count(uuidCollA), 1U); ASSERT_EQ(infos[uuidCollA].size(), 1U); - ASSERT(stdx::get_if<TTLCollectionCache::ClusteredId>(&infos[uuidCollA][0])); + ASSERT(infos[uuidCollA][0].isClustered()); // Remove clustered info for collection A. - cache.deregisterTTLInfo(uuidCollA, infoClusteredA); + cache.deregisterTTLClusteredIndex(uuidCollA); infos = cache.getTTLInfos(); ASSERT_EQ(infos.size(), 0U); } |