summaryrefslogtreecommitdiff
path: root/src/mongo/db/ttl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ttl.cpp')
-rw-r--r--src/mongo/db/ttl.cpp157
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