From cf72acd7f83576a12abf2928ca53699fdb341e25 Mon Sep 17 00:00:00 2001 From: Benety Goh Date: Sun, 28 Aug 2022 08:31:07 -0400 Subject: SERVER-68477 listIndexes repairs TTL indexes with NaN expireAfterSeconds (cherry picked from commit d6528bf96f08b79ca850902b2d1d81264fa7baa1) --- jstests/noPassthrough/ttl_expire_nan.js | 24 ++++++++++++++++++++++++ src/mongo/db/catalog/index_key_validate.cpp | 16 +++++++++++++--- src/mongo/db/catalog/index_key_validate.h | 6 ++++++ src/mongo/db/catalog/index_key_validate_test.cpp | 14 ++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/jstests/noPassthrough/ttl_expire_nan.js b/jstests/noPassthrough/ttl_expire_nan.js index 5d26b214487..6b8ff73d015 100644 --- a/jstests/noPassthrough/ttl_expire_nan.js +++ b/jstests/noPassthrough/ttl_expire_nan.js @@ -11,9 +11,13 @@ (function() { 'use strict'; +load('jstests/noPassthrough/libs/index_build.js'); + const rst = new ReplSetTest({ nodes: [{}, {rsConfig: {votes: 0, priority: 0}}], nodeOptions: {setParameter: {ttlMonitorSleepSecs: 5}}, + // Sync from primary only so that we have a well-defined node to check listIndexes behavior. + settings: {chainingAllowed: false}, }); rst.startSet(); rst.initiate(); @@ -41,5 +45,25 @@ checkLog.containsJson(secondary, 20384, { assert.eq( coll.countDocuments({}), 1, 'ttl index with NaN duration should not remove any documents.'); +// Confirm that TTL index is replicated with a non-zero 'expireAfterSeconds' during initial sync. +const newNode = rst.add({rsConfig: {votes: 0, priority: 0}}); +rst.reInitiate(); +rst.waitForState(newNode, ReplSetTest.State.SECONDARY); +rst.awaitReplication(); +let newNodeTestDB = newNode.getDB(db.getName()); +let newNodeColl = newNodeTestDB.getCollection(coll.getName()); +const newNodeIndexes = IndexBuildTest.assertIndexes(newNodeColl, 2, ['_id_', 't_1']); +const newNodeSpec = newNodeIndexes.t_1; +jsTestLog('TTL index on initial sync node: ' + tojson(newNodeSpec)); +assert(newNodeSpec.hasOwnProperty('expireAfterSeconds'), + 'Index was not replicated as a TTL index during initial sync.'); +assert.gt(newNodeSpec.expireAfterSeconds, + 0, + 'NaN expireAferSeconds was replicated as zero during initial sync.'); + +// Check that listIndexes on the primary logged a "Fixing expire field from TTL index spec" message +// during the NaN 'expireAfterSeconds' conversion. +checkLog.containsJson(primary, 6835900, {namespace: coll.getFullName()}); + rst.stopSet(); })(); diff --git a/src/mongo/db/catalog/index_key_validate.cpp b/src/mongo/db/catalog/index_key_validate.cpp index 3fc2aa28304..6e7c5faeb09 100644 --- a/src/mongo/db/catalog/index_key_validate.cpp +++ b/src/mongo/db/catalog/index_key_validate.cpp @@ -269,8 +269,8 @@ BSONObj removeUnknownFields(const NamespaceString& ns, const BSONObj& indexSpec) BSONObj repairIndexSpec(const NamespaceString& ns, const BSONObj& indexSpec, const std::set& allowedFieldNames) { - auto fixBoolIndexSpecFn = [&indexSpec, &ns](const BSONElement& indexSpecElem, - BSONObjBuilder* builder) { + auto fixIndexSpecFn = [&indexSpec, &ns](const BSONElement& indexSpecElem, + BSONObjBuilder* builder) { StringData fieldName = indexSpecElem.fieldNameStringData(); if ((IndexDescriptor::kBackgroundFieldName == fieldName || IndexDescriptor::kUniqueFieldName == fieldName || @@ -285,11 +285,21 @@ BSONObj repairIndexSpec(const NamespaceString& ns, "fieldName"_attr = redact(fieldName), "indexSpec"_attr = redact(indexSpec)); builder->appendBool(fieldName, true); + } else if (IndexDescriptor::kExpireAfterSecondsFieldName == fieldName && + !(indexSpecElem.isNumber() && !indexSpecElem.isNaN())) { + LOGV2_WARNING(6835900, + "Fixing expire field from TTL index spec", + "namespace"_attr = redact(ns.toString()), + "fieldName"_attr = redact(fieldName), + "indexSpec"_attr = redact(indexSpec)); + builder->appendNumber(fieldName, + durationCount(kExpireAfterSecondsForInactiveTTLIndex)); } else { builder->append(indexSpecElem); } }; - return buildRepairedIndexSpec(ns, indexSpec, allowedFieldNames, fixBoolIndexSpecFn); + + return buildRepairedIndexSpec(ns, indexSpec, allowedFieldNames, fixIndexSpecFn); } StatusWith validateIndexSpec(OperationContext* opCtx, const BSONObj& indexSpec) { diff --git a/src/mongo/db/catalog/index_key_validate.h b/src/mongo/db/catalog/index_key_validate.h index f9680ac035b..2ba7174c1b0 100644 --- a/src/mongo/db/catalog/index_key_validate.h +++ b/src/mongo/db/catalog/index_key_validate.h @@ -43,6 +43,12 @@ class StatusWith; namespace index_key_validate { +// TTL indexes with 'expireAfterSeconds' are repaired with this duration, which is chosen to be +// the largest possible value for the 'safeInt' type that can be returned in the listIndexes +// response. +constexpr auto kExpireAfterSecondsForInactiveTTLIndex = + Seconds(std::numeric_limits::max()); + static std::set allowedFieldNames = { IndexDescriptor::k2dIndexBitsFieldName, IndexDescriptor::k2dIndexMaxFieldName, diff --git a/src/mongo/db/catalog/index_key_validate_test.cpp b/src/mongo/db/catalog/index_key_validate_test.cpp index 9c4813a2d2e..c60df1b34aa 100644 --- a/src/mongo/db/catalog/index_key_validate_test.cpp +++ b/src/mongo/db/catalog/index_key_validate_test.cpp @@ -426,6 +426,20 @@ TEST(IndexKeyValidateTest, RepairIndexSpecs) { NamespaceString("coll"), fromjson("{key: {a: 1}, name: 'index', sparse: 'true', background: '1', safe: " "true, force: true}")))); + + ASSERT(BSON("key" << BSON("a" << 1) << "name" + << "index" + << "expireAfterSeconds" << std::numeric_limits::max()) + .binaryEqual(index_key_validate::repairIndexSpec( + NamespaceString("coll"), + fromjson("{key: {a: 1}, name: 'index', expireAfterSeconds: NaN}")))); + + ASSERT(BSON("key" << BSON("a" << 1) << "name" + << "index" + << "expireAfterSeconds" << std::numeric_limits::max()) + .binaryEqual(index_key_validate::repairIndexSpec( + NamespaceString("coll"), + fromjson("{key: {a: 1}, name: 'index', expireAfterSeconds: '123'}")))); } TEST(IndexKeyValidateTest, GeoIndexSpecs) { -- cgit v1.2.1