diff options
Diffstat (limited to 'src/mongo/db/ttl.cpp')
| -rw-r--r-- | src/mongo/db/ttl.cpp | 157 |
1 files changed, 141 insertions, 16 deletions
diff --git a/src/mongo/db/ttl.cpp b/src/mongo/db/ttl.cpp index ee9b801882a..b0774169f42 100644 --- a/src/mongo/db/ttl.cpp +++ b/src/mongo/db/ttl.cpp @@ -36,10 +36,12 @@ #include "mongo/base/counter.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/user_name.h" +#include "mongo/db/catalog/coll_mod.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/collection_catalog.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/catalog/index_key_validate.h" #include "mongo/db/client.h" #include "mongo/db/commands/fsync_locked.h" #include "mongo/db/commands/server_status_metric.h" @@ -51,6 +53,7 @@ #include "mongo/db/ops/insert.h" #include "mongo/db/query/internal_plans.h" #include "mongo/db/record_id_helpers.h" +#include "mongo/db/repl/replica_set_aware_service.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/repl/tenant_migration_access_blocker_registry.h" #include "mongo/db/s/operation_sharding_state.h" @@ -175,6 +178,11 @@ public: LOGV2(3684101, "Finished shutting down TTL collection monitor thread"); } + /** + * Invoked when the node enters the primary state. + */ + void onStepUp(OperationContext* opCtx); + private: /** * Gets all TTL specifications for every collection and deletes expired documents. @@ -211,7 +219,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; } @@ -328,15 +340,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()); + } } /** @@ -369,13 +377,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; } @@ -413,9 +421,12 @@ private: } BSONElement secondsExpireElt = spec[IndexDescriptor::kExpireAfterSecondsFieldName]; - if (!secondsExpireElt.isNumber()) { + if (!secondsExpireElt.isNumber() || secondsExpireElt.isNaN()) { LOGV2_ERROR(22542, - "TTL indexes require the expire field to be numeric, skipping TTL job", + "TTL indexes require the expire field to be numeric and not a NaN, " + "skipping TTL job", + "ns"_attr = collection->ns(), + "uuid"_attr = collection->uuid(), "field"_attr = IndexDescriptor::kExpireAfterSecondsFieldName, "type"_attr = typeName(secondsExpireElt.type()), "index"_attr = spec); @@ -525,8 +536,7 @@ private: auto expireAfterSeconds = collOptions.expireAfterSeconds; if (!expireAfterSeconds) { - ttlCollectionCache->deregisterTTLInfo(collection->uuid(), - TTLCollectionCache::ClusteredId{}); + ttlCollectionCache->deregisterTTLClusteredIndex(collection->uuid()); return; } @@ -601,4 +611,119 @@ void shutdownTTLMonitor(ServiceContext* serviceContext) { } } +void TTLMonitor::onStepUp(OperationContext* opCtx) { + auto&& ttlCollectionCache = TTLCollectionCache::get(opCtx->getServiceContext()); + auto ttlInfos = ttlCollectionCache.getTTLInfos(); + for (const auto& [uuid, infos] : ttlInfos) { + auto collectionCatalog = CollectionCatalog::get(opCtx); + if (collectionCatalog->isCollectionAwaitingVisibility(uuid)) { + continue; + } + + // The collection was dropped. + auto nss = collectionCatalog->lookupNSSByUUID(opCtx, uuid); + if (!nss) { + continue; + } + + if (nss->isTemporaryReshardingCollection() || nss->isDropPendingNamespace()) { + continue; + } + + try { + uassertStatusOK(userAllowedWriteNS(opCtx, *nss)); + + for (const auto& info : infos) { + // Skip clustered indexes with TTL. This includes time-series collections. + if (info.isClustered()) { + continue; + } + if (!info.isExpireAfterSecondsNaN()) { + continue; + } + + auto indexName = info.getIndexName(); + LOGV2(6847700, + "Running collMod to fix TTL index with NaN 'expireAfterSeconds'.", + "ns"_attr = *nss, + "uuid"_attr = uuid, + "name"_attr = indexName, + "expireAfterSecondsNew"_attr = + index_key_validate::kExpireAfterSecondsForInactiveTTLIndex); + + // Compose collMod command to amend 'expireAfterSeconds' to same value that + // would be used by listIndexes() to convert the NaN value in the catalog. + CollModIndex collModIndex; + collModIndex.setName(StringData{indexName}); + collModIndex.setExpireAfterSeconds(mongo::durationCount<Seconds>( + index_key_validate::kExpireAfterSecondsForInactiveTTLIndex)); + CollMod collModCmd{*nss}; + collModCmd.getCollModRequest().setIndex(collModIndex); + + // processCollModCommand() will acquire MODE_X access to the collection. + BSONObjBuilder builder; + uassertStatusOK(processCollModCommand( + opCtx, {nss->db().toString(), uuid}, collModCmd, &builder)); + auto result = builder.obj(); + LOGV2(6847701, + "Successfully fixed TTL index with NaN 'expireAfterSeconds' using collMod", + "ns"_attr = *nss, + "uuid"_attr = uuid, + "name"_attr = indexName, + "result"_attr = result); + } + } catch (const ExceptionForCat<ErrorCategory::Interruption>&) { + // The exception is relevant to the entire TTL monitoring process, not just the specific + // TTL index. Let the exception escape so it can be addressed at the higher monitoring + // layer. + throw; + } catch (const DBException& ex) { + LOGV2_ERROR(6835901, + "Error checking TTL job on collection during step up", + logAttrs(*nss), + "error"_attr = ex); + continue; + } + } +} + +namespace { + +/** + * Runs on primaries and secondaries. Forwards replica set events to the TTLMonitor. + */ +class TTLMonitorService : public ReplicaSetAwareService<TTLMonitorService> { +public: + static TTLMonitorService* get(ServiceContext* serviceContext); + TTLMonitorService() = default; + +private: + void onStartup(OperationContext* opCtx) override {} + void onInitialDataAvailable(OperationContext* opCtx, bool isMajorityDataAvailable) override {} + void onShutdown() override {} + void onStepUpBegin(OperationContext* opCtx, long long term) override {} + void onStepUpComplete(OperationContext* opCtx, long long term) override { + auto ttlMonitor = TTLMonitor::get(opCtx->getServiceContext()); + if (!ttlMonitor) { + // Some test fixtures might not install the TTLMonitor. + return; + } + ttlMonitor->onStepUp(opCtx); + } + void onStepDown() override {} + void onBecomeArbiter() override {} +}; + +const auto _ttlMonitorService = ServiceContext::declareDecoration<TTLMonitorService>(); + +const ReplicaSetAwareServiceRegistry::Registerer<TTLMonitorService> _ttlMonitorServiceRegisterer( + "TTLMonitorService"); + +// static +TTLMonitorService* TTLMonitorService::get(ServiceContext* serviceContext) { + return &_ttlMonitorService(serviceContext); +} + +} // namespace + } // namespace mongo |
