diff options
author | Benety Goh <benety@mongodb.com> | 2022-08-28 20:02:36 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-09-11 18:36:57 +0000 |
commit | 17d15eb7f3d8b044456ea9191d5777af3fbc5651 (patch) | |
tree | 47818747ccb1bb1952c5ca96a9392d80d7064f5e | |
parent | 6a1b08c8108635be9a7624243ca9f4ff191fd48c (diff) | |
download | mongo-17d15eb7f3d8b044456ea9191d5777af3fbc5651.tar.gz |
SERVER-68477 remove epoch restriction on ttl indexes
(cherry picked from commit eb2f7f03f8c0522f85a9cae2c61bec4673251103)
-rw-r--r-- | jstests/core/collmod_convert_to_ttl.js | 7 | ||||
-rw-r--r-- | jstests/core/ttl_index_options.js | 12 | ||||
-rw-r--r-- | src/mongo/db/catalog/coll_mod.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/catalog/create_collection.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_key_validate.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_key_validate.h | 9 |
6 files changed, 55 insertions, 31 deletions
diff --git a/jstests/core/collmod_convert_to_ttl.js b/jstests/core/collmod_convert_to_ttl.js index ab617d3eeb7..c4c8b7d2bbd 100644 --- a/jstests/core/collmod_convert_to_ttl.js +++ b/jstests/core/collmod_convert_to_ttl.js @@ -39,13 +39,6 @@ assert.commandFailedWithCode( db.runCommand({"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": -1}}), ErrorCodes.InvalidOptions); -// Tries to modify with an 'expireAfterSeconds' value too large. -assert.commandFailedWithCode(db.runCommand({ - "collMod": collName, - "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 10000000000000} -}), - ErrorCodes.InvalidOptions); - // Successfully converts to a TTL index. assert.commandWorked(db.runCommand( {"collMod": collName, "index": {"keyPattern": {a: 1}, "expireAfterSeconds": 100}})); diff --git a/jstests/core/ttl_index_options.js b/jstests/core/ttl_index_options.js index 47ae2709073..ea35a93bec2 100644 --- a/jstests/core/ttl_index_options.js +++ b/jstests/core/ttl_index_options.js @@ -1,7 +1,10 @@ /** * Ensures that the options passed in for TTL indexes are validated during index creation. * - * @tags: [requires_ttl_index] + * @tags: [ + * requires_fcv_60, + * requires_ttl_index, + * ] */ (function() { 'use strict'; @@ -16,11 +19,10 @@ assert.commandFailedWithCode( assert.commandFailedWithCode(coll.createIndexes([{x: 1}], {expireAfterSeconds: 9999999999999999}), ErrorCodes.CannotCreateIndex); -// Ensure that we cannot provide a time that is larger than the current epoch time. +// Ensure that we can provide a time that is larger than the current epoch time. let secondsSinceEpoch = Date.now() / 1000; -assert.commandFailedWithCode( - coll.createIndexes([{x: 1}], {expireAfterSeconds: secondsSinceEpoch + 1000}), - ErrorCodes.CannotCreateIndex); +assert.commandWorked( + coll.createIndexes([{x_before_epoch: 1}], {expireAfterSeconds: secondsSinceEpoch + 1000})); // 'expireAfterSeconds' cannot be less than 0. assert.commandFailedWithCode(coll.createIndexes([{x: 1}], {expireAfterSeconds: -1}), diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index 21b5d6dc920..9fc32c239cd 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -250,7 +250,8 @@ StatusWith<std::pair<ParsedCollModRequest, BSONObj>> parseCollModRequest(Operati "TTL indexes are not supported for capped collections."}; } if (auto status = index_key_validate::validateExpireAfterSeconds( - *cmdIndex.getExpireAfterSeconds()); + *cmdIndex.getExpireAfterSeconds(), + index_key_validate::ValidateExpireAfterSecondsMode::kSecondaryTTLIndex); !status.isOK()) { return {ErrorCodes::InvalidOptions, status.reason()}; } @@ -533,7 +534,9 @@ StatusWith<std::pair<ParsedCollModRequest, BSONObj>> parseCollModRequest(Operati }, [&oplogEntryBuilder](std::int64_t value) { oplogEntryBuilder.append(CollMod::kExpireAfterSecondsFieldName, value); - return index_key_validate::validateExpireAfterSeconds(value); + return index_key_validate::validateExpireAfterSeconds( + value, + index_key_validate::ValidateExpireAfterSecondsMode::kClusteredTTLIndex); }, }, *expireAfterSeconds); diff --git a/src/mongo/db/catalog/create_collection.cpp b/src/mongo/db/catalog/create_collection.cpp index 74be2bd73e4..0a4400680b2 100644 --- a/src/mongo/db/catalog/create_collection.cpp +++ b/src/mongo/db/catalog/create_collection.cpp @@ -117,7 +117,9 @@ Status validateClusteredIndexSpec(OperationContext* opCtx, if (expireAfterSeconds) { // Not included in the indexSpec itself. - auto status = index_key_validate::validateExpireAfterSeconds(*expireAfterSeconds); + auto status = index_key_validate::validateExpireAfterSeconds( + *expireAfterSeconds, + index_key_validate::ValidateExpireAfterSecondsMode::kClusteredTTLIndex); if (!status.isOK()) { return status; } @@ -321,8 +323,9 @@ Status _createTimeseries(OperationContext* opCtx, // Cluster time-series buckets collections by _id. auto expireAfterSeconds = options.expireAfterSeconds; if (expireAfterSeconds) { - uassertStatusOK( - index_key_validate::validateExpireAfterSeconds(*expireAfterSeconds)); + uassertStatusOK(index_key_validate::validateExpireAfterSeconds( + *expireAfterSeconds, + index_key_validate::ValidateExpireAfterSecondsMode::kClusteredTTLIndex)); bucketsOptions.expireAfterSeconds = expireAfterSeconds; } diff --git a/src/mongo/db/catalog/index_key_validate.cpp b/src/mongo/db/catalog/index_key_validate.cpp index 501df1931bb..af71c30bea7 100644 --- a/src/mongo/db/catalog/index_key_validate.cpp +++ b/src/mongo/db/catalog/index_key_validate.cpp @@ -778,7 +778,8 @@ StatusWith<BSONObj> validateIndexSpecCollation(OperationContext* opCtx, return indexSpec; } -Status validateExpireAfterSeconds(std::int64_t expireAfterSeconds) { +Status validateExpireAfterSeconds(std::int64_t expireAfterSeconds, + ValidateExpireAfterSecondsMode mode) { if (expireAfterSeconds < 0) { return {ErrorCodes::InvalidOptions, str::stream() << "TTL index '" << IndexDescriptor::kExpireAfterSecondsFieldName @@ -789,16 +790,31 @@ Status validateExpireAfterSeconds(std::int64_t expireAfterSeconds) { << "TTL index '" << IndexDescriptor::kExpireAfterSecondsFieldName << "' option must be within an acceptable range, try a lower number"; - // There are two cases where we can encounter an issue here. - // The first case is when we try to cast to millseconds from seconds, which could cause an - // overflow. The second case is where 'expireAfterSeconds' is larger than the current epoch - // time. - if (expireAfterSeconds > std::numeric_limits<std::int64_t>::max() / 1000) { - return {ErrorCodes::InvalidOptions, tooLargeErr}; - } - auto expireAfterMillis = duration_cast<Milliseconds>(Seconds(expireAfterSeconds)); - if (expireAfterMillis > Date_t::now().toDurationSinceEpoch()) { - return {ErrorCodes::InvalidOptions, tooLargeErr}; + if (mode == ValidateExpireAfterSecondsMode::kSecondaryTTLIndex) { + // Relax epoch restriction on TTL indexes. This allows us to export and import existing + // TTL indexes with large values or NaN for the 'expireAfterSeconds' field. + // Additionally, the 'expireAfterSeconds' for TTL indexes is defined as safeInt (int32_t) + // in the IDL for listIndexes and collMod. See list_indexes.idl and coll_mod.idl. + if (expireAfterSeconds > std::numeric_limits<std::int32_t>::max()) { + return {ErrorCodes::InvalidOptions, tooLargeErr}; + } + } else { + // Clustered collections with TTL. + // Note that 'expireAfterSeconds' is defined as safeInt64 in the IDL for the create and + // collMod commands. See create.idl and coll_mod.idl. + // There are two cases where we can encounter an issue here. + // The first case is when we try to cast to millseconds from seconds, which could cause an + // overflow. The second case is where 'expireAfterSeconds' is larger than the current epoch + // time. This isn't necessarily problematic for the general case, but for the specific case + // of time series collections, we cluster the collection by an OID value, where the + // timestamp portion is only a 32-bit unsigned integer offset of seconds since the epoch. + if (expireAfterSeconds > std::numeric_limits<std::int64_t>::max() / 1000) { + return {ErrorCodes::InvalidOptions, tooLargeErr}; + } + auto expireAfterMillis = duration_cast<Milliseconds>(Seconds(expireAfterSeconds)); + if (expireAfterMillis > Date_t::now().toDurationSinceEpoch()) { + return {ErrorCodes::InvalidOptions, tooLargeErr}; + } } return Status::OK(); } @@ -822,7 +838,9 @@ Status validateIndexSpecTTL(const BSONObj& indexSpec) { << "'. Index spec: " << indexSpec}; } - if (auto status = validateExpireAfterSeconds(expireAfterSecondsElt.safeNumberLong()); + if (auto status = + validateExpireAfterSeconds(expireAfterSecondsElt.safeNumberLong(), + ValidateExpireAfterSecondsMode::kSecondaryTTLIndex); !status.isOK()) { return {ErrorCodes::CannotCreateIndex, str::stream() << status.reason() << ". Index spec: " << indexSpec}; diff --git a/src/mongo/db/catalog/index_key_validate.h b/src/mongo/db/catalog/index_key_validate.h index 49ffa120e60..af819f4286a 100644 --- a/src/mongo/db/catalog/index_key_validate.h +++ b/src/mongo/db/catalog/index_key_validate.h @@ -127,9 +127,14 @@ StatusWith<BSONObj> validateIndexSpecCollation(OperationContext* opCtx, const CollatorInterface* defaultCollator); /** - * Validates the the 'expireAfterSeconds' value for a TTL index.. + * Validates the the 'expireAfterSeconds' value for a TTL index or clustered collection. */ -Status validateExpireAfterSeconds(std::int64_t expireAfterSeconds); +enum class ValidateExpireAfterSecondsMode { + kSecondaryTTLIndex, + kClusteredTTLIndex, +}; +Status validateExpireAfterSeconds(std::int64_t expireAfterSeconds, + ValidateExpireAfterSecondsMode mode); /** * Returns true if 'indexSpec' refers to a TTL index. |