diff options
| author | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
|---|---|---|
| committer | Lucas de Castro Borges <lucas@gnuabordo.com.br> | 2025-02-11 15:07:35 -0300 |
| commit | 4cb8841196d0625dfa3825aa326f071cd27c7b8b (patch) | |
| tree | 1682a647d4463397c119183369ae6f750d5fdcff /src/mongo/s | |
| parent | aa03c6362cbaa767638e6eed9b031d86dd2643d1 (diff) | |
| parent | 8f0827553e09872941945a093b647a4211a9db7f (diff) | |
Update upstream source from tag 'upstream/6.0.0'master
Update to upstream version '6.0.0'
with Debian dir 5604a80ec1c96ca76f25f40d78e6ef855abec322
Diffstat (limited to 'src/mongo/s')
144 files changed, 2063 insertions, 6042 deletions
diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript index e24b7998cb4..336e7a2ff98 100644 --- a/src/mongo/s/SConscript +++ b/src/mongo/s/SConscript @@ -37,7 +37,6 @@ env.Library( '$BUILD_DIR/mongo/db/fle_crud', '$BUILD_DIR/mongo/db/not_primary_error_tracker', '$BUILD_DIR/mongo/db/timeseries/timeseries_conversion_util', - '$BUILD_DIR/mongo/db/timeseries/timeseries_metadata', '$BUILD_DIR/mongo/db/timeseries/timeseries_options', 'query/cluster_query', 'write_ops/cluster_write_ops', @@ -50,19 +49,19 @@ env.Library( 'cluster_commands_helpers.cpp', 'collection_uuid_mismatch.cpp', 'multi_statement_transaction_requests_sender.cpp', - 'router_role.cpp', 'router_transactions_metrics.cpp', 'router_transactions_stats.idl', + 'router.cpp', 'session_catalog_router.cpp', 'stale_shard_version_helpers.cpp', - 'transaction_router.cpp', 'transaction_router_resource_yielder.cpp', + 'transaction_router.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/db/commands/txn_cmd_request', + '$BUILD_DIR/mongo/db/curop', '$BUILD_DIR/mongo/db/logical_session_id_helpers', '$BUILD_DIR/mongo/db/pipeline/process_interface/mongo_process_interface', - '$BUILD_DIR/mongo/db/query/query_stats/query_stats', '$BUILD_DIR/mongo/db/repl/read_concern_args', '$BUILD_DIR/mongo/db/session_catalog', '$BUILD_DIR/mongo/db/shared_request_handling', @@ -188,7 +187,6 @@ env.Library( 'refine_collection_shard_key_coordinator_feature_flags.idl', 'request_types/abort_reshard_collection.idl', 'request_types/add_shard_request_type.cpp', - 'request_types/get_stats_for_balancing.idl', 'request_types/add_shard_to_zone_request_type.cpp', 'request_types/auto_split_vector.idl', 'request_types/balance_chunk_request_type.cpp', @@ -239,7 +237,6 @@ env.Library( '$BUILD_DIR/mongo/db/index_commands_idl', '$BUILD_DIR/mongo/db/namespace_string', '$BUILD_DIR/mongo/db/query/query_request', - '$BUILD_DIR/mongo/db/query/query_shape/query_shape', '$BUILD_DIR/mongo/db/repl/optime', '$BUILD_DIR/mongo/db/server_options', '$BUILD_DIR/mongo/idl/feature_flag', @@ -463,6 +460,7 @@ env.Library( '$BUILD_DIR/mongo/db/commands/server_status', '$BUILD_DIR/mongo/db/commands/server_status_core', '$BUILD_DIR/mongo/db/commands/server_status_servers', + '$BUILD_DIR/mongo/db/curop', '$BUILD_DIR/mongo/db/dbdirectclient', '$BUILD_DIR/mongo/db/ftdc/ftdc_mongos', '$BUILD_DIR/mongo/db/logical_session_cache', @@ -470,7 +468,6 @@ env.Library( '$BUILD_DIR/mongo/db/logical_time_metadata_hook', '$BUILD_DIR/mongo/db/pipeline/process_interface/mongos_process_interface_factory', '$BUILD_DIR/mongo/db/process_health/fault_manager', - '$BUILD_DIR/mongo/db/query/query_stats/query_stats', '$BUILD_DIR/mongo/db/read_write_concern_defaults', '$BUILD_DIR/mongo/db/server_options', '$BUILD_DIR/mongo/db/server_options_base', @@ -620,7 +617,6 @@ env.CppUnitTest( 'catalog/type_mongos_test.cpp', 'catalog/type_shard_test.cpp', 'catalog/type_tags_test.cpp', - 'chunks_test_util.cpp', 'chunk_manager_index_bounds_test.cpp', 'chunk_manager_query_test.cpp', 'chunk_manager_targeter_test.cpp', diff --git a/src/mongo/s/append_raw_responses_test.cpp b/src/mongo/s/append_raw_responses_test.cpp index 01d26080e6e..9bbc161bfb1 100644 --- a/src/mongo/s/append_raw_responses_test.cpp +++ b/src/mongo/s/append_raw_responses_test.cpp @@ -165,9 +165,7 @@ protected: StaticCatalogClient(std::vector<ShardId> shardIds) : _shardIds(std::move(shardIds)) {} StatusWith<repl::OpTimeWith<std::vector<ShardType>>> getAllShards( - OperationContext* opCtx, - repl::ReadConcernLevel readConcern, - bool excludeDraining) override { + OperationContext* opCtx, repl::ReadConcernLevel readConcern) override { std::vector<ShardType> shardTypes; for (const auto& shardId : _shardIds) { const ConnectionString cs = ConnectionString::forReplicaSet( diff --git a/src/mongo/s/async_requests_sender.cpp b/src/mongo/s/async_requests_sender.cpp index a5100472f69..1fceefc428e 100644 --- a/src/mongo/s/async_requests_sender.cpp +++ b/src/mongo/s/async_requests_sender.cpp @@ -37,7 +37,6 @@ #include <memory> #include "mongo/client/remote_command_targeter.h" -#include "mongo/db/curop.h" #include "mongo/executor/remote_command_request.h" #include "mongo/logv2/log.h" #include "mongo/rpc/get_status_from_command_result.h" @@ -88,8 +87,6 @@ AsyncRequestsSender::AsyncRequestsSender(OperationContext* opCtx, // Kick off requests immediately. _remotes.emplace_back(this, request.shardId, request.cmdObj).executeRequest(); } - - CurOp::get(_opCtx)->ensureRecordRemoteOpWait(); } AsyncRequestsSender::Response AsyncRequestsSender::next() noexcept { @@ -120,22 +117,9 @@ AsyncRequestsSender::Response AsyncRequestsSender::next() noexcept { _resourceYielder->yield(_opCtx); } - auto curOp = CurOp::get(_opCtx); - // Calculating the total wait time for remote operations relies on the CurOp's timing - // measurement facility and we can't use such facility when the current operation is marked - // as done. Some commands such as 'analyzeShardKey' command may send remote operations using - // AsyncRequestsSender even after marking the current operation done and so we need to check - // whether the current operation is still in progress. - auto curOpInProgress = !curOp->isDone(); - if (curOpInProgress) { - curOp->startRemoteOpWaitTimer(); - } // Only wait for the next result without popping it, so an error unyielding doesn't // discard an already popped response. auto waitStatus = _responseQueue.waitForNonEmptyNoThrow(_opCtx); - if (curOpInProgress) { - curOp->stopRemoteOpWaitTimer(); - } auto unyieldStatus = _resourceYielder ? _resourceYielder->unyieldNoThrow(_opCtx) : Status::OK(); @@ -186,10 +170,9 @@ AsyncRequestsSender::RemoteData::RemoteData(AsyncRequestsSender* ars, BSONObj cmdObj) : _ars(ars), _shardId(std::move(shardId)), _cmdObj(std::move(cmdObj)) {} -SemiFuture<std::shared_ptr<Shard>> AsyncRequestsSender::RemoteData::getShard() noexcept { - return Grid::get(getGlobalServiceContext()) - ->shardRegistry() - ->getShard(*_ars->_subBaton, _shardId); +std::shared_ptr<Shard> AsyncRequestsSender::RemoteData::getShard() { + // TODO: Pass down an OperationContext* to use here. + return Grid::get(getGlobalServiceContext())->shardRegistry()->getShardNoReload(_shardId); } void AsyncRequestsSender::RemoteData::executeRequest() { @@ -209,12 +192,7 @@ void AsyncRequestsSender::RemoteData::executeRequest() { auto AsyncRequestsSender::RemoteData::scheduleRequest() -> SemiFuture<RemoteCommandOnAnyCallbackArgs> { - return getShard() - .thenRunOn(*_ars->_subBaton) - .then([this](auto&& shard) { - return shard->getTargeter()->findHosts(_ars->_readPreference, - CancellationToken::uncancelable()); - }) + return resolveShardIdToHostAndPorts(_ars->_readPreference) .thenRunOn(*_ars->_subBaton) .then([this](auto&& hostAndPorts) { _shardHostAndPort.emplace(hostAndPorts.front()); @@ -224,6 +202,17 @@ auto AsyncRequestsSender::RemoteData::scheduleRequest() .semi(); } +SemiFuture<std::vector<HostAndPort>> AsyncRequestsSender::RemoteData::resolveShardIdToHostAndPorts( + const ReadPreferenceSetting& readPref) { + const auto shard = getShard(); + if (!shard) { + return Status(ErrorCodes::ShardNotFound, + str::stream() << "Could not find shard " << _shardId); + } + + return shard->getTargeter()->findHosts(readPref, CancellationToken::uncancelable()); +} + auto AsyncRequestsSender::RemoteData::scheduleRemoteCommand(std::vector<HostAndPort>&& hostAndPorts) -> SemiFuture<RemoteCommandOnAnyCallbackArgs> { hangBeforeSchedulingRemoteCommand.executeIf( @@ -289,47 +278,43 @@ auto AsyncRequestsSender::RemoteData::handleResponse(RemoteCommandOnAnyCallbackA } // There was an error with either the response or the command. - return getShard() - .thenRunOn(*_ars->_subBaton) - .then([this, status = std::move(status), rcr = std::move(rcr)]( - std::shared_ptr<mongo::Shard>&& shard) { - std::vector<HostAndPort> failedTargets; - - if (rcr.response.target) { - failedTargets = {*rcr.response.target}; - } else { - failedTargets = rcr.request.target; - } + auto shard = getShard(); + if (!shard) { + uasserted(ErrorCodes::ShardNotFound, str::stream() << "Could not find shard " << _shardId); + } else { + std::vector<HostAndPort> failedTargets; + + if (rcr.response.target) { + failedTargets = {*rcr.response.target}; + } else { + failedTargets = rcr.request.target; + } - shard->updateReplSetMonitor(failedTargets.front(), status); - bool isStartingTransaction = _cmdObj.getField("startTransaction").booleanSafe(); - if (!_ars->_stopRetrying && - shard->isRetriableError(status.code(), _ars->_retryPolicy) && - _retryCount < kMaxNumFailedHostRetryAttempts && !isStartingTransaction) { - - LOGV2_DEBUG( - 4615637, - 1, - "Command to remote {shardId} for hosts {hosts} failed with retryable error " - "{error} and will be retried", - "Command to remote shard failed with retryable error and will be retried", - "shardId"_attr = _shardId, - "hosts"_attr = failedTargets, - "error"_attr = redact(status)); - ++_retryCount; - _shardHostAndPort.reset(); - // retry through recursion - return scheduleRequest(); - } + shard->updateReplSetMonitor(failedTargets.front(), status); + bool isStartingTransaction = _cmdObj.getField("startTransaction").booleanSafe(); + if (!_ars->_stopRetrying && shard->isRetriableError(status.code(), _ars->_retryPolicy) && + _retryCount < kMaxNumFailedHostRetryAttempts && !isStartingTransaction) { + + LOGV2_DEBUG(4615637, + 1, + "Command to remote {shardId} for hosts {hosts} failed with retryable error " + "{error} and will be retried", + "Command to remote shard failed with retryable error and will be retried", + "shardId"_attr = _shardId, + "hosts"_attr = failedTargets, + "error"_attr = redact(status)); + ++_retryCount; + _shardHostAndPort.reset(); + // retry through recursion + return scheduleRequest(); + } + } - // Status' in the response.status field that aren't retried get converted to top level - // errors - uassertStatusOK(rcr.response.status); + // Status' in the response.status field that aren't retried get converted to top level errors + uassertStatusOK(rcr.response.status); - // We're not okay (on the remote), but still not going to retry - return Future<RemoteCommandOnAnyCallbackArgs>::makeReady(std::move(rcr)).semi(); - }) - .semi(); + // We're not okay (on the remote), but still not going to retry + return std::move(rcr); }; } // namespace mongo diff --git a/src/mongo/s/async_requests_sender.h b/src/mongo/s/async_requests_sender.h index 27d22bc5511..432227356cc 100644 --- a/src/mongo/s/async_requests_sender.h +++ b/src/mongo/s/async_requests_sender.h @@ -179,15 +179,9 @@ private: RemoteData(AsyncRequestsSender* ars, ShardId shardId, BSONObj cmdObj); /** - * Returns a SemiFuture containing a shard object associated with this remote. - * - * This will return a SemiFuture with a ShardNotFound error status in case the shard is not - * found. - * - * Additionally this call can trigger a refresh of the ShardRegistry so it could possibly - * return other network error status related to the refresh. + * Returns the Shard object associated with this remote. */ - SemiFuture<std::shared_ptr<Shard>> getShard() noexcept; + std::shared_ptr<Shard> getShard(); /** * Returns true if we've already queued a response from the remote. diff --git a/src/mongo/s/catalog/sharding_catalog_client.h b/src/mongo/s/catalog/sharding_catalog_client.h index 41057194e3a..05ea0bf1658 100644 --- a/src/mongo/s/catalog/sharding_catalog_client.h +++ b/src/mongo/s/catalog/sharding_catalog_client.h @@ -131,14 +131,11 @@ public: /** * Retrieves all collections under a specified database (or in the system). If the dbName * parameter is empty, returns all collections. - * - * @param sort Fields to use for sorting the results. If empty, no sorting is performed. */ virtual std::vector<CollectionType> getCollections( OperationContext* opCtx, StringData db, - repl::ReadConcernLevel readConcernLevel = repl::ReadConcernLevel::kMajorityReadConcern, - const BSONObj& sort = BSONObj()) = 0; + repl::ReadConcernLevel readConcernLevel = repl::ReadConcernLevel::kMajorityReadConcern) = 0; /** * Returns the set of collections for the specified database, which have been marked as sharded. @@ -206,15 +203,11 @@ public: const NamespaceString& nss) = 0; /** - * Retrieves the list of shards in this sharded cluster. If `excludeDraining` is set to `false` - * (default), it retrieves all shards. Otherwise, it retrieves only shards that are not - * draining. + * Retrieves all shards in this sharded cluster. * Returns a !OK status if an error occurs. */ virtual StatusWith<repl::OpTimeWith<std::vector<ShardType>>> getAllShards( - OperationContext* opCtx, - repl::ReadConcernLevel readConcern, - bool excludeDraining = false) = 0; + OperationContext* opCtx, repl::ReadConcernLevel readConcern) = 0; /** * Runs a user management command on the config servers. Do not use for general write command @@ -288,22 +281,15 @@ public: repl::ReadConcernLevel readConcern) = 0; /** - * Returns internal keys for the given purpose and have an expiresAt value greater than - * newerThanThis. + * Returns keys for the given purpose and with an expiresAt value greater than newerThanThis. */ - virtual StatusWith<std::vector<KeysCollectionDocument>> getNewInternalKeys( + virtual StatusWith<std::vector<KeysCollectionDocument>> getNewKeys( OperationContext* opCtx, StringData purpose, const LogicalTime& newerThanThis, repl::ReadConcernLevel readConcernLevel) = 0; /** - * Returns all external (i.e. validation-only) keys for the given purpose. - */ - virtual StatusWith<std::vector<ExternalKeysCollectionDocument>> getAllExternalKeys( - OperationContext* opCtx, StringData purpose, repl::ReadConcernLevel readConcernLevel) = 0; - - /** * Directly inserts a document in the specified namespace on the config server. The document * must have an _id index. Must only be used for insertions in the 'config' database. * @@ -315,6 +301,19 @@ public: const WriteConcernOptions& writeConcern) = 0; /** + * Directly inserts documents in the specified namespace on the config server. Inserts said + * documents using a retryable write. Underneath, a session is created and destroyed -- this + * ad-hoc session creation strategy should never be used outside of specific, non-performant + * code paths. + * + * Must only be used for insertions in the 'config' database. + */ + virtual void insertConfigDocumentsAsRetryableWrite(OperationContext* opCtx, + const NamespaceString& nss, + std::vector<BSONObj> docs, + const WriteConcernOptions& writeConcern) = 0; + + /** * Updates a single document in the specified namespace on the config server. Must only be used * for updates to the 'config' database. * diff --git a/src/mongo/s/catalog/sharding_catalog_client_impl.cpp b/src/mongo/s/catalog/sharding_catalog_client_impl.cpp index 38949060bf1..bf98a08c7aa 100644 --- a/src/mongo/s/catalog/sharding_catalog_client_impl.cpp +++ b/src/mongo/s/catalog/sharding_catalog_client_impl.cpp @@ -102,6 +102,37 @@ void toBatchError(const Status& status, BatchedCommandResponse* response) { response->setStatus(status); } +void sendRetryableWriteBatchRequestToConfig(OperationContext* opCtx, + const NamespaceString& nss, + std::vector<BSONObj>& docs, + TxnNumber txnNumber, + const WriteConcernOptions& writeConcern) { + auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); + + BatchedCommandRequest request([&] { + write_ops::InsertCommandRequest insertOp(nss); + insertOp.setDocuments(docs); + return insertOp; + }()); + request.setWriteConcern(writeConcern.toBSON()); + + BSONObj cmdObj = request.toBSON(); + BSONObjBuilder bob(cmdObj); + bob.append(OperationSessionInfo::kTxnNumberFieldName, txnNumber); + + BatchedCommandResponse batchResponse; + auto response = configShard->runCommand(opCtx, + ReadPreferenceSetting{ReadPreference::PrimaryOnly}, + nss.db().toString(), + bob.obj(), + Shard::kDefaultConfigCommandTimeout, + Shard::RetryPolicy::kIdempotent); + + auto writeStatus = Shard::CommandResponse::processBatchWriteResponse(response, &batchResponse); + + uassertStatusOK(batchResponse.toStatus()); + uassertStatusOK(writeStatus); +} AggregateCommandRequest makeCollectionAndChunksAggregation(OperationContext* opCtx, const NamespaceString& nss, @@ -271,45 +302,6 @@ AggregateCommandRequest makeCollectionAndChunksAggregation(OperationContext* opC return AggregateCommandRequest(CollectionType::ConfigNS, std::move(serializedPipeline)); } -/** - * Returns keys for the given purpose and have an expiresAt value greater than newerThanThis on the - * given shard. - */ -template <typename KeyDocumentType> -StatusWith<std::vector<KeyDocumentType>> _getNewKeys(OperationContext* opCtx, - std::shared_ptr<Shard> shard, - const NamespaceString& nss, - StringData purpose, - const LogicalTime& newerThanThis, - repl::ReadConcernLevel readConcernLevel) { - BSONObjBuilder queryBuilder; - queryBuilder.append("purpose", purpose); - queryBuilder.append("expiresAt", BSON("$gt" << newerThanThis.asTimestamp())); - - auto findStatus = shard->exhaustiveFindOnConfig(opCtx, - kConfigReadSelector, - readConcernLevel, - nss, - queryBuilder.obj(), - BSON("expiresAt" << 1), - boost::none); - if (!findStatus.isOK()) { - return findStatus.getStatus(); - } - const auto& objs = findStatus.getValue().docs; - - std::vector<KeyDocumentType> keyDocs; - keyDocs.reserve(objs.size()); - for (auto&& obj : objs) { - try { - keyDocs.push_back(KeyDocumentType::parse(IDLParserErrorContext("keyDoc"), obj)); - } catch (...) { - return exceptionToStatus(); - } - } - return keyDocs; -} - } // namespace ShardingCatalogClientImpl::ShardingCatalogClientImpl() = default; @@ -465,10 +457,7 @@ CollectionType ShardingCatalogClientImpl::getCollection(OperationContext* opCtx, } std::vector<CollectionType> ShardingCatalogClientImpl::getCollections( - OperationContext* opCtx, - StringData dbName, - repl::ReadConcernLevel readConcernLevel, - const BSONObj& sort) { + OperationContext* opCtx, StringData dbName, repl::ReadConcernLevel readConcernLevel) { BSONObjBuilder b; if (!dbName.empty()) b.appendRegex(CollectionType::kNssFieldName, @@ -480,7 +469,7 @@ std::vector<CollectionType> ShardingCatalogClientImpl::getCollections( readConcernLevel, CollectionType::ConfigNS, b.obj(), - sort, + BSONObj(), boost::none)) .value; std::vector<CollectionType> collections; @@ -493,7 +482,7 @@ std::vector<CollectionType> ShardingCatalogClientImpl::getCollections( std::vector<NamespaceString> ShardingCatalogClientImpl::getAllShardedCollectionsForDb( OperationContext* opCtx, StringData dbName, repl::ReadConcernLevel readConcern) { - auto collectionsOnConfig = getCollections(opCtx, dbName, readConcern, BSONObj()); + auto collectionsOnConfig = getCollections(opCtx, dbName, readConcern); std::vector<NamespaceString> collectionsToReturn; collectionsToReturn.reserve(collectionsOnConfig.size()); @@ -773,35 +762,38 @@ StatusWith<std::vector<TagsType>> ShardingCatalogClientImpl::getTagsForCollectio } StatusWith<repl::OpTimeWith<std::vector<ShardType>>> ShardingCatalogClientImpl::getAllShards( - OperationContext* opCtx, repl::ReadConcernLevel readConcern, bool excludeDraining) { - const auto& findRes = uassertStatusOK( - _exhaustiveFindOnConfig(opCtx, - kConfigReadSelector, - readConcern, - ShardType::ConfigNS, - excludeDraining ? BSON(ShardType::draining.ne(true)) : BSONObj(), - BSONObj() /* No sorting */, - boost::none /* No limit */)); + OperationContext* opCtx, repl::ReadConcernLevel readConcern) { + auto findStatus = _exhaustiveFindOnConfig(opCtx, + kConfigReadSelector, + readConcern, + ShardType::ConfigNS, + BSONObj(), // no query filter + BSONObj(), // no sort + boost::none); // no limit + if (!findStatus.isOK()) { + return findStatus.getStatus(); + } std::vector<ShardType> shards; - shards.reserve(findRes.value.size()); - for (const BSONObj& doc : findRes.value) { + shards.reserve(findStatus.getValue().value.size()); + for (const BSONObj& doc : findStatus.getValue().value) { auto shardRes = ShardType::fromBSON(doc); if (!shardRes.isOK()) { return shardRes.getStatus().withContext(stream() << "Failed to parse shard document " << doc); } - ShardType& shard = shardRes.getValue(); - if (const Status validateStatus = shard.validate(); !validateStatus.isOK()) { - return validateStatus.withContext(str::stream() + Status validateStatus = shardRes.getValue().validate(); + if (!validateStatus.isOK()) { + return validateStatus.withContext(stream() << "Failed to validate shard document " << doc); } - shards.push_back(std::move(shard)); + shards.push_back(shardRes.getValue()); } - return repl::OpTimeWith<std::vector<ShardType>>{std::move(shards), findRes.opTime}; + return repl::OpTimeWith<std::vector<ShardType>>{std::move(shards), + findStatus.getValue().opTime}; } Status ShardingCatalogClientImpl::runUserManagementWriteCommand(OperationContext* opCtx, @@ -1011,14 +1003,12 @@ Status ShardingCatalogClientImpl::insertConfigDocument(OperationContext* opCtx, insertOp.setDocuments({doc}); return insertOp; }()); + request.setWriteConcern(writeConcern.toBSON()); auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); for (int retry = 1; retry <= kMaxWriteRetry; retry++) { - auto response = configShard->runBatchWriteCommand(opCtx, - Shard::kDefaultConfigCommandTimeout, - request, - writeConcern, - Shard::RetryPolicy::kNoRetry); + auto response = configShard->runBatchWriteCommand( + opCtx, Shard::kDefaultConfigCommandTimeout, request, Shard::RetryPolicy::kNoRetry); Status status = response.toStatus(); @@ -1073,6 +1063,49 @@ Status ShardingCatalogClientImpl::insertConfigDocument(OperationContext* opCtx, MONGO_UNREACHABLE; } +void ShardingCatalogClientImpl::insertConfigDocumentsAsRetryableWrite( + OperationContext* opCtx, + const NamespaceString& nss, + std::vector<BSONObj> docs, + const WriteConcernOptions& writeConcern) { + invariant(nss.db() == NamespaceString::kAdminDb || nss.db() == NamespaceString::kConfigDb); + + AlternativeSessionRegion asr(opCtx); + TxnNumber currentTxnNumber = 0; + + std::vector<BSONObj> workingBatch; + size_t workingBatchItemSize = 0; + int workingBatchDocSize = 0; + + while (!docs.empty()) { + BSONObj toAdd = docs.back(); + docs.pop_back(); + + const int docSizePlusOverhead = + toAdd.objsize() + write_ops::kRetryableAndTxnBatchWriteBSONSizeOverhead; + // Check if pushing this object will exceed the batch size limit or the max object size + if ((workingBatchItemSize + 1 > write_ops::kMaxWriteBatchSize) || + (workingBatchDocSize + docSizePlusOverhead > BSONObjMaxUserSize)) { + sendRetryableWriteBatchRequestToConfig( + asr.opCtx(), nss, workingBatch, currentTxnNumber, writeConcern); + ++currentTxnNumber; + + workingBatch.clear(); + workingBatchItemSize = 0; + workingBatchDocSize = 0; + } + + workingBatch.push_back(toAdd); + ++workingBatchItemSize; + workingBatchDocSize += docSizePlusOverhead; + } + + if (!workingBatch.empty()) { + sendRetryableWriteBatchRequestToConfig( + asr.opCtx(), nss, workingBatch, currentTxnNumber, writeConcern); + } +} + StatusWith<bool> ShardingCatalogClientImpl::updateConfigDocument( OperationContext* opCtx, const NamespaceString& nss, @@ -1117,10 +1150,11 @@ StatusWith<bool> ShardingCatalogClientImpl::_updateConfigDocument( }()}); return updateOp; }()); + request.setWriteConcern(writeConcern.toBSON()); auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); auto response = configShard->runBatchWriteCommand( - opCtx, maxTimeMs, request, writeConcern, Shard::RetryPolicy::kIdempotent); + opCtx, maxTimeMs, request, Shard::RetryPolicy::kIdempotent); Status status = response.toStatus(); if (!status.isOK()) { @@ -1152,13 +1186,11 @@ Status ShardingCatalogClientImpl::removeConfigDocuments(OperationContext* opCtx, }()}); return deleteOp; }()); + request.setWriteConcern(writeConcern.toBSON()); auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); - auto response = configShard->runBatchWriteCommand(opCtx, - Shard::kDefaultConfigCommandTimeout, - request, - writeConcern, - Shard::RetryPolicy::kIdempotent); + auto response = configShard->runBatchWriteCommand( + opCtx, Shard::kDefaultConfigCommandTimeout, request, Shard::RetryPolicy::kIdempotent); return response.toStatus(); } @@ -1181,32 +1213,41 @@ StatusWith<repl::OpTimeWith<vector<BSONObj>>> ShardingCatalogClientImpl::_exhaus response.getValue().opTime); } -StatusWith<std::vector<KeysCollectionDocument>> ShardingCatalogClientImpl::getNewInternalKeys( +StatusWith<std::vector<KeysCollectionDocument>> ShardingCatalogClientImpl::getNewKeys( OperationContext* opCtx, StringData purpose, const LogicalTime& newerThanThis, repl::ReadConcernLevel readConcernLevel) { - auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); - return _getNewKeys<KeysCollectionDocument>(opCtx, - configShard, - NamespaceString::kKeysCollectionNamespace, - purpose, - newerThanThis, - readConcernLevel); -} + auto config = Grid::get(opCtx)->shardRegistry()->getConfigShard(); -StatusWith<std::vector<ExternalKeysCollectionDocument>> -ShardingCatalogClientImpl::getAllExternalKeys(OperationContext* opCtx, - StringData purpose, - repl::ReadConcernLevel readConcernLevel) { - auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); - return _getNewKeys<ExternalKeysCollectionDocument>( - opCtx, - configShard, - NamespaceString::kExternalKeysCollectionNamespace, - purpose, - LogicalTime(), - readConcernLevel); + BSONObjBuilder queryBuilder; + queryBuilder.append("purpose", purpose); + queryBuilder.append("expiresAt", BSON("$gt" << newerThanThis.asTimestamp())); + + auto findStatus = config->exhaustiveFindOnConfig(opCtx, + kConfigReadSelector, + readConcernLevel, + NamespaceString::kKeysCollectionNamespace, + queryBuilder.obj(), + BSON("expiresAt" << 1), + boost::none); + + if (!findStatus.isOK()) { + return findStatus.getStatus(); + } + + const auto& keyDocs = findStatus.getValue().docs; + std::vector<KeysCollectionDocument> keys; + keys.reserve(keyDocs.size()); + for (auto&& keyDoc : keyDocs) { + try { + keys.push_back(KeysCollectionDocument::parse(IDLParserErrorContext("keyDoc"), keyDoc)); + } catch (...) { + return exceptionToStatus(); + } + } + + return keys; } } // namespace mongo diff --git a/src/mongo/s/catalog/sharding_catalog_client_impl.h b/src/mongo/s/catalog/sharding_catalog_client_impl.h index e0aa93ad3b6..99c86ef03a1 100644 --- a/src/mongo/s/catalog/sharding_catalog_client_impl.h +++ b/src/mongo/s/catalog/sharding_catalog_client_impl.h @@ -82,8 +82,7 @@ public: std::vector<CollectionType> getCollections(OperationContext* opCtx, StringData db, - repl::ReadConcernLevel readConcernLevel, - const BSONObj& sort) override; + repl::ReadConcernLevel readConcernLevel) override; std::vector<NamespaceString> getAllShardedCollectionsForDb( OperationContext* opCtx, StringData dbName, repl::ReadConcernLevel readConcern) override; @@ -112,7 +111,7 @@ public: const NamespaceString& nss) override; StatusWith<repl::OpTimeWith<std::vector<ShardType>>> getAllShards( - OperationContext* opCtx, repl::ReadConcernLevel readConcern, bool excludeDraining) override; + OperationContext* opCtx, repl::ReadConcernLevel readConcern) override; Status runUserManagementWriteCommand(OperationContext* opCtx, StringData commandName, @@ -144,6 +143,11 @@ public: const BSONObj& doc, const WriteConcernOptions& writeConcern) override; + void insertConfigDocumentsAsRetryableWrite(OperationContext* opCtx, + const NamespaceString& nss, + std::vector<BSONObj> docs, + const WriteConcernOptions& writeConcern) override; + StatusWith<bool> updateConfigDocument(OperationContext* opCtx, const NamespaceString& nss, const BSONObj& query, @@ -165,17 +169,12 @@ public: const WriteConcernOptions& writeConcern, boost::optional<BSONObj> hint = boost::none) override; - StatusWith<std::vector<KeysCollectionDocument>> getNewInternalKeys( + StatusWith<std::vector<KeysCollectionDocument>> getNewKeys( OperationContext* opCtx, StringData purpose, const LogicalTime& newerThanThis, repl::ReadConcernLevel readConcernLevel) override; - StatusWith<std::vector<ExternalKeysCollectionDocument>> getAllExternalKeys( - OperationContext* opCtx, - StringData purpose, - repl::ReadConcernLevel readConcernLevel) override; - private: /** * Updates a single document (if useMultiUpdate is false) or multiple documents (if diff --git a/src/mongo/s/catalog/sharding_catalog_client_mock.cpp b/src/mongo/s/catalog/sharding_catalog_client_mock.cpp index 7f19157811b..3ad6ab3f54a 100644 --- a/src/mongo/s/catalog/sharding_catalog_client_mock.cpp +++ b/src/mongo/s/catalog/sharding_catalog_client_mock.cpp @@ -69,10 +69,7 @@ CollectionType ShardingCatalogClientMock::getCollection(OperationContext* opCtx, } std::vector<CollectionType> ShardingCatalogClientMock::getCollections( - OperationContext* opCtx, - StringData dbName, - repl::ReadConcernLevel readConcernLevel, - const BSONObj& sort) { + OperationContext* opCtx, StringData dbName, repl::ReadConcernLevel readConcernLevel) { uasserted(ErrorCodes::InternalError, "Method not implemented"); } @@ -113,7 +110,7 @@ StatusWith<std::vector<TagsType>> ShardingCatalogClientMock::getTagsForCollectio } StatusWith<repl::OpTimeWith<std::vector<ShardType>>> ShardingCatalogClientMock::getAllShards( - OperationContext* opCtx, repl::ReadConcernLevel readConcern, bool excludeDraining) { + OperationContext* opCtx, repl::ReadConcernLevel readConcern) { return {ErrorCodes::InternalError, "Method not implemented"}; } @@ -152,6 +149,12 @@ Status ShardingCatalogClientMock::insertConfigDocument(OperationContext* opCtx, return {ErrorCodes::InternalError, "Method not implemented"}; } +void ShardingCatalogClientMock::insertConfigDocumentsAsRetryableWrite( + OperationContext* opCtx, + const NamespaceString& nss, + std::vector<BSONObj> docs, + const WriteConcernOptions& writeConcern) {} + StatusWith<bool> ShardingCatalogClientMock::updateConfigDocument( OperationContext* opCtx, const NamespaceString& nss, @@ -188,7 +191,7 @@ Status ShardingCatalogClientMock::createDatabase(OperationContext* opCtx, return {ErrorCodes::InternalError, "Method not implemented"}; } -StatusWith<std::vector<KeysCollectionDocument>> ShardingCatalogClientMock::getNewInternalKeys( +StatusWith<std::vector<KeysCollectionDocument>> ShardingCatalogClientMock::getNewKeys( OperationContext* opCtx, StringData purpose, const LogicalTime& newerThanThis, @@ -196,13 +199,6 @@ StatusWith<std::vector<KeysCollectionDocument>> ShardingCatalogClientMock::getNe return {ErrorCodes::InternalError, "Method not implemented"}; } -StatusWith<std::vector<ExternalKeysCollectionDocument>> -ShardingCatalogClientMock::getAllExternalKeys(OperationContext* opCtx, - StringData purpose, - repl::ReadConcernLevel readConcernLevel) { - return {ErrorCodes::InternalError, "Method not implemented"}; -} - StatusWith<repl::OpTimeWith<std::vector<BSONObj>>> ShardingCatalogClientMock::_exhaustiveFindOnConfig(OperationContext* opCtx, const ReadPreferenceSetting& readPref, diff --git a/src/mongo/s/catalog/sharding_catalog_client_mock.h b/src/mongo/s/catalog/sharding_catalog_client_mock.h index 4cbfd759604..fdab949c024 100644 --- a/src/mongo/s/catalog/sharding_catalog_client_mock.h +++ b/src/mongo/s/catalog/sharding_catalog_client_mock.h @@ -58,8 +58,7 @@ public: std::vector<CollectionType> getCollections(OperationContext* opCtx, StringData db, - repl::ReadConcernLevel readConcernLevel, - const BSONObj& sort) override; + repl::ReadConcernLevel readConcernLevel) override; std::vector<NamespaceString> getAllShardedCollectionsForDb( OperationContext* opCtx, StringData dbName, repl::ReadConcernLevel readConcern) override; @@ -87,7 +86,7 @@ public: const NamespaceString& nss) override; StatusWith<repl::OpTimeWith<std::vector<ShardType>>> getAllShards( - OperationContext* opCtx, repl::ReadConcernLevel readConcern, bool excludeDraining) override; + OperationContext* opCtx, repl::ReadConcernLevel readConcern) override; Status runUserManagementWriteCommand(OperationContext* opCtx, StringData commandName, @@ -121,6 +120,11 @@ public: const BSONObj& doc, const WriteConcernOptions& writeConcern) override; + void insertConfigDocumentsAsRetryableWrite(OperationContext* opCtx, + const NamespaceString& nss, + std::vector<BSONObj> docs, + const WriteConcernOptions& writeConcern) override; + StatusWith<bool> updateConfigDocument(OperationContext* opCtx, const NamespaceString& nss, const BSONObj& query, @@ -144,17 +148,12 @@ public: Status createDatabase(OperationContext* opCtx, StringData dbName, ShardId primaryShard); - StatusWith<std::vector<KeysCollectionDocument>> getNewInternalKeys( + StatusWith<std::vector<KeysCollectionDocument>> getNewKeys( OperationContext* opCtx, StringData purpose, const LogicalTime& newerThanThis, repl::ReadConcernLevel readConcernLevel) override; - StatusWith<std::vector<ExternalKeysCollectionDocument>> getAllExternalKeys( - OperationContext* opCtx, - StringData purpose, - repl::ReadConcernLevel readConcernLevel) override; - private: StatusWith<repl::OpTimeWith<std::vector<BSONObj>>> _exhaustiveFindOnConfig( OperationContext* opCtx, diff --git a/src/mongo/s/catalog/sharding_catalog_client_test.cpp b/src/mongo/s/catalog/sharding_catalog_client_test.cpp index 80967107ff6..1c9e8bca3b9 100644 --- a/src/mongo/s/catalog/sharding_catalog_client_test.cpp +++ b/src/mongo/s/catalog/sharding_catalog_client_test.cpp @@ -361,40 +361,6 @@ TEST_F(ShardingCatalogClientTest, GetAllShardsWithInvalidShard) { future.default_timed_get(); } -TEST_F(ShardingCatalogClientTest, GetAllShardsWithDrainingShard) { - configTargeter()->setFindHostReturnValue(HostAndPort("TestHost1")); - - auto future = launchAsync([this]() { - const auto shards = - assertGet(catalogClient()->getAllShards(operationContext(), - repl::ReadConcernLevel::kMajorityReadConcern, - true /* excludeDraining */)); - return shards.value; - }); - - onFindCommand([this](const RemoteCommandRequest& request) { - ASSERT_BSONOBJ_EQ(getReplSecondaryOkMetadata(), - rpc::TrackingMetadata::removeTrackingData(request.metadata)); - - const auto opMsg = OpMsgRequest::fromDBAndBody(request.dbname, request.cmdObj); - const auto query = query_request_helper::makeFromFindCommandForTests(opMsg.body); - - ASSERT_EQ(query->getNamespaceOrUUID().nss().value_or(NamespaceString()), - ShardType::ConfigNS); - ASSERT_BSONOBJ_EQ(query->getFilter(), BSON(ShardType::draining.ne(true))); - ASSERT_BSONOBJ_EQ(query->getSort(), BSONObj()); - ASSERT_FALSE(query->getLimit().has_value()); - - checkReadConcern(request.cmdObj, - VectorClock::kInitialComponentTime.asTimestamp(), - repl::OpTime::kUninitializedTerm); - - return vector<BSONObj>{}; - }); - - future.default_timed_get(); -} - TEST_F(ShardingCatalogClientTest, GetChunksForNSWithSortAndLimit) { configTargeter()->setFindHostReturnValue(HostAndPort("TestHost1")); @@ -1385,10 +1351,10 @@ TEST_F(ShardingCatalogClientTest, GetNewKeys) { repl::ReadConcernLevel readConcernLevel(repl::ReadConcernLevel::kMajorityReadConcern); auto future = launchAsync([this, purpose, currentTime, readConcernLevel] { - auto swKeys = catalogClient()->getNewInternalKeys( - operationContext(), purpose, currentTime, readConcernLevel); - ASSERT_OK(swKeys.getStatus()); - return swKeys.getValue(); + auto status = + catalogClient()->getNewKeys(operationContext(), purpose, currentTime, readConcernLevel); + ASSERT_OK(status.getStatus()); + return status.getValue(); }); LogicalTime dummyTime(Timestamp(9876, 5432)); @@ -1450,10 +1416,10 @@ TEST_F(ShardingCatalogClientTest, GetNewKeysWithEmptyCollection) { repl::ReadConcernLevel readConcernLevel(repl::ReadConcernLevel::kMajorityReadConcern); auto future = launchAsync([this, purpose, currentTime, readConcernLevel] { - auto swKeys = catalogClient()->getNewInternalKeys( - operationContext(), purpose, currentTime, readConcernLevel); - ASSERT_OK(swKeys.getStatus()); - return swKeys.getValue(); + auto status = + catalogClient()->getNewKeys(operationContext(), purpose, currentTime, readConcernLevel); + ASSERT_OK(status.getStatus()); + return status.getValue(); }); onFindCommand([this](const RemoteCommandRequest& request) { diff --git a/src/mongo/s/catalog/type_chunk.cpp b/src/mongo/s/catalog/type_chunk.cpp index 39b4295f66f..61c964af93d 100644 --- a/src/mongo/s/catalog/type_chunk.cpp +++ b/src/mongo/s/catalog/type_chunk.cpp @@ -88,15 +88,6 @@ Status extractObject(const BSONObj& obj, const std::string& fieldName, BSONEleme return Status::OK(); } -bool allElementsAreMaxKey(const BSONObj& obj) { - for (auto&& elem : obj) { - if (elem.type() != MaxKey) { - return false; - } - } - return true; -} - } // namespace ChunkRange::ChunkRange(BSONObj minKey, BSONObj maxKey) @@ -133,8 +124,7 @@ StatusWith<ChunkRange> ChunkRange::fromBSON(const BSONObj& obj) { } bool ChunkRange::containsKey(const BSONObj& key) const { - return (_minKey.woCompare(key) <= 0 && key.woCompare(_maxKey) < 0) || - MONGO_unlikely(allElementsAreMaxKey(key) && key.binaryEqual(_maxKey)); + return _minKey.woCompare(key) <= 0 && key.woCompare(_maxKey) < 0; } void ChunkRange::append(BSONObjBuilder* builder) const { @@ -597,9 +587,8 @@ Status ChunkType::validate() const { if (!_history.empty()) { if (_history.front().getShard() != *_shard) { return {ErrorCodes::BadValue, - str::stream() << "Latest entry of chunk history refer to shard " - << _history.front().getShard() - << " that does not match the current shard " << *_shard}; + str::stream() << "History contains an invalid shard " + << _history.front().getShard()}; } } diff --git a/src/mongo/s/catalog/type_chunk.h b/src/mongo/s/catalog/type_chunk.h index 0b24977cfba..be05f7af42f 100644 --- a/src/mongo/s/catalog/type_chunk.h +++ b/src/mongo/s/catalog/type_chunk.h @@ -137,9 +137,6 @@ private: class ChunkHistory : public ChunkHistoryBase { public: - using ChunkHistoryBase::serialize; - using ChunkHistoryBase::toBSON; - ChunkHistory() : ChunkHistoryBase() {} ChunkHistory(mongo::Timestamp ts, mongo::ShardId shard) : ChunkHistoryBase() { setValidAfter(std::move(ts)); diff --git a/src/mongo/s/catalog/type_chunk_test.cpp b/src/mongo/s/catalog/type_chunk_test.cpp index 3de5d37ce88..52c172a529a 100644 --- a/src/mongo/s/catalog/type_chunk_test.cpp +++ b/src/mongo/s/catalog/type_chunk_test.cpp @@ -352,69 +352,6 @@ TEST(ChunkRange, Union) { target.unionWith(ChunkRange(BSON("x" << 9), BSON("x" << 15)))); } -TEST(ChunkRange, ContainsKey) { - auto target = ChunkRange(BSON("x" << 5), BSON("x" << 10)); - ASSERT_FALSE(target.containsKey(BSON("x" << MINKEY))); - ASSERT_FALSE(target.containsKey(BSON("x" << 2))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5))); - ASSERT_TRUE(target.containsKey(BSON("x" << 7))); - ASSERT_FALSE(target.containsKey(BSON("x" << 10))); - ASSERT_FALSE(target.containsKey(BSON("x" << 15))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY))); - - target = ChunkRange(BSON("x" << MINKEY), BSON("x" << 5)); - ASSERT_TRUE(target.containsKey(BSON("x" << MINKEY))); - ASSERT_TRUE(target.containsKey(BSON("x" << 2))); - ASSERT_FALSE(target.containsKey(BSON("x" << 5))); - ASSERT_FALSE(target.containsKey(BSON("x" << 10))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY))); - - target = ChunkRange(BSON("x" << 5), BSON("x" << MAXKEY)); - ASSERT_FALSE(target.containsKey(BSON("x" << MINKEY))); - ASSERT_FALSE(target.containsKey(BSON("x" << 2))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5))); - ASSERT_TRUE(target.containsKey(BSON("x" << 10))); - ASSERT_TRUE(target.containsKey(BSON("x" << MAXKEY))); -} - -TEST(ChunkRange, ContainsKeyCompound) { - auto target = ChunkRange(BSON("x" << 5 << "y" << 100), BSON("x" << 10 << "y" << 120)); - ASSERT_FALSE(target.containsKey(BSON("x" << MINKEY))); - ASSERT_FALSE(target.containsKey(BSON("x" << MINKEY << "y" << 110))); - ASSERT_FALSE(target.containsKey(BSON("x" << 2))); - ASSERT_FALSE(target.containsKey(BSON("x" << 2 << "y" << 110))); - ASSERT_FALSE(target.containsKey(BSON("x" << 5))); - ASSERT_FALSE(target.containsKey(BSON("x" << 5 << "y" << 0))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5 << "y" << 120))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5 << "y" << MAXKEY))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5 << "y" << 100))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5 << "y" << 110))); - ASSERT_TRUE(target.containsKey(BSON("x" << 7 << "y" << MINKEY))); - ASSERT_TRUE(target.containsKey(BSON("x" << 7 << "y" << 100))); - ASSERT_TRUE(target.containsKey(BSON("x" << 7 << "y" << 110))); - ASSERT_TRUE(target.containsKey(BSON("x" << 7 << "y" << 120))); - ASSERT_TRUE(target.containsKey(BSON("x" << 7 << "y" << MAXKEY))); - ASSERT_TRUE(target.containsKey(BSON("x" << 10))); - ASSERT_TRUE(target.containsKey(BSON("x" << 10 << "y" << 0))); - ASSERT_TRUE(target.containsKey(BSON("x" << 10 << "y" << 100))); - ASSERT_TRUE(target.containsKey(BSON("x" << 10 << "y" << 110))); - ASSERT_FALSE(target.containsKey(BSON("x" << 10 << "y" << 120))); - ASSERT_FALSE(target.containsKey(BSON("x" << 10 << "y" << MAXKEY))); - ASSERT_FALSE(target.containsKey(BSON("x" << 15))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY << "y" << 0))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY << "y" << 100))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY << "y" << 110))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY << "y" << 120))); - ASSERT_FALSE(target.containsKey(BSON("x" << MAXKEY << "y" << MAXKEY))); - - target = ChunkRange(BSON("x" << 5 << "y" << 5), BSON("x" << MAXKEY << "y" << MAXKEY)); - ASSERT_FALSE(target.containsKey(BSON("x" << MINKEY << "y" << MAXKEY))); - ASSERT_FALSE(target.containsKey(BSON("x" << 2 << "y" << MAXKEY))); - ASSERT_TRUE(target.containsKey(BSON("x" << 5 << "y" << MAXKEY))); - ASSERT_TRUE(target.containsKey(BSON("x" << MAXKEY << "y" << MAXKEY))); -} - TEST(ChunkRange, MinGreaterThanMaxShouldError) { auto parseStatus = ChunkRange::fromBSON(BSON("min" << BSON("x" << 10) << "max" << BSON("x" << 0))); diff --git a/src/mongo/s/catalog/type_collection.cpp b/src/mongo/s/catalog/type_collection.cpp index 320382f9e98..5cc64eca8b5 100644 --- a/src/mongo/s/catalog/type_collection.cpp +++ b/src/mongo/s/catalog/type_collection.cpp @@ -54,7 +54,7 @@ CollectionType::CollectionType(NamespaceString nss, std::move(creationTime), std::move(uuid), std::move(keyPattern)) { - invariant(getTimestamp() != Timestamp(0, 0)); + invariant(creationTime != Timestamp(0, 0)); setEpoch(std::move(epoch)); } diff --git a/src/mongo/s/catalog_cache.cpp b/src/mongo/s/catalog_cache.cpp index fff547935bc..60314d67ef3 100644 --- a/src/mongo/s/catalog_cache.cpp +++ b/src/mongo/s/catalog_cache.cpp @@ -37,8 +37,6 @@ #include "mongo/s/catalog_cache.h" -#include <fmt/format.h> - #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/repl/optime_with.h" @@ -59,7 +57,6 @@ namespace mongo { namespace { MONGO_FAIL_POINT_DEFINE(blockCollectionCacheLookup); -MONGO_FAIL_POINT_DEFINE(blockDatabaseCacheLookup); // How many times to try refreshing the routing info if the set of chunks loaded from the config // server is found to be inconsistent. @@ -71,6 +68,9 @@ const int kCollectionCacheSize = 10000; const OperationContext::Decoration<bool> operationShouldBlockBehindCatalogCacheRefresh = OperationContext::declareDecoration<bool>(); +const OperationContext::Decoration<bool> operationBlockedBehindCatalogCacheRefresh = + OperationContext::declareDecoration<bool>(); + std::shared_ptr<RoutingTableHistory> createUpdatedRoutingTableHistory( OperationContext* opCtx, const NamespaceString& nss, @@ -81,31 +81,28 @@ std::shared_ptr<RoutingTableHistory> createUpdatedRoutingTableHistory( if (isIncremental && collectionAndChunks.changedChunks.size() == 1 && collectionAndChunks.changedChunks[0].getVersion() == existingHistory->optRt->getVersion()) { - tassert(7032310, - fmt::format("allowMigrations field of collection '{}' changed without changing the " - "collection version {}. Old value: {}, new value: {}", - nss.toString(), - existingHistory->optRt->getVersion().toString(), - existingHistory->optRt->allowMigrations(), - collectionAndChunks.allowMigrations), - collectionAndChunks.allowMigrations == existingHistory->optRt->allowMigrations()); + invariant(collectionAndChunks.allowMigrations == existingHistory->optRt->allowMigrations(), + str::stream() << "allowMigrations field of " << nss + << " collection changed without changing the collection version " + << existingHistory->optRt->getVersion().toString() + << ". Old value: " << existingHistory->optRt->allowMigrations() + << ", new value: " << collectionAndChunks.allowMigrations); const auto& oldReshardingFields = existingHistory->optRt->getReshardingFields(); const auto& newReshardingFields = collectionAndChunks.reshardingFields; - tassert(7032311, - fmt::format("reshardingFields field of collection '{}' changed without changing " - "the collection version {}. Old value: {}, new value: {}", - nss.toString(), - existingHistory->optRt->getVersion().toString(), - oldReshardingFields->toBSON().toString(), - newReshardingFields->toBSON().toString()), - [&] { - if (oldReshardingFields && newReshardingFields) - return oldReshardingFields->toBSON().woCompare( - newReshardingFields->toBSON()) == 0; - else - return !oldReshardingFields && !newReshardingFields; - }()); + invariant( + [&] { + if (oldReshardingFields && newReshardingFields) + return oldReshardingFields->toBSON().woCompare(newReshardingFields->toBSON()) == + 0; + else + return !oldReshardingFields && !newReshardingFields; + }(), + str::stream() << "reshardingFields field of " << nss + << " collection changed without changing the collection version " + << existingHistory->optRt->getVersion().toString() + << ". Old value: " << oldReshardingFields->toBSON() + << ", new value: " << newReshardingFields->toBSON()); return existingHistory->optRt; } @@ -117,11 +114,7 @@ std::shared_ptr<RoutingTableHistory> createUpdatedRoutingTableHistory( return 0; } if (collectionAndChunks.maxChunkSizeBytes) { - tassert(7032312, - fmt::format("Invalid maxChunkSizeBytes value {} for collection '{}'", - nss.toString(), - collectionAndChunks.maxChunkSizeBytes.get()), - collectionAndChunks.maxChunkSizeBytes.get() > 0); + invariant(collectionAndChunks.maxChunkSizeBytes.get() > 0); return uint64_t(*collectionAndChunks.maxChunkSizeBytes); } return boost::none; @@ -249,51 +242,26 @@ CatalogCache::CatalogCache(ServiceContext* const service, CatalogCacheLoader& ca } CatalogCache::~CatalogCache() { - // The executor is used by all the caches that correspond to the router role, so it must be - // joined before these caches are destroyed, per the contract of ReadThroughCache. - shutDownAndJoin(); -} - -void CatalogCache::shutDownAndJoin() { + // The executor is used by the Database and Collection caches, + // so it must be joined, before these caches are destroyed, + // per the contract of ReadThroughCache. _executor->shutdown(); _executor->join(); } StatusWith<CachedDatabaseInfo> CatalogCache::getDatabase(OperationContext* opCtx, - StringData dbName) { - return _getDatabase(opCtx, dbName); -} - -StatusWith<CachedDatabaseInfo> CatalogCache::_getDatabase(OperationContext* opCtx, - StringData dbName, - bool allowLocks) { - tassert(7032313, + StringData dbName, + bool allowLocks) { + if (!allowLocks) { + invariant( + !opCtx->lockState() || !opCtx->lockState()->isLocked(), "Do not hold a lock while refreshing the catalog cache. Doing so would potentially " "hold the lock during a network call, and can lead to a deadlock as described in " - "SERVER-37398.", - allowLocks || !opCtx->lockState() || !opCtx->lockState()->isLocked()); + "SERVER-37398."); + } try { - auto dbEntryFuture = - _databaseCache.acquireAsync(dbName, CacheCausalConsistency::kLatestKnown); - - if (allowLocks) { - // When allowLocks is true we may be holding a lock, so we don't want to block the - // current thread: if the future is ready let's use it, otherwise return an error. - if (dbEntryFuture.isReady()) { - return dbEntryFuture.get(opCtx); - } else { - // This error only contains the database name and must be handled by any callers of - // _getDatabase with the potential for allowLocks to be true. The caller should - // convert this to ErrorCodes::ShardCannotRefreshDueToLocksHeld with the full - // namespace. - return Status{ShardCannotRefreshDueToLocksHeldInfo(NamespaceString(dbName)), - "Database info refresh did not complete"}; - } - } - - // From this point we can guarantee that allowLocks is false. - auto dbEntry = dbEntryFuture.get(opCtx); + auto dbEntry = _databaseCache.acquire(opCtx, dbName, CacheCausalConsistency::kLatestKnown); uassert(ErrorCodes::NamespaceNotFound, str::stream() << "database " << dbName << " not found", dbEntry); @@ -309,28 +277,18 @@ StatusWith<ChunkManager> CatalogCache::_getCollectionRoutingInfoAt( const NamespaceString& nss, boost::optional<Timestamp> atClusterTime, bool allowLocks) { - tassert(7032314, - "Do not hold a lock while refreshing the catalog cache. Doing so would potentially " - "hold the lock during a network call, and can lead to a deadlock as described in " - "SERVER-37398.", - allowLocks || !opCtx->lockState() || !opCtx->lockState()->isLocked()); + if (!allowLocks) { + invariant(!opCtx->lockState() || !opCtx->lockState()->isLocked(), + "Do not hold a lock while refreshing the catalog cache. Doing so would " + "potentially hold " + "the lock during a network call, and can lead to a deadlock as described in " + "SERVER-37398."); + } try { - const auto swDbInfo = _getDatabase(opCtx, nss.db(), allowLocks); + const auto swDbInfo = getDatabase(opCtx, nss.db(), allowLocks); if (!swDbInfo.isOK()) { - if (swDbInfo == ErrorCodes::ShardCannotRefreshDueToLocksHeld) { - // Since collection refreshes always imply database refreshes, it is ok to transform - // this error into a collection error rather than a database error. - auto dbRefreshInfo = - swDbInfo.getStatus().extraInfo<ShardCannotRefreshDueToLocksHeldInfo>(); - LOGV2_DEBUG(7850500, - 2, - "Adding collection name to ShardCannotRefreshDueToLocksHeld error", - "dbName"_attr = dbRefreshInfo->getNss().db(), - "nss"_attr = nss); - return Status{ShardCannotRefreshDueToLocksHeldInfo(nss), - "Routing info refresh did not complete"}; - } else if (swDbInfo == ErrorCodes::NamespaceNotFound) { + if (swDbInfo == ErrorCodes::NamespaceNotFound) { LOGV2_FOR_CATALOG_REFRESH( 4947103, 2, @@ -368,6 +326,9 @@ StatusWith<ChunkManager> CatalogCache::_getCollectionRoutingInfoAt( } // From this point we can guarantee that allowLocks is false + + operationBlockedBehindCatalogCacheRefresh(opCtx) = true; + size_t acquireTries = 0; Timer t; @@ -564,6 +525,37 @@ void CatalogCache::report(BSONObjBuilder* builder) const { _collectionCache.reportStats(&cacheStatsBuilder); } +void CatalogCache::checkAndRecordOperationBlockedByRefresh(OperationContext* opCtx, + mongo::LogicalOp opType) { + if (!isMongos() || !operationBlockedBehindCatalogCacheRefresh(opCtx)) { + return; + } + + auto& opsBlockedByRefresh = _stats.operationsBlockedByRefresh; + + opsBlockedByRefresh.countAllOperations.fetchAndAddRelaxed(1); + + switch (opType) { + case LogicalOp::opInsert: + opsBlockedByRefresh.countInserts.fetchAndAddRelaxed(1); + break; + case LogicalOp::opQuery: + opsBlockedByRefresh.countQueries.fetchAndAddRelaxed(1); + break; + case LogicalOp::opUpdate: + opsBlockedByRefresh.countUpdates.fetchAndAddRelaxed(1); + break; + case LogicalOp::opDelete: + opsBlockedByRefresh.countDeletes.fetchAndAddRelaxed(1); + break; + case LogicalOp::opCommand: + opsBlockedByRefresh.countCommands.fetchAndAddRelaxed(1); + break; + default: + MONGO_UNREACHABLE; + } +} + void CatalogCache::invalidateDatabaseEntry_LINEARIZABLE(const StringData& dbName) { _databaseCache.invalidateKey(dbName); } @@ -576,6 +568,26 @@ void CatalogCache::Stats::report(BSONObjBuilder* builder) const { builder->append("countStaleConfigErrors", countStaleConfigErrors.load()); builder->append("totalRefreshWaitTimeMicros", totalRefreshWaitTimeMicros.load()); + + if (isMongos()) { + BSONObjBuilder operationsBlockedByRefreshBuilder( + builder->subobjStart("operationsBlockedByRefresh")); + + operationsBlockedByRefreshBuilder.append( + "countAllOperations", operationsBlockedByRefresh.countAllOperations.load()); + operationsBlockedByRefreshBuilder.append("countInserts", + operationsBlockedByRefresh.countInserts.load()); + operationsBlockedByRefreshBuilder.append("countQueries", + operationsBlockedByRefresh.countQueries.load()); + operationsBlockedByRefreshBuilder.append("countUpdates", + operationsBlockedByRefresh.countUpdates.load()); + operationsBlockedByRefreshBuilder.append("countDeletes", + operationsBlockedByRefresh.countDeletes.load()); + operationsBlockedByRefreshBuilder.append("countCommands", + operationsBlockedByRefresh.countCommands.load()); + + operationsBlockedByRefreshBuilder.done(); + } } CatalogCache::DatabaseCache::DatabaseCache(ServiceContext* service, @@ -598,10 +610,6 @@ CatalogCache::DatabaseCache::LookupResult CatalogCache::DatabaseCache::_lookupDa const std::string& dbName, const DatabaseTypeValueHandle& previousDbType, const ComparableDatabaseVersion& previousDbVersion) { - if (MONGO_unlikely(blockDatabaseCacheLookup.shouldFail())) { - LOGV2(8023400, "Hanging before refreshing cached database entry"); - blockDatabaseCacheLookup.pauseWhileSet(); - } // TODO (SERVER-34164): Track and increment stats for database refreshes LOGV2_FOR_CATALOG_REFRESH(24102, 2, "Refreshing cached database entry", "db"_attr = dbName); @@ -693,7 +701,7 @@ CatalogCache::CollectionCache::LookupResult CatalogCache::CollectionCache::_look OperationContext* opCtx, const NamespaceString& nss, const RoutingTableHistoryValueHandle& existingHistory, - const ComparableChunkVersion& timeInStore) { + const ComparableChunkVersion& previousVersion) { const bool isIncremental(existingHistory && existingHistory->optRt); _updateRefreshesStats(isIncremental, true); blockCollectionCacheLookup.pauseWhileSet(opCtx); @@ -712,7 +720,7 @@ CatalogCache::CollectionCache::LookupResult CatalogCache::CollectionCache::_look "Refreshing cached collection", "namespace"_attr = nss, "lookupSinceVersion"_attr = lookupVersion, - "timeInStore"_attr = timeInStore); + "timeInStore"_attr = previousVersion); auto collectionAndChunks = _catalogCacheLoader.getChunksSince(nss, lookupVersion).get(); @@ -734,18 +742,14 @@ CatalogCache::CollectionCache::LookupResult CatalogCache::CollectionCache::_look const ChunkVersion newVersion = newRoutingHistory->getVersion(); newComparableVersion.setChunkVersion(newVersion); - // The log below is logged at debug(0) (equivalent to info level) only if the new placement - // version is different than the one we already had (if any). - LOGV2_FOR_CATALOG_REFRESH( - 4619901, - (!isIncremental || newVersion != existingHistory->optRt->getVersion()) ? 0 : 1, - "Refreshed cached collection", - "namespace"_attr = nss, - "lookupSinceVersion"_attr = lookupVersion, - "newVersion"_attr = newComparableVersion, - "timeInStore"_attr = timeInStore, - "duration"_attr = Milliseconds(t.millis())); - + LOGV2_FOR_CATALOG_REFRESH(4619901, + isIncremental || newComparableVersion != previousVersion ? 0 : 1, + "Refreshed cached collection", + "namespace"_attr = nss, + "lookupSinceVersion"_attr = lookupVersion, + "newVersion"_attr = newComparableVersion, + "timeInStore"_attr = previousVersion, + "duration"_attr = Milliseconds(t.millis())); _updateRefreshesStats(isIncremental, false); return LookupResult(OptionalRoutingTableHistory(std::move(newRoutingHistory)), diff --git a/src/mongo/s/catalog_cache.h b/src/mongo/s/catalog_cache.h index 1b40ffb5a53..48c781d27ad 100644 --- a/src/mongo/s/catalog_cache.h +++ b/src/mongo/s/catalog_cache.h @@ -140,16 +140,13 @@ public: virtual ~CatalogCache(); /** - * Shuts down and joins the executor used by all the caches to run their blocking work. - */ - void shutDownAndJoin(); - - /** * Blocking method that ensures the specified database is in the cache, loading it if necessary, * and returns it. If the database was not in cache, all the sharded collections will be in the * 'needsRefresh' state. */ - StatusWith<CachedDatabaseInfo> getDatabase(OperationContext* opCtx, StringData dbName); + StatusWith<CachedDatabaseInfo> getDatabase(OperationContext* opCtx, + StringData dbName, + bool allowLocks = false); /** * Blocking method to get the routing information for a specific collection at a given cluster @@ -257,6 +254,13 @@ public: void report(BSONObjBuilder* builder) const; /** + * Checks if the current operation was ever marked as needing refresh. If the curent operation + * was marked as needing refresh, updates the relevant counters inside the Stats struct. + */ + void checkAndRecordOperationBlockedByRefresh(OperationContext* opCtx, mongo::LogicalOp opType); + + + /** * Non-blocking method that marks the current database entry for the dbName as needing * refresh. Will cause all further targetting attempts to block on a catalog cache refresh, * even if they do not require causal consistency. @@ -332,13 +336,6 @@ private: void _updateRefreshesStats(bool isIncremental, bool add); }; - // Callers of this internal function that are passing allowLocks must handle allowLocks failures - // by checking for ErrorCodes::ShardCannotRefreshDueToLocksHeld and addint the full namespace to - // the exception. - StatusWith<CachedDatabaseInfo> _getDatabase(OperationContext* opCtx, - StringData dbName, - bool allowLocks = false); - StatusWith<ChunkManager> _getCollectionRoutingInfoAt(OperationContext* opCtx, const NamespaceString& nss, boost::optional<Timestamp> atClusterTime, @@ -366,6 +363,18 @@ private: // combined AtomicWord<long long> totalRefreshWaitTimeMicros{0}; + // Cumulative, always-increasing counter of how many operations have been blocked by a + // catalog cache refresh. Broken down by operation type to match the operations tracked + // by the OpCounters class. + struct OperationsBlockedByRefresh { + AtomicWord<long long> countAllOperations{0}; + AtomicWord<long long> countInserts{0}; + AtomicWord<long long> countQueries{0}; + AtomicWord<long long> countUpdates{0}; + AtomicWord<long long> countDeletes{0}; + AtomicWord<long long> countCommands{0}; + } operationsBlockedByRefresh; + /** * Reports the accumulated statistics for serverStatus. */ diff --git a/src/mongo/s/catalog_cache_loader.h b/src/mongo/s/catalog_cache_loader.h index af2bdb4045b..18a26324fbc 100644 --- a/src/mongo/s/catalog_cache_loader.h +++ b/src/mongo/s/catalog_cache_loader.h @@ -126,11 +126,6 @@ public: virtual void onStepUp() = 0; /** - * Interrupts ongoing refreshes on rollback. - */ - virtual void onReplicationRollback() = 0; - - /** * Transitions into shut down and cleans up state. Once this transitions to shut down, should * not be able to transition back to normal. Should be safe to be called more than once. */ diff --git a/src/mongo/s/catalog_cache_loader_mock.cpp b/src/mongo/s/catalog_cache_loader_mock.cpp index ffca2e3786e..09b3b60e3f4 100644 --- a/src/mongo/s/catalog_cache_loader_mock.cpp +++ b/src/mongo/s/catalog_cache_loader_mock.cpp @@ -61,10 +61,6 @@ void CatalogCacheLoaderMock::onStepUp() { MONGO_UNREACHABLE; } -void CatalogCacheLoaderMock::onReplicationRollback() { - MONGO_UNREACHABLE; -} - void CatalogCacheLoaderMock::shutDown() {} void CatalogCacheLoaderMock::notifyOfCollectionVersionUpdate(const NamespaceString& nss) { diff --git a/src/mongo/s/catalog_cache_loader_mock.h b/src/mongo/s/catalog_cache_loader_mock.h index dd015b0258d..13538c4f69c 100644 --- a/src/mongo/s/catalog_cache_loader_mock.h +++ b/src/mongo/s/catalog_cache_loader_mock.h @@ -52,7 +52,6 @@ public: void initializeReplicaSetRole(bool isPrimary) override; void onStepDown() override; void onStepUp() override; - void onReplicationRollback() override; void shutDown() override; void notifyOfCollectionVersionUpdate(const NamespaceString& nss) override; void waitForCollectionFlush(OperationContext* opCtx, const NamespaceString& nss) override; diff --git a/src/mongo/s/catalog_cache_refresh_test.cpp b/src/mongo/s/catalog_cache_refresh_test.cpp index 39c8e4b13cd..c958dec98fb 100644 --- a/src/mongo/s/catalog_cache_refresh_test.cpp +++ b/src/mongo/s/catalog_cache_refresh_test.cpp @@ -33,7 +33,6 @@ #include "mongo/db/concurrency/locker_noop.h" #include "mongo/db/pipeline/aggregation_request_helper.h" -#include "mongo/db/query/cursor_response.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/catalog/type_collection.h" #include "mongo/s/catalog/type_database_gen.h" diff --git a/src/mongo/s/catalog_cache_test.cpp b/src/mongo/s/catalog_cache_test.cpp index 61a6ca2b067..be608cbb68c 100644 --- a/src/mongo/s/catalog_cache_test.cpp +++ b/src/mongo/s/catalog_cache_test.cpp @@ -33,12 +33,9 @@ #include <boost/optional/optional_io.hpp> -#include "mongo/db/cursor_id.h" -#include "mongo/db/query/cursor_response.h" #include "mongo/s/catalog/type_database_gen.h" #include "mongo/s/catalog_cache.h" #include "mongo/s/catalog_cache_loader_mock.h" -#include "mongo/s/shard_cannot_refresh_due_to_locks_held_exception.h" #include "mongo/s/sharding_router_test_fixture.h" #include "mongo/s/stale_exception.h" #include "mongo/s/type_collection_common_types_gen.h" @@ -302,69 +299,6 @@ TEST_F(CatalogCacheTest, OnStaleShardVersionWithGraterVersion) { ASSERT(status == ErrorCodes::InternalError); } -TEST_F(CatalogCacheTest, GetCollectionRoutingInfoAllowLocksReturnsImmediately) { - const auto dbVersion = DatabaseVersion(UUID::gen(), Timestamp(1, 1)); - const auto cachedCollVersion = ChunkVersion(1, 0, OID::gen(), Timestamp(1, 1)); - - loadDatabases({DatabaseType(kNss.db().toString(), kShards[0], dbVersion)}); - loadCollection(cachedCollVersion); - - const auto swCm = - _catalogCache->getCollectionRoutingInfo(operationContext(), kNss, true /* allowLocks */); - ASSERT_OK(swCm.getStatus()); - - ASSERT(swCm.getValue().getVersion() == cachedCollVersion); -} - -TEST_F(CatalogCacheTest, GetCollectionRoutingInfoAllowLocksNeedsToFetchNewCollInfo) { - const auto dbVersion = DatabaseVersion(UUID::gen(), Timestamp(1, 1)); - const auto cachedCollVersion = ChunkVersion(1, 0, OID::gen(), Timestamp(1, 1)); - const auto wantedCollVersion = - ChunkVersion(2, 0, cachedCollVersion.epoch(), cachedCollVersion.getTimestamp()); - - loadDatabases({DatabaseType(kNss.db().toString(), kShards[0], dbVersion)}); - loadCollection(cachedCollVersion); - _catalogCache->invalidateShardOrEntireCollectionEntryForShardedCollection( - kNss, wantedCollVersion, kShards[0]); - - { - FailPointEnableBlock failPoint("blockCollectionCacheLookup"); - - const auto status = - _catalogCache->getCollectionRoutingInfo(operationContext(), kNss, true /* allowLocks */) - .getStatus(); - - ASSERT(status == ErrorCodes::ShardCannotRefreshDueToLocksHeld); - auto refreshInfo = status.extraInfo<ShardCannotRefreshDueToLocksHeldInfo>(); - ASSERT(refreshInfo); - } - // Cancel ongoing refresh - _catalogCache->invalidateCollectionEntry_LINEARIZABLE(kNss); -} - -TEST_F(CatalogCacheTest, GetCollectionRoutingInfoAllowLocksNeedsToFetchNewDBInfo) { - const auto dbVersion = DatabaseVersion(UUID::gen(), Timestamp(1, 1)); - const auto cachedCollVersion = ChunkVersion(1, 0, OID::gen(), Timestamp(1, 1)); - - loadDatabases({DatabaseType(kNss.db().toString(), kShards[0], dbVersion)}); - loadCollection(cachedCollVersion); - _catalogCache->invalidateDatabaseEntry_LINEARIZABLE(kNss.db()); - - { - FailPointEnableBlock failPoint("blockDatabaseCacheLookup"); - - const auto status = - _catalogCache->getCollectionRoutingInfo(operationContext(), kNss, true /* allowLocks */) - .getStatus(); - - ASSERT(status == ErrorCodes::ShardCannotRefreshDueToLocksHeld); - auto refreshInfo = status.extraInfo<ShardCannotRefreshDueToLocksHeldInfo>(); - ASSERT(refreshInfo); - } - // Cancel ongoing refresh - _catalogCache->invalidateDatabaseEntry_LINEARIZABLE(kNss.db()); -} - TEST_F(CatalogCacheTest, TimeseriesFieldsAreProperlyPropagatedOnCC) { const auto dbVersion = DatabaseVersion(UUID::gen(), Timestamp(1, 1)); const auto epoch = OID::gen(); diff --git a/src/mongo/s/catalog_cache_test_fixture.cpp b/src/mongo/s/catalog_cache_test_fixture.cpp index 9edd553f704..6e66a30d6b2 100644 --- a/src/mongo/s/catalog_cache_test_fixture.cpp +++ b/src/mongo/s/catalog_cache_test_fixture.cpp @@ -38,9 +38,7 @@ #include "mongo/client/remote_command_targeter_factory_mock.h" #include "mongo/client/remote_command_targeter_mock.h" #include "mongo/db/client.h" -#include "mongo/db/cursor_id.h" #include "mongo/db/query/collation/collator_factory_mock.h" -#include "mongo/db/query/cursor_response.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/catalog/type_collection.h" #include "mongo/s/catalog/type_database_gen.h" diff --git a/src/mongo/s/chunk.cpp b/src/mongo/s/chunk.cpp index e141cfea226..e59c9143ecf 100644 --- a/src/mongo/s/chunk.cpp +++ b/src/mongo/s/chunk.cpp @@ -107,32 +107,16 @@ void ChunkInfo::throwIfMovedSince(const Timestamp& ts) const { } bool ChunkInfo::containsKey(const BSONObj& shardKey) const { - return _range.containsKey(shardKey); + return getMin().woCompare(shardKey) <= 0 && shardKey.woCompare(getMax()) < 0; } std::string ChunkInfo::toString() const { - return toBSON().toString(); -} - -BSONObj ChunkInfo::toBSON() const { - BSONObjBuilder bob; - _range.append(&bob); - bob.append("maxKeyString", _maxKeyString); - bob.append("shardId", _shardId); - _lastmod.serializeToBSON("lastmod", &bob); - bob.append("jumbo", _jumbo.load()); - bob.append("bytesWritten", (long long)_writesTracker->getBytesWritten()); - - BSONArrayBuilder historyArr{bob.subarrayStart("history")}; - for (const auto& historyEntry : _history) { - historyArr.append(historyEntry.toBSON()); - } - historyArr.doneFast(); - return bob.obj(); + return str::stream() << ChunkType::shard() << ": " << _shardId << ", " << ChunkType::lastmod() + << ": " << _lastmod.toString() << ", " << _range.toString(); } void ChunkInfo::markAsJumbo() { - _jumbo.store(true); + _jumbo = true; } void Chunk::throwIfMoved() const { @@ -143,16 +127,4 @@ void Chunk::throwIfMoved() const { _chunkInfo.throwIfMovedSince(*_atClusterTime); } -std::string Chunk::toString() const { - return toBSON().toString(); -} - -BSONObj Chunk::toBSON() const { - BSONObjBuilder bob; - bob.append("chunkInfo", _chunkInfo.toBSON()); - bob.append("atClusterTime", _atClusterTime ? _atClusterTime->toBSON() : BSONObj()); - return bob.obj(); -} - - } // namespace mongo diff --git a/src/mongo/s/chunk.h b/src/mongo/s/chunk.h index 16f5909a72a..3f60e997737 100644 --- a/src/mongo/s/chunk.h +++ b/src/mongo/s/chunk.h @@ -29,7 +29,6 @@ #pragma once -#include "mongo/platform/atomic_word.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/chunk_version.h" #include "mongo/s/shard_id.h" @@ -93,7 +92,7 @@ public: } bool isJumbo() const { - return _jumbo.load(); + return _jumbo; } /** @@ -108,8 +107,6 @@ public: */ std::string toString() const; - BSONObj toBSON() const; - // Returns true if this chunk contains the given shard key, and false otherwise // // Note: this function takes an extracted *key*, not an original document (the point may be @@ -133,7 +130,7 @@ private: // Indicates whether this chunk should be treated as jumbo and not attempted to be moved or // split - AtomicWord<bool> _jumbo; + mutable bool _jumbo; // Used for tracking writes to this chunk, to estimate its size for the autosplitter. Since // ChunkInfo objects are always treated as const, and this contains metadata about the chunk @@ -192,8 +189,9 @@ public: /** * Returns a string represenation of the chunk for logging. */ - std::string toString() const; - BSONObj toBSON() const; + std::string toString() const { + return _chunkInfo.toString(); + } // Returns true if this chunk contains the given shard key, and false otherwise // diff --git a/src/mongo/s/chunk_manager.cpp b/src/mongo/s/chunk_manager.cpp index 1d3ed85e925..888ba644b52 100644 --- a/src/mongo/s/chunk_manager.cpp +++ b/src/mongo/s/chunk_manager.cpp @@ -33,8 +33,6 @@ #include "mongo/s/chunk_manager.h" -#include <algorithm> - #include "mongo/bson/simple_bsonobj_comparator.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/query/collation/collation_index_key.h" @@ -65,42 +63,18 @@ void checkAllElementsAreOfType(BSONType type, const BSONObj& o) { allElementsAreOfType(type, o)); } -bool overlaps(const ChunkInfo& a, const ChunkInfo& b) { - // Microbenchmarks results showed that comparing keystrings - // is more performant than comparing BSONObj - const auto aMinKeyStr = ShardKeyPattern::toKeyString(a.getMin()); - const auto& aMaxKeyStr = a.getMaxKeyString(); - const auto bMinKeyStr = ShardKeyPattern::toKeyString(b.getMin()); - const auto& bMaxKeyStr = b.getMaxKeyString(); - - return aMinKeyStr < bMaxKeyStr && aMaxKeyStr > bMinKeyStr; -} - -void checkChunksAreContiguous(const ChunkInfo& left, const ChunkInfo& right) { - const auto& leftKeyString = left.getMaxKeyString(); - const auto rightKeyString = ShardKeyPattern::toKeyString(right.getMin()); - if (leftKeyString == rightKeyString) { - return; - } - - if (SimpleBSONObjComparator::kInstance.evaluate(left.getMax() < right.getMin())) { - uasserted(ErrorCodes::ConflictingOperationInProgress, - str::stream() << "Gap exists in the routing table between chunks " - << left.getRange().toString() << " and " - << right.getRange().toString()); +void appendChunkTo(std::vector<std::shared_ptr<ChunkInfo>>& chunks, + const std::shared_ptr<ChunkInfo>& chunk) { + if (!chunks.empty() && chunk->getRange().overlaps(chunks.back()->getRange())) { + if (chunks.back()->getLastmod().isOlderThan(chunk->getLastmod())) { + chunks.pop_back(); + chunks.push_back(chunk); + } } else { - uasserted(ErrorCodes::ConflictingOperationInProgress, - str::stream() << "Overlap exists in the routing table between chunks " - << left.getRange().toString() << " and " - << right.getRange().toString()); + chunks.push_back(chunk); } - - MONGO_UNREACHABLE; } -using ChunkVector = ChunkMap::ChunkVector; -using ChunkVectorMap = ChunkMap::ChunkVectorMap; - // This function processes the passed in chunks by removing the older versions of any overlapping // chunks. The resulting chunks must be ordered by the maximum bound and not have any // overlapping chunks. In order to process the original set of chunks correctly which may have @@ -125,18 +99,10 @@ std::vector<std::shared_ptr<ChunkInfo>> flatten(const std::vector<ChunkType>& ch std::vector<std::shared_ptr<ChunkInfo>> flattened; flattened.reserve(changedChunkInfos.size()); - flattened.emplace_back(std::move(changedChunkInfos[0])); + flattened.push_back(changedChunkInfos[0]); for (size_t i = 1; i < changedChunkInfos.size(); ++i) { - auto& chunk = changedChunkInfos[i]; - if (overlaps(*chunk, *flattened.back())) { - if (flattened.back()->getLastmod().isOlderThan(chunk->getLastmod())) { - flattened.pop_back(); - flattened.emplace_back(std::move(chunk)); - } - } else { - flattened.emplace_back(std::move(chunk)); - } + appendChunkTo(flattened, changedChunkInfos[i]); } std::reverse(flattened.begin(), flattened.end()); @@ -144,489 +110,205 @@ std::vector<std::shared_ptr<ChunkInfo>> flatten(const std::vector<ChunkType>& ch return flattened; } -} // namespace - -size_t ChunkMap::size() const { - size_t totalChunks{0}; - for (const auto& mapIt : _chunkVectorMap) { - totalChunks += mapIt.second->size(); - } - return totalChunks; -} - -std::shared_ptr<ChunkInfo> ChunkMap::findIntersectingChunk(const BSONObj& shardKey) const { - const auto shardKeyString = ShardKeyPattern::toKeyString(shardKey); - - const auto it = _chunkVectorMap.upper_bound(shardKeyString); - if (it == _chunkVectorMap.end()) { - // upper_bound() will miss the last chunkVector if shardKey is actually the MaxKey, - // thus we need to check explicitly if shardKey is contained in the last chunk. - if (const auto& lastChunk = std::prev(_chunkVectorMap.end())->second->back(); - lastChunk->containsKey(shardKey)) { - return lastChunk; - } else { - return {}; - } - } - - const auto& chunkVector = *(it->second); - const auto chunkIt = _findIntersectingChunkIterator( - shardKeyString, chunkVector.begin(), chunkVector.end(), true /*isMaxInclusive*/); - - if (chunkIt == chunkVector.end()) { - return {}; - } +void validateChunkIsNotOlderThan(const std::shared_ptr<ChunkInfo>& chunk, + const ChunkVersion& version) { + uassert(ErrorCodes::ConflictingOperationInProgress, + str::stream() << "Changed chunk " << chunk->toString() + << " has timestamp different from that of the collection " + << version.getTimestamp(), + version.getTimestamp() == chunk->getLastmod().getTimestamp()); - return *chunkIt; + uassert(626840, + str::stream() + << "Changed chunk " << chunk->toString() + << " doesn't have version that's greater or equal than that of the collection " + << version.toString(), + version.isOlderOrEqualThan(chunk->getLastmod())); } -ChunkMap ChunkMap::createMerged(std::vector<std::shared_ptr<ChunkInfo>> changedChunks) const { - auto updatedChunkMap = _makeUpdated(std::move(changedChunks)); - tassert(6752900, - "Chunk map found to be empty after refresh", - updatedChunkMap._chunkVectorMap.size() && - updatedChunkMap._chunkVectorMap.begin()->second->size()); - return updatedChunkMap; -} - -void ChunkMap::_commitUpdatedChunkVector(std::shared_ptr<ChunkVector>&& chunkVectorPtr, - bool checkMaxKeyConsistency) { - - invariant(!chunkVectorPtr->empty()); - - const auto& vectorMaxKeyString = chunkVectorPtr->back()->getMaxKeyString(); - const auto nextMapIt = _chunkVectorMap.lower_bound(vectorMaxKeyString); +} // namespace - // Check lower bound is consistent - if (nextMapIt == _chunkVectorMap.begin()) { - checkAllElementsAreOfType(MinKey, chunkVectorPtr->front()->getMin()); - } else { - checkChunksAreContiguous(*(std::prev(nextMapIt)->second->back()), - *(chunkVectorPtr->front())); - } +ShardVersionMap ChunkMap::constructShardVersionMap() const { + ShardVersionMap shardVersions; + ChunkVector::const_iterator current = _chunkMap.cbegin(); + + boost::optional<BSONObj> firstMin = boost::none; + boost::optional<BSONObj> lastMax = boost::none; + + while (current != _chunkMap.cend()) { + const auto& firstChunkInRange = *current; + const auto& currentRangeShardId = firstChunkInRange->getShardIdAt(boost::none); + + // Tracks the max shard version for the shard on which the current range will reside + auto shardVersionIt = shardVersions.find(currentRangeShardId); + if (shardVersionIt == shardVersions.end()) { + shardVersionIt = shardVersions + .emplace(std::piecewise_construct, + std::forward_as_tuple(currentRangeShardId), + std::forward_as_tuple(_collectionVersion.epoch(), + _collectionVersion.getTimestamp())) + .first; + } - if (checkMaxKeyConsistency) { - // Check upper bound is consistent - if (nextMapIt == _chunkVectorMap.end()) { - checkAllElementsAreOfType(MaxKey, chunkVectorPtr->back()->getMax()); - } else { - checkChunksAreContiguous(*(chunkVectorPtr->back()), *(nextMapIt->second->front())); + auto& maxShardVersion = shardVersionIt->second.shardVersion; + + current = + std::find_if(current, + _chunkMap.cend(), + [¤tRangeShardId, &maxShardVersion](const auto& currentChunk) { + if (currentChunk->getShardIdAt(boost::none) != currentRangeShardId) + return true; + + if (maxShardVersion.isOlderThan(currentChunk->getLastmod())) + maxShardVersion = currentChunk->getLastmod(); + + return false; + }); + + const auto rangeLast = *std::prev(current); + + const auto& rangeMin = firstChunkInRange->getMin(); + const auto& rangeMax = rangeLast->getMax(); + + // Check the continuity of the chunks map + if (lastMax && !SimpleBSONObjComparator::kInstance.evaluate(*lastMax == rangeMin)) { + if (SimpleBSONObjComparator::kInstance.evaluate(*lastMax < rangeMin)) + uasserted(ErrorCodes::ConflictingOperationInProgress, + str::stream() << "Gap exists in the routing table between chunks " + << findIntersectingChunk(*lastMax)->getRange().toString() + << " and " << rangeLast->getRange().toString()); + else + uasserted(ErrorCodes::ConflictingOperationInProgress, + str::stream() << "Overlap exists in the routing table between chunks " + << findIntersectingChunk(*lastMax)->getRange().toString() + << " and " << rangeLast->getRange().toString()); } - } - auto minVectorSize = _maxChunkVectorSize / 2; - if (chunkVectorPtr->size() < minVectorSize) { - _mergeAndCommitUpdatedChunkVector(nextMapIt, std::move(chunkVectorPtr)); - } else { - _splitAndCommitUpdatedChunkVector(nextMapIt, std::move(chunkVectorPtr)); - } -} + if (!firstMin) + firstMin = rangeMin; -void ChunkMap::_mergeAndCommitUpdatedChunkVector(ChunkVectorMap::const_iterator pos, - std::shared_ptr<ChunkVector>&& smallVectorPtr) { - if (pos == _chunkVectorMap.begin()) { - // Vector will be placed at the head of the map, - // thus there is not previous vector we could merge with - smallVectorPtr->shrink_to_fit(); - _chunkVectorMap.emplace_hint( - pos, smallVectorPtr->back()->getMaxKeyString(), std::move(smallVectorPtr)); + lastMax = rangeMax; - return; + // If a shard has chunks it must have a shard version, otherwise we have an invalid chunk + // somewhere, which should have been caught at chunk load time + invariant(maxShardVersion.isSet()); } - auto prevVectorPtr = _chunkVectorMap.extract(std::prev(pos)).mapped(); - auto mergeVectorPtr = std::make_shared<ChunkVector>(); - mergeVectorPtr->reserve(prevVectorPtr->size() + smallVectorPtr->size()); - - // Fill initial part of merged vector with a copy of old vector (prevVectorPtr) - // Note that the old vector is potentially shared with previous ChunkMap instances, - // thus we copy rather than moving elements to maintain its integrity. - mergeVectorPtr->insert(mergeVectorPtr->end(), prevVectorPtr->begin(), prevVectorPtr->end()); - - // Fill the rest of merged vector with the small updated vector - mergeVectorPtr->insert(mergeVectorPtr->end(), - std::make_move_iterator(smallVectorPtr->begin()), - std::make_move_iterator(smallVectorPtr->end())); + if (!_chunkMap.empty()) { + invariant(!shardVersions.empty()); + invariant(firstMin.is_initialized()); + invariant(lastMax.is_initialized()); - _chunkVectorMap.emplace_hint( - pos, mergeVectorPtr->back()->getMaxKeyString(), std::move(mergeVectorPtr)); -} - -/* - * Split the given chunk vector into pieces not bigger than _maxChunkVectorSize - * and add them to the chunkVector map. - * - * When chunks can't be divided equally among all generated pieces, - * this algorithm guarantee that the size difference between all pieces will be minimal and that - * smaller pieces will be placed at the end. - */ -void ChunkMap::_splitAndCommitUpdatedChunkVector(ChunkVectorMap::const_iterator pos, - std::shared_ptr<ChunkVector>&& chunkVectorPtr) { - auto& chunkVector = *chunkVectorPtr; - const long long totalSize = chunkVector.size(); - const long long numPieces = (totalSize + _maxChunkVectorSize - 1) / _maxChunkVectorSize; - const long long largePieceSize = (totalSize + numPieces - 1) / numPieces; - const long long numLargePieces = totalSize % numPieces; - const long long smallPieceSize = totalSize / numPieces; - - auto lastPos = pos; - auto chunkIt = chunkVector.end(); - for (int pieceCount = 1; pieceCount < numPieces; pieceCount++) { - auto tmpVectorPtr = std::make_shared<ChunkVector>(); - auto targetPieceSize = - (numPieces - pieceCount) < numLargePieces ? largePieceSize : smallPieceSize; - tmpVectorPtr->insert(tmpVectorPtr->end(), - std::make_move_iterator(chunkIt - targetPieceSize), - std::make_move_iterator(chunkIt)); - chunkIt -= targetPieceSize; - lastPos = _chunkVectorMap.emplace_hint( - lastPos, tmpVectorPtr->back()->getMaxKeyString(), std::move(tmpVectorPtr)); + checkAllElementsAreOfType(MinKey, firstMin.get()); + checkAllElementsAreOfType(MaxKey, lastMax.get()); } - invariant(std::distance(chunkVector.begin(), chunkIt) == largePieceSize); - chunkVector.resize(largePieceSize); - chunkVector.shrink_to_fit(); - _chunkVectorMap.emplace_hint( - lastPos, chunkVector.back()->getMaxKeyString(), std::move(chunkVectorPtr)); + return shardVersions; } -void ChunkMap::_updateShardVersionFromDiscardedChunk(const ChunkInfo& chunk) { - auto shardVersionIt = _shardVersions.find(chunk.getShardId()); - if (shardVersionIt != _shardVersions.end() && - shardVersionIt->second.shardVersion == chunk.getLastmod()) { - _shardVersions.erase(shardVersionIt); +void ChunkMap::appendChunk(const std::shared_ptr<ChunkInfo>& chunk) { + appendChunkTo(_chunkMap, chunk); + const auto chunkVersion = chunk->getLastmod(); + if (_collectionVersion.isOlderThan(chunkVersion)) { + _collectionVersion = ChunkVersion(chunkVersion.majorVersion(), + chunkVersion.minorVersion(), + chunkVersion.epoch(), + _collTimestamp); } } -void ChunkMap::_updateShardVersionFromUpdateChunk(const ChunkInfo& chunk, - const ShardVersionMap& oldShardVersions) { - const auto& newVersion = chunk.getLastmod(); - const auto newValidAfter = [&] { - auto thisChunkValidAfter = chunk.getHistory().empty() - ? Timestamp{0, 0} - : chunk.getHistory().front().getValidAfter(); - - auto oldShardVersionIt = oldShardVersions.find(chunk.getShardId()); - auto oldShardValidAfter = oldShardVersionIt == oldShardVersions.end() - ? Timestamp{0, 0} - : oldShardVersionIt->second.validAfter; - - return std::max(thisChunkValidAfter, oldShardValidAfter); - }(); - - // Version for this chunk shard got updated - bool versionUpdated{false}; - - auto [shardVersionIt, created] = - _shardVersions.try_emplace(chunk.getShardId(), newVersion, newValidAfter); - - if (created) { - // We just created a new entry in the _shardVersions map with latest version and latest - // valid after. - versionUpdated = true; - } else { - // _shardVersions map already contained an entry for this chunk shard - - // Update version for this shard - if (shardVersionIt->second.shardVersion.isOlderThan(newVersion)) { - shardVersionIt->second.shardVersion = newVersion; - versionUpdated = true; - } +std::shared_ptr<ChunkInfo> ChunkMap::findIntersectingChunk(const BSONObj& shardKey) const { + const auto it = _findIntersectingChunk(shardKey); - // Update validAfter for this shard - if (newValidAfter > shardVersionIt->second.validAfter) { - shardVersionIt->second.validAfter = newValidAfter; - } - } + if (it != _chunkMap.end()) + return *it; - // Update version for the entire collection - if (versionUpdated && _collectionVersion.isOlderThan(newVersion)) { - _collectionVersion = ChunkVersion{newVersion.majorVersion(), - newVersion.minorVersion(), - newVersion.epoch(), - _collectionVersion.getTimestamp()}; - } + return std::shared_ptr<ChunkInfo>(); } -ChunkMap ChunkMap::_makeUpdated(ChunkVector&& updateChunks) const { - ChunkMap newMap{*this}; +ChunkMap ChunkMap::createMerged( + const std::vector<std::shared_ptr<ChunkInfo>>& changedChunks) const { + size_t chunkMapIndex = 0; + size_t changedChunkIndex = 0; - if (updateChunks.empty()) { - // No updates, just clone the original map - return newMap; - } + ChunkMap updatedChunkMap( + getVersion().epoch(), getVersion().getTimestamp(), _chunkMap.size() + changedChunks.size()); - std::shared_ptr<ChunkVector> oldVectorPtr; - ChunkVector::const_iterator oldChunkIt; - ChunkVector::iterator updateChunkIt; - ChunkVector::const_iterator updateChunkWrittenBytesIt; - std::shared_ptr<ChunkVector> newVectorPtr; - bool lastCommittedIsNew; - - const auto processOldChunk = [&](const std::shared_ptr<ChunkInfo>& nextChunkPtr, - bool discard = false) { - if (discard) { - // Discard chunk from oldVector - - while (updateChunkWrittenBytesIt != updateChunks.end() && - overlaps(*nextChunkPtr, **updateChunkWrittenBytesIt)) { - // Copy writtenBytes to all new overlapping chunks - (*updateChunkWrittenBytesIt++) - ->getWritesTracker() - ->addBytesWritten(nextChunkPtr->getWritesTracker()->getBytesWritten()); - } - - newMap._updateShardVersionFromDiscardedChunk(*nextChunkPtr); - // Since we are discarding the old chunk rather than committing, - // we do not update `lastCommitedIsNew` flag. - } else { - if (!newVectorPtr->empty() && lastCommittedIsNew) { - checkChunksAreContiguous(*newVectorPtr->back(), *nextChunkPtr); - } - lastCommittedIsNew = false; - newVectorPtr->emplace_back(nextChunkPtr); - } - }; - - const auto processUpdateChunk = [&](std::shared_ptr<ChunkInfo>&& nextChunkPtr) { - newMap._updateShardVersionFromUpdateChunk(*nextChunkPtr, _shardVersions); - uassert(ErrorCodes::ConflictingOperationInProgress, - str::stream() << "Changed chunk " << nextChunkPtr->toString() - << " has timestamp different from that of the collection " - << _collectionVersion.getTimestamp(), - nextChunkPtr->getLastmod().getTimestamp() == _collectionVersion.getTimestamp()); - - uassert(626840, - str::stream() - << "Changed chunk " << nextChunkPtr->toString() - << " doesn't have version that's greater or equal than that of the collection " - << _collectionVersion.toString(), - _collectionVersion.isOlderOrEqualThan(nextChunkPtr->getLastmod())); - - if (!newVectorPtr->empty()) { - checkChunksAreContiguous(*newVectorPtr->back(), *nextChunkPtr); - } - lastCommittedIsNew = true; - newVectorPtr->emplace_back(std::move(nextChunkPtr)); - }; - - const auto processOneChunk = [&] { - dassert(oldChunkIt != oldVectorPtr->end() || updateChunkIt != updateChunks.end()); - if (updateChunkIt == updateChunks.end()) { - // no more updates - processOldChunk(*(oldChunkIt++)); - return; - } - if (oldChunkIt == oldVectorPtr->end()) { - // No more old chunks - processUpdateChunk(std::move(*(updateChunkIt++))); - return; + while (chunkMapIndex < _chunkMap.size() || changedChunkIndex < changedChunks.size()) { + if (chunkMapIndex >= _chunkMap.size()) { + validateChunkIsNotOlderThan(changedChunks[changedChunkIndex], getVersion()); + updatedChunkMap.appendChunk(changedChunks[changedChunkIndex++]); + continue; } - const auto& oldChunk = **oldChunkIt; - auto& updateChunk = **updateChunkIt; - - // We have both update and old chunk to peak from - // If they overlaps we discard the old chunk otherwise we process the one with smaller key - if (overlaps(updateChunk, oldChunk)) { - processOldChunk(*(oldChunkIt++), true /* discard */); - return; - } else { - // Ranges do not overlap so we yield the chunk with smaller max key - if (updateChunk.getMaxKeyString() < oldChunk.getMaxKeyString()) { - processUpdateChunk(std::move(*(updateChunkIt++))); - return; - } else { - processOldChunk(*(oldChunkIt++)); - return; - } + if (changedChunkIndex >= changedChunks.size()) { + updatedChunkMap.appendChunk(_chunkMap[chunkMapIndex++]); + continue; } - }; - - updateChunkIt = updateChunks.begin(); - updateChunkWrittenBytesIt = updateChunkIt; - // Skip first vectors that were not affected by this update since we don't need to modify them - auto mapIt = newMap._chunkVectorMap.upper_bound( - ShardKeyPattern::toKeyString((*updateChunkIt)->getRange().getMin())); - oldVectorPtr = - mapIt != newMap._chunkVectorMap.end() ? mapIt->second : std::make_shared<ChunkVector>(); - oldChunkIt = oldVectorPtr->begin(); - // Prepare newVector used as destination of merge sort algorithm - newVectorPtr = std::make_shared<ChunkVector>(); - newVectorPtr->reserve(mapIt != newMap._chunkVectorMap.end() - ? oldVectorPtr->size() - : std::min(_maxChunkVectorSize, updateChunks.size())); - lastCommittedIsNew = false; - - // Iterate until we drained all updates and old vectors - while (updateChunkIt != updateChunks.end() || mapIt != newMap._chunkVectorMap.end()) { - processOneChunk(); - - // Keep processing chunks until we reach the end of the current old vector - if (oldChunkIt == oldVectorPtr->end()) { - if (mapIt == newMap._chunkVectorMap.end()) { - // Only updates left - if (newVectorPtr->size() >= _maxChunkVectorSize) { - auto checkMaxKeyConsistency = updateChunkIt == updateChunks.end(); - newMap._commitUpdatedChunkVector(std::move(newVectorPtr), - checkMaxKeyConsistency); - newVectorPtr = std::make_shared<ChunkVector>(); - // Allocate space only for the remaining updates - newVectorPtr->reserve(newVectorPtr->size() + - std::min(_maxChunkVectorSize, - static_cast<size_t>(std::distance( - updateChunkIt, updateChunks.end())))); - } - } else { - // drained all chunks from old vector in use, - // remove old vector from the new map since we are going to replace it. - auto followingMapIt = newMap._chunkVectorMap.erase(mapIt); - - // Advance the map iterator to the next old vector to update - mapIt = [&] { - if (followingMapIt == newMap._chunkVectorMap.end()) { - // No more old vector to process - return newMap._chunkVectorMap.end(); - } - if (updateChunkIt == updateChunks.end()) { - // No more updates skip all remaining vectors - return newMap._chunkVectorMap.end(); - } + auto overlap = _chunkMap[chunkMapIndex]->getRange().overlaps( + changedChunks[changedChunkIndex]->getRange()); - if (newVectorPtr->size() < _maxChunkVectorSize / 2) { - // New vector is too small, keep accumulating next oldVector - return followingMapIt; - } + if (overlap) { + auto& changedChunk = changedChunks[changedChunkIndex++]; + auto& chunkInfo = _chunkMap[chunkMapIndex]; - // next update doesn't overlap with current old vector so we need to jump - // forward to the first overlapping old vector. - // This is an optimization to skip vectors that are not affected by any updates. - auto nextOvelappingMapIt = newMap._chunkVectorMap.upper_bound( - ShardKeyPattern::toKeyString((*updateChunkIt)->getRange().getMin())); - invariant(nextOvelappingMapIt != newMap._chunkVectorMap.end()); - return nextOvelappingMapIt; - }(); - - // Commit chunks accumulated in new vector if - // - We are skipping next old vector, thus next old chunk is not adjacent to last - // committed chunk - // - We already reached maxChunkSize and next update is not adjacent to last - // committed chunk - if (mapIt != followingMapIt || - (newVectorPtr->size() >= _maxChunkVectorSize && - (updateChunkIt == updateChunks.end() || - ShardKeyPattern::toKeyString((*updateChunkIt)->getRange().getMin()) != - newVectorPtr->back()->getMaxKeyString()))) { - newMap._commitUpdatedChunkVector(std::move(newVectorPtr), true); - newVectorPtr = std::make_shared<ChunkVector>(); - } + auto bytesInReplacedChunk = chunkInfo->getWritesTracker()->getBytesWritten(); + changedChunk->getWritesTracker()->addBytesWritten(bytesInReplacedChunk); - if (mapIt != newMap._chunkVectorMap.end()) { - // Update references to oldVector - oldVectorPtr = mapIt->second; - oldChunkIt = oldVectorPtr->begin(); - // Reserve space for next chunks, - // we cannot know before traversing the next old vector how many chunks will be - // added to the new vector, thus this reservation is just best effort. - newVectorPtr->reserve(newVectorPtr->size() + oldVectorPtr->size()); - } else { - // Only updates left, allocate space only for the remaining updates - newVectorPtr->reserve(newVectorPtr->size() + - std::min(_maxChunkVectorSize, - static_cast<size_t>(std::distance( - updateChunkIt, updateChunks.end())))); - } - } + validateChunkIsNotOlderThan(changedChunk, getVersion()); + updatedChunkMap.appendChunk(changedChunk); + } else { + updatedChunkMap.appendChunk(_chunkMap[chunkMapIndex++]); } } - if (!newVectorPtr->empty()) { - newMap._commitUpdatedChunkVector(std::move(newVectorPtr), true); - } - - return newMap; + return updatedChunkMap; } BSONObj ChunkMap::toBSON() const { BSONObjBuilder builder; getVersion().serializeToBSON("startingVersion"_sd, &builder); - builder.append("chunkCount", static_cast<int64_t>(size())); + builder.append("chunkCount", static_cast<int64_t>(_chunkMap.size())); { BSONArrayBuilder arrayBuilder(builder.subarrayStart("chunks"_sd)); - for (const auto& mapIt : _chunkVectorMap) { - for (const auto& chunkInfoPtr : *mapIt.second) { - arrayBuilder.append(chunkInfoPtr->toString()); - } + for (const auto& chunk : _chunkMap) { + arrayBuilder.append(chunk->toString()); } } return builder.obj(); } -std::string ChunkMap::toString() const { - StringBuilder sb; - - sb << "Bucket size: " << _maxChunkVectorSize << "\n"; - sb << "Num buckets: " << _chunkVectorMap.size() << "\n"; - sb << "Num chunks: " << size() << "\n"; - sb << "Chunks:\n"; - size_t vectorCount{0}; - for (const auto& mapIt : _chunkVectorMap) { - sb << "\t vector[" << vectorCount++ << "] key: " << mongo::base64::encode(mapIt.first) - << ", size: " << mapIt.second->size() << "\n"; - for (const auto& chunkInfoPtr : *mapIt.second) { - sb << "\t" << chunkInfoPtr->toString() << '\n'; - } - } - - sb << "Shard versions:\n"; - for (const auto& entry : _shardVersions) { - sb << "\t" << entry.first << ": " << entry.second.shardVersion.toString() << '\n'; - } - - sb << "Collection version:" << _collectionVersion.toString() << '\n'; - - return sb.str(); -} - -ChunkVector::const_iterator ChunkMap::_findIntersectingChunkIterator( - const std::string& shardKeyString, - ChunkVector::const_iterator first, - ChunkVector::const_iterator last, - bool isMaxInclusive) const { +ChunkMap::ChunkVector::const_iterator ChunkMap::_findIntersectingChunk(const BSONObj& shardKey, + bool isMaxInclusive) const { + auto shardKeyString = ShardKeyPattern::toKeyString(shardKey); if (!isMaxInclusive) { - return std::lower_bound(first, - last, - shardKeyString, - [&](const auto& chunkInfo, const std::string& shardKeyString) { + return std::lower_bound(_chunkMap.begin(), + _chunkMap.end(), + shardKey, + [&shardKeyString](const auto& chunkInfo, const BSONObj& shardKey) { return chunkInfo->getMaxKeyString() < shardKeyString; }); } else { - return std::upper_bound(first, - last, - shardKeyString, - [&](const std::string& shardKeyString, const auto& chunkInfo) { + return std::upper_bound(_chunkMap.begin(), + _chunkMap.end(), + shardKey, + [&shardKeyString](const BSONObj& shardKey, const auto& chunkInfo) { return shardKeyString < chunkInfo->getMaxKeyString(); }); } } - -std::pair<ChunkVectorMap::const_iterator, ChunkVectorMap::const_iterator> -ChunkMap::_overlappingVectorSlotBounds(const std::string& minShardKeyStr, - const std::string& maxShardKeyStr, - bool isMaxInclusive) const { - - const auto itMin = _chunkVectorMap.upper_bound(minShardKeyStr); +std::pair<ChunkMap::ChunkVector::const_iterator, ChunkMap::ChunkVector::const_iterator> +ChunkMap::_overlappingBounds(const BSONObj& min, const BSONObj& max, bool isMaxInclusive) const { + const auto itMin = _findIntersectingChunk(min); const auto itMax = [&]() { - auto it = isMaxInclusive ? _chunkVectorMap.upper_bound(maxShardKeyStr) - : _chunkVectorMap.lower_bound(maxShardKeyStr); - - return it == _chunkVectorMap.end() ? it : ++it; + auto it = _findIntersectingChunk(max, isMaxInclusive); + return it == _chunkMap.end() ? it : ++it; }(); return {itMin, itMax}; @@ -656,7 +338,7 @@ RoutingTableHistory::RoutingTableHistory( _maxChunkSizeBytes(maxChunkSizeBytes), _allowMigrations(allowMigrations), _chunkMap(std::move(chunkMap)), - _shardVersions(_chunkMap.getShardVersionsMap()) {} + _shardVersions(_chunkMap.constructShardVersionMap()) {} void RoutingTableHistory::setShardStale(const ShardId& shardId) { if (gEnableFinerGrainedCatalogCacheRefresh) { @@ -706,7 +388,7 @@ Chunk ChunkManager::findIntersectingChunk(const BSONObj& shardKey, uassert(ErrorCodes::ShardKeyNotFound, str::stream() << "Cannot target single shard using key " << shardKey << " for namespace " << _rt->optRt->nss(), - chunkInfo); + chunkInfo && chunkInfo->containsKey(shardKey)); return Chunk(*chunkInfo, _clusterTime); } @@ -719,14 +401,15 @@ bool ChunkManager::keyBelongsToShard(const BSONObj& shardKey, const ShardId& sha if (!chunkInfo) return false; + invariant(chunkInfo->containsKey(shardKey)); + return chunkInfo->getShardIdAt(_clusterTime) == shardId; } void ChunkManager::getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj& query, const BSONObj& collation, - std::set<ShardId>* shardIds, - bool bypassIsFieldHashedCheck) const { + std::set<ShardId>* shardIds) const { auto findCommand = std::make_unique<FindCommandRequest>(_rt->optRt->nss()); findCommand->setFilter(query.getOwned()); @@ -752,7 +435,7 @@ void ChunkManager::getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> e auto shardKeyToFind = _rt->optRt->getShardKeyPattern().extractShardKeyFromQuery(*cq); if (!shardKeyToFind.isEmpty()) { try { - auto chunk = findIntersectingChunk(shardKeyToFind, collation, bypassIsFieldHashedCheck); + auto chunk = findIntersectingChunk(shardKeyToFind, collation); shardIds->insert(chunk.getShardId()); return; } catch (const DBException&) { @@ -792,7 +475,7 @@ void ChunkManager::getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> e // For now, we satisfy that assumption by adding a shard with no matches rather than returning // an empty set of shards. if (shardIds->empty()) { - _rt->optRt->forEachChunk([&](const auto& chunkInfo) { + _rt->optRt->forEachChunk([&](const std::shared_ptr<ChunkInfo>& chunkInfo) { shardIds->insert(chunkInfo->getShardIdAt(_clusterTime)); return false; }); @@ -812,7 +495,7 @@ void ChunkManager::getShardIdsForRange(const BSONObj& min, return; } - _rt->optRt->forEachOverlappingChunk(min, max, true, [&](const auto& chunkInfo) { + _rt->optRt->forEachOverlappingChunk(min, max, true, [&](auto& chunkInfo) { shardIds->insert(chunkInfo->getShardIdAt(_clusterTime)); // No need to iterate through the rest of the ranges, because we already know we need to use @@ -832,7 +515,7 @@ bool ChunkManager::rangeOverlapsShard(const ChunkRange& range, const ShardId& sh bool overlapFound = false; _rt->optRt->forEachOverlappingChunk( - range.getMin(), range.getMax(), false, [&](const auto& chunkInfo) { + range.getMin(), range.getMax(), false, [&](auto& chunkInfo) { if (chunkInfo->getShardIdAt(_clusterTime) == shardId) { overlapFound = true; return false; @@ -849,7 +532,7 @@ boost::optional<Chunk> ChunkManager::getNextChunkOnShard(const BSONObj& shardKey boost::optional<Chunk> chunk; _rt->optRt->forEachChunk( - [&](const auto& chunkInfo) { + [&](auto& chunkInfo) { if (chunkInfo->getShardIdAt(_clusterTime) == shardId) { chunk.emplace(*chunkInfo, _clusterTime); return false; @@ -922,7 +605,7 @@ IndexBounds ChunkManager::getIndexBoundsForQuery(const BSONObj& key, // Use query framework to generate index bounds QueryPlannerParams plannerParams; // Must use "shard key" index - plannerParams.options = QueryPlannerParams::STRICT_NO_TABLE_SCAN; + plannerParams.options = QueryPlannerParams::NO_TABLE_SCAN; IndexEntry indexEntry(key, indexType, IndexDescriptor::kLatestIndexVersion, @@ -1051,15 +734,21 @@ std::string ChunkManager::toString() const { return _rt->optRt ? _rt->optRt->toString() : "UNSHARDED"; } -ShardVersionTargetingInfo RoutingTableHistory::_getVersion(const ShardId& shardName, - bool throwOnStaleShard) const { +bool RoutingTableHistory::compatibleWith(const RoutingTableHistory& other, + const ShardId& shardName) const { + // Return true if the shard version is the same in the two chunk managers + // TODO: This doesn't need to be so strong, just major vs + return other.getVersion(shardName) == getVersion(shardName); +} + +ChunkVersion RoutingTableHistory::_getVersion(const ShardId& shardName, + bool throwOnStaleShard) const { auto it = _shardVersions.find(shardName); if (it == _shardVersions.end()) { // Shards without explicitly tracked shard versions (meaning they have no chunks) always // have a version of (0, 0, epoch, timestamp) const auto collVersion = _chunkMap.getVersion(); - return ShardVersionTargetingInfo( - ChunkVersion(0, 0, collVersion.epoch(), collVersion.getTimestamp()), Timestamp(0, 0)); + return ChunkVersion(0, 0, collVersion.epoch(), collVersion.getTimestamp()); } if (throwOnStaleShard && gEnableFinerGrainedCatalogCacheRefresh) { @@ -1068,21 +757,30 @@ ShardVersionTargetingInfo RoutingTableHistory::_getVersion(const ShardId& shardN !it->second.isStale.load()); } - const auto& shardVersionTargetingInfo = it->second; - return ShardVersionTargetingInfo(shardVersionTargetingInfo.shardVersion, - shardVersionTargetingInfo.validAfter); + return it->second.shardVersion; +} + +ChunkVersion RoutingTableHistory::getVersion(const ShardId& shardName) const { + return _getVersion(shardName, true); +} + +ChunkVersion RoutingTableHistory::getVersionForLogging(const ShardId& shardName) const { + return _getVersion(shardName, false); } std::string RoutingTableHistory::toString() const { StringBuilder sb; sb << "RoutingTableHistory: " << _nss.ns() << " key: " << _shardKeyPattern.toString() << '\n'; - sb << _chunkMap.toString(); + sb << "Chunks:\n"; + _chunkMap.forEach([&sb](const auto& chunk) { + sb << "\t" << chunk->toString() << '\n'; + return true; + }); sb << "Shard versions:\n"; for (const auto& entry : _shardVersions) { - sb << "\t" << entry.first << ": " << entry.second.shardVersion.toString() << " @ " - << entry.second.validAfter.toString() << '\n'; + sb << "\t" << entry.first << ": " << entry.second.shardVersion.toString() << '\n'; } return sb.str(); @@ -1103,19 +801,16 @@ RoutingTableHistory RoutingTableHistory::makeNew( const std::vector<ChunkType>& chunks) { auto changedChunkInfos = flatten(chunks); - - return RoutingTableHistory( - std::move(nss), - std::move(uuid), - std::move(shardKeyPattern), - std::move(defaultCollator), - std::move(unique), - std::move(timeseriesFields), - std::move(reshardingFields), - boost::none, - allowMigrations, - ChunkMap{epoch, timestamp, static_cast<size_t>(gRoutingTableCacheChunkBucketSize)} - .createMerged(std::move(changedChunkInfos))); + return RoutingTableHistory(std::move(nss), + std::move(uuid), + std::move(shardKeyPattern), + std::move(defaultCollator), + std::move(unique), + std::move(timeseriesFields), + std::move(reshardingFields), + maxChunkSizeBytes, + allowMigrations, + ChunkMap{epoch, timestamp}.createMerged(changedChunkInfos)); } // Note that any new parameters added to RoutingTableHistory::makeUpdated() must also be added to @@ -1129,7 +824,7 @@ RoutingTableHistory RoutingTableHistory::makeUpdated( const std::vector<ChunkType>& changedChunks) const { auto changedChunkInfos = flatten(changedChunks); - auto chunkMap = _chunkMap.createMerged(std::move(changedChunkInfos)); + auto chunkMap = _chunkMap.createMerged(changedChunkInfos); // Only update the same collection. invariant(getVersion().isSameCollection(chunkMap.getVersion())); diff --git a/src/mongo/s/chunk_manager.h b/src/mongo/s/chunk_manager.h index 39755b418a9..00c75957d37 100644 --- a/src/mongo/s/chunk_manager.h +++ b/src/mongo/s/chunk_manager.h @@ -41,6 +41,7 @@ #include "mongo/s/resharding/type_collection_fields_gen.h" #include "mongo/s/shard_key_pattern.h" #include "mongo/s/type_collection_common_types_gen.h" +#include "mongo/stdx/unordered_map.h" #include "mongo/util/concurrency/ticketholder.h" #include "mongo/util/read_through_cache.h" @@ -51,24 +52,13 @@ struct QuerySolutionNode; class ChunkManager; struct ShardVersionTargetingInfo { - ShardVersionTargetingInfo(const ShardVersionTargetingInfo& other) - : shardVersion(other.shardVersion), - validAfter(other.validAfter), - isStale(other.isStale.load()) {} - - ShardVersionTargetingInfo(const OID& epoch, const Timestamp& timestamp); - ShardVersionTargetingInfo(ChunkVersion shardVersion, Timestamp validAfter) - : shardVersion(std::move(shardVersion)), validAfter(std::move(validAfter)) {} + // Indicates whether the shard is stale and thus needs a catalog cache refresh + AtomicWord<bool> isStale{false}; // Max chunk version for the shard ChunkVersion shardVersion; - // Max validAfter for the shard, effectively this is the timestamp of the latest placement - // change that occurred on a particular shard. - Timestamp validAfter; - - // Indicates whether the shard is stale and thus needs a catalog cache refresh - AtomicWord<bool> isStale{false}; + ShardVersionTargetingInfo(const OID& epoch, const Timestamp& timestamp); }; // Map from a shard to a struct indicating both the max chunk version on that shard and whether the @@ -81,174 +71,73 @@ using ShardVersionMap = stdx::unordered_map<ShardId, ShardVersionTargetingInfo, * underlying implementation. */ class ChunkMap { -public: - // Vector of chunks ordered by max key in ascending order. + // Vector of chunks ordered by max key. using ChunkVector = std::vector<std::shared_ptr<ChunkInfo>>; - using ChunkVectorMap = std::map<std::string, std::shared_ptr<ChunkVector>>; - - explicit ChunkMap(OID epoch, const Timestamp& timestamp, size_t chunkVectorSize) - : _collectionVersion(0, 0, epoch, timestamp), - _collTimestamp(timestamp), - _maxChunkVectorSize(chunkVectorSize) {} - size_t size() const; - - // Max version across all chunks - ChunkVersion getVersion() const { - return _collectionVersion; - } - - size_t getMaxChunkVectorSize() const { - return _maxChunkVectorSize; +public: + explicit ChunkMap(OID epoch, const Timestamp& timestamp, size_t initialCapacity = 0) + : _collectionVersion(0, 0, epoch, timestamp), _collTimestamp(timestamp) { + _chunkMap.reserve(initialCapacity); } - const ShardVersionMap& getShardVersionsMap() const { - return _shardVersions; + size_t size() const { + return _chunkMap.size(); } - const ChunkVectorMap& getChunkVectorMap() const { - return _chunkVectorMap; + ChunkVersion getVersion() const { + return _collectionVersion; } - - /* - * Invoke the given handler for each std::shared_ptr<ChunkInfo> contained in this chunk map - * until either all matching chunks have been processed or @handler returns false. - * - * Chunks are yielded in ascending order of shardkey (e.g. minKey to maxKey); - * - * When shardKey is provided the function will start yileding from the chunk that contains the - * given shard key. - */ template <typename Callable> void forEach(Callable&& handler, const BSONObj& shardKey = BSONObj()) const { - if (shardKey.isEmpty()) { - for (const auto& mapIt : _chunkVectorMap) { - for (const auto& chunkInfoPtr : *(mapIt.second)) { - if (!handler(chunkInfoPtr)) - return; - } - } - - return; - } + auto it = shardKey.isEmpty() ? _chunkMap.begin() : _findIntersectingChunk(shardKey); - auto shardKeyString = ShardKeyPattern::toKeyString(shardKey); - - const auto mapItBegin = _chunkVectorMap.upper_bound(shardKeyString); - for (auto mapIt = mapItBegin; mapIt != _chunkVectorMap.end(); mapIt++) { - const auto& chunkVector = *(mapIt->second); - auto it = mapIt == mapItBegin ? _findIntersectingChunkIterator(shardKeyString, - chunkVector.begin(), - chunkVector.end(), - true /*isMaxInclusive*/) - : chunkVector.begin(); - for (; it != chunkVector.end(); ++it) { - if (!handler(*it)) - return; - } + for (; it != _chunkMap.end(); ++it) { + if (!handler(*it)) + break; } } - - /* - * Invoke the given @handler for each std::shared_ptr<ChunkInfo> that overlaps with range [@min, - * @max] until either all matching chunks have been processed or @handler returns false. - * - * Chunks are yielded in ascending order of shardkey (e.g. minKey to maxKey); - * - * When @isMaxInclusive is true also the chunk whose minKey is equal to @max will be yielded. - */ template <typename Callable> void forEachOverlappingChunk(const BSONObj& min, const BSONObj& max, bool isMaxInclusive, Callable&& handler) const { - const auto minShardKeyStr = ShardKeyPattern::toKeyString(min); - const auto maxShardKeyStr = ShardKeyPattern::toKeyString(max); - const auto bounds = - _overlappingVectorSlotBounds(minShardKeyStr, maxShardKeyStr, isMaxInclusive); - for (auto mapIt = bounds.first; mapIt != bounds.second; ++mapIt) { - - const auto& chunkVector = *(mapIt->second); - - const auto chunkItBegin = [&] { - if (mapIt == bounds.first) { - // On first vector we need to start from chunk that contain the given minKey - return _findIntersectingChunkIterator(minShardKeyStr, - chunkVector.begin(), - chunkVector.end(), - true /* isMaxInclusive */); - } - return chunkVector.begin(); - }(); - - const auto chunkItEnd = [&] { - if (mapIt == std::prev(bounds.second)) { - // On last vector we need to skip all chunks that are greater than the give - // maxKey - auto it = _findIntersectingChunkIterator( - maxShardKeyStr, chunkItBegin, chunkVector.end(), isMaxInclusive); - return it == chunkVector.end() ? it : ++it; - } - return chunkVector.end(); - }(); - - for (auto chunkIt = chunkItBegin; chunkIt != chunkItEnd; ++chunkIt) { - if (!handler(*chunkIt)) - return; - } + const auto bounds = _overlappingBounds(min, max, isMaxInclusive); + + for (auto it = bounds.first; it != bounds.second; ++it) { + if (!handler(*it)) + break; } } + ShardVersionMap constructShardVersionMap() const; std::shared_ptr<ChunkInfo> findIntersectingChunk(const BSONObj& shardKey) const; - ChunkMap createMerged(ChunkVector changedChunks) const; + void appendChunk(const std::shared_ptr<ChunkInfo>& chunk); - BSONObj toBSON() const; + ChunkMap createMerged(const std::vector<std::shared_ptr<ChunkInfo>>& changedChunks) const; - std::string toString() const; + BSONObj toBSON() const; private: - ChunkVector::const_iterator _findIntersectingChunkIterator(const std::string& shardKeyString, - ChunkVector::const_iterator first, - ChunkVector::const_iterator last, - bool isMaxInclusive) const; - - std::pair<ChunkVectorMap::const_iterator, ChunkVectorMap::const_iterator> - _overlappingVectorSlotBounds(const std::string& minShardKeyStr, - const std::string& maxShardKeyStr, - bool isMaxInclusive) const; - ChunkMap _makeUpdated(ChunkVector&& changedChunks) const; - - void _updateShardVersionFromDiscardedChunk(const ChunkInfo& chunk); - void _updateShardVersionFromUpdateChunk(const ChunkInfo& chunk, - const ShardVersionMap& oldShardVersions); - void _commitUpdatedChunkVector(std::shared_ptr<ChunkVector>&& chunkVectorPtr, - bool checkMaxKeyConsistency); - void _mergeAndCommitUpdatedChunkVector(ChunkVectorMap::const_iterator pos, - std::shared_ptr<ChunkVector>&& chunkVectorPtr); - void _splitAndCommitUpdatedChunkVector(ChunkVectorMap::const_iterator pos, - std::shared_ptr<ChunkVector>&& chunkVectorPtr); - - ChunkVectorMap _chunkVectorMap; + ChunkVector::const_iterator _findIntersectingChunk(const BSONObj& shardKey, + bool isMaxInclusive = true) const; + std::pair<ChunkVector::const_iterator, ChunkVector::const_iterator> _overlappingBounds( + const BSONObj& min, const BSONObj& max, bool isMaxInclusive) const; + + ChunkVector _chunkMap; // Max version across all chunks ChunkVersion _collectionVersion; - // The representation of shard versions and staleness indicators for this namespace. If a - // shard does not exist, it will not have an entry in the map. - // Note: this declaration must not be moved before _chunkMap since it is initialized by using - // the _chunkVectorMap instance. - ShardVersionMap _shardVersions; - // Represents the timestamp present in config.collections for this ChunkMap. + // + // Note that due to the way Phase 1 of the FCV upgrade writes timestamps to chunks + // (non-atomically), it is possible that chunks exist with timestamps, but the corresponding + // config.collections entry doesn't. In this case, the chunks timestamp should be ignored when + // computing the collection version and we should use _collTimestamp instead. Timestamp _collTimestamp; - - // Maximum size of chunk vectors stored in the chunk vector map. - // Bigger vectors will imply slower incremental refreshes (more chunks to copy) but - // faster map copy (less chunk vector pointers to copy). - size_t _maxChunkVectorSize; }; /** @@ -339,7 +228,6 @@ public: */ void setAllShardsRefreshed(); - // Max version across all chunks ChunkVersion getVersion() const { return _chunkMap.getVersion(); } @@ -348,26 +236,14 @@ public: * Retrieves the shard version for the given shard. Will throw a ShardInvalidatedForTargeting * exception if the shard is marked as stale. */ - ChunkVersion getVersion(const ShardId& shardId) const { - return _getVersion(shardId, true).shardVersion; - } + ChunkVersion getVersion(const ShardId& shardId) const; /** * Retrieves the shard version for the given shard. Will not throw if the shard is marked as * stale. Only use when logging the given chunk version -- if the caller must execute logic * based on the returned version, use getVersion() instead. */ - ChunkVersion getVersionForLogging(const ShardId& shardId) const { - return _getVersion(shardId, false).shardVersion; - } - - /** - * Retrieves the maximum validAfter timestamp for the given shard. Will throw a - * ShardInvalidatedForTargeting exception if the shard is marked as stale. - */ - Timestamp getMaxValidAfter(const ShardId& shardId) const { - return _getVersion(shardId, true).validAfter; - } + ChunkVersion getVersionForLogging(const ShardId& shardId) const; size_t numChunks() const { return _chunkMap.size(); @@ -401,6 +277,11 @@ public: */ int getNShardsOwningChunks() const; + /** + * Returns true if, for this shard, the chunks are identical in both chunk managers + */ + bool compatibleWith(const RoutingTableHistory& other, const ShardId& shard) const; + std::string toString() const; bool uuidMatches(const UUID& uuid) const { @@ -442,7 +323,7 @@ private: bool allowMigrations, ChunkMap chunkMap); - ShardVersionTargetingInfo _getVersion(const ShardId& shardId, bool throwOnStaleShard) const; + ChunkVersion _getVersion(const ShardId& shardName, bool throwOnStaleShard) const; // Namespace to which this routing information corresponds NamespaceString _nss; @@ -675,27 +556,10 @@ public: return _rt->optRt->getVersion(); } - /** - * Retrieves the placement version for the given shard. Will throw a - * ShardInvalidatedForTargeting exception if the shard is marked as stale. - */ ChunkVersion getVersion(const ShardId& shardId) const { return _rt->optRt->getVersion(shardId); } - /** - * Retrieves the maximum validAfter timestamp for the given shard. Will throw a - * ShardInvalidatedForTargeting exception if the shard is marked as stale. - */ - Timestamp getMaxValidAfter(const ShardId& shardId) const { - return _rt->optRt->getMaxValidAfter(shardId); - } - - /** - * Retrieves the placement version for the given shard. Will not throw if the shard is marked as - * stale. Only use when logging the given chunk version -- if the caller must execute logic - * based on the returned version, use getVersion() instead. - */ ChunkVersion getVersionForLogging(const ShardId& shardId) const { return _rt->optRt->getVersionForLogging(shardId); } @@ -711,23 +575,6 @@ public: }); } - template <typename Callable> - void forEachOverlappingChunk(const BSONObj& min, - const BSONObj& max, - bool isMaxInclusive, - Callable&& handler) const { - _rt->optRt->forEachOverlappingChunk( - min, - max, - isMaxInclusive, - [this, handler = std::forward<Callable>(handler)](const auto& chunkInfo) mutable { - if (!handler(Chunk{*chunkInfo, _clusterTime})) { - return false; - } - return true; - }); - } - /** * Returns true if a document with the given "shardKey" is owned by the shard with the given * "shardId" in this routing table. If "shardKey" is empty returns false. If "shardKey" is not a @@ -778,15 +625,12 @@ public: /** * Finds the shard IDs for a given filter and collation. If collation is empty, we use the - * collection default collation for targeting. If 'bypassIsFieldHashedCheck' is true, it skips - * checking if the shard key was hashed and assumes that any non-collatable shard key was not - * hashed from a collatable type. + * collection default collation for targeting. */ void getShardIdsForQuery(boost::intrusive_ptr<ExpressionContext> expCtx, const BSONObj& query, const BSONObj& collation, - std::set<ShardId>* shardIds, - bool bypassIsFieldHashedCheck = false) const; + std::set<ShardId>* shardIds) const; /** * Returns all shard ids which contain chunks overlapping the range [min, max]. Please note the @@ -835,6 +679,13 @@ public: */ static ChunkManager makeAtTime(const ChunkManager& cm, Timestamp clusterTime); + /** + * Returns true if, for this shard, the chunks are identical in both chunk managers + */ + bool compatibleWith(const ChunkManager& other, const ShardId& shard) const { + return _rt->optRt->compatibleWith(*other._rt->optRt, shard); + } + bool uuidMatches(const UUID& uuid) const { return _rt->optRt->uuidMatches(uuid); } diff --git a/src/mongo/s/chunk_manager_refresh_bm.cpp b/src/mongo/s/chunk_manager_refresh_bm.cpp index c3577944fab..3c7f3adb6b3 100644 --- a/src/mongo/s/chunk_manager_refresh_bm.cpp +++ b/src/mongo/s/chunk_manager_refresh_bm.cpp @@ -50,17 +50,17 @@ RoutingTableHistoryValueHandle makeStandaloneRoutingTableHistory(RoutingTableHis ComparableChunkVersion::makeComparableChunkVersion(version)); } -ShardId getShardId(int i) { - return {std::string(str::stream() << "shard_" << i)}; -} - ChunkRange getRangeForChunk(int i, int nChunks) { invariant(i >= 0); invariant(nChunks > 0); invariant(i < nChunks); - auto min = (i == 0) ? BSON("_id" << MINKEY) : BSON("_id" << (i - 1) * 100); - auto max = (i == nChunks - 1) ? BSON("_id" << MAXKEY) : BSON("_id" << i * 100); - return {std::move(min), std::move(max)}; + if (i == 0) { + return {BSON("_id" << MINKEY), BSON("_id" << 0)}; + } + if (i + 1 == nChunks) { + return {BSON("_id" << (i - 1) * 100), BSON("_id" << MAXKEY)}; + } + return {BSON("_id" << (i - 1) * 100), BSON("_id" << i * 100)}; } template <typename ShardSelectorFn> @@ -93,21 +93,21 @@ CollectionMetadata makeChunkManagerWithShardSelector(int nShards, boost::none /* chunkSizeBytes */, true, chunks); - return CollectionMetadata(ChunkManager(getShardId(0), + return CollectionMetadata(ChunkManager(ShardId("Shard0"), DatabaseVersion(UUID::gen(), Timestamp(1, 0)), makeStandaloneRoutingTableHistory(std::move(rt)), boost::none), - getShardId(0)); + ShardId("shard0")); } ShardId pessimalShardSelector(int i, int nShards, int nChunks) { - return getShardId(i % nShards); + return ShardId(str::stream() << "shard" << (i % nShards)); } ShardId optimalShardSelector(int i, int nShards, int nChunks) { invariant(nShards <= nChunks); const auto shardNum = (int64_t(i) * nShards / nChunks) % nShards; - return getShardId(shardNum); + return ShardId(str::stream() << "shard" << shardNum); } MONGO_COMPILER_NOINLINE auto makeChunkManagerWithPessimalBalancedDistribution(int nShards, @@ -124,133 +124,35 @@ MONGO_COMPILER_NOINLINE auto runIncrementalUpdate(const CollectionMetadata& cm, const std::vector<ChunkType>& newChunks) { auto rt = cm.getChunkManager()->getRoutingTableHistory_ForTest().makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, newChunks); - return CollectionMetadata(ChunkManager(getShardId(0), + return CollectionMetadata(ChunkManager(ShardId("shard0"), DatabaseVersion(UUID::gen(), Timestamp(1, 0)), makeStandaloneRoutingTableHistory(std::move(rt)), boost::none), - getShardId(0)); + ShardId("shard0")); } -/* - * Simulate a refresh of the ChunkManager where a number of chunks is migrated from one shard to - * another. - * - * The chunks modified in the routing table are equally spaced. - */ -void BM_IncrementalSpacedRefreshMoveChunks(benchmark::State& state) { +void BM_IncrementalRefreshOfPessimalBalancedDistribution(benchmark::State& state) { const int nShards = state.range(0); const int nChunks = state.range(1); - const int nUpdates = state.range(2); auto metadata = makeChunkManagerWithPessimalBalancedDistribution(nShards, nChunks); - auto lastVersion = metadata.getCollVersion(); - + auto postMoveVersion = metadata.getChunkManager()->getVersion(); + const UUID uuid = metadata.getUUID(); std::vector<ChunkType> newChunks; - newChunks.reserve(nUpdates); - const auto updateSpacing = nChunks / nUpdates; - for (int i = 0; i < nUpdates; i++) { - const auto idx = i * updateSpacing; - lastVersion.incMajor(); - newChunks.emplace_back(metadata.getUUID(), - getRangeForChunk(idx, nChunks), - lastVersion, - pessimalShardSelector(idx, nShards, nChunks)); - } - - std::mt19937 g; - g.seed(456); - std::shuffle(newChunks.begin(), newChunks.end(), g); - - for (auto _ : state) { - benchmark::DoNotOptimize(runIncrementalUpdate(metadata, newChunks)); - } -} - -BENCHMARK(BM_IncrementalSpacedRefreshMoveChunks) - ->Args({4, 1, 1}) - ->Args({4, 10, 1}) - ->Args({4, 100, 1}) - ->Args({4, 1000, 1}) - ->Args({4, 10000, 1}) - ->Args({4, 100000, 1}) - ->Args({4, 10000, 10}) - ->Args({4, 10000, 100}) - ->Args({4, 10000, 1000}) - ->Args({4, 10000, 10000}); - -/* - * Simulate a refresh of the ChunkManager where a number of chunks is merged together. - */ -void BM_IncrementalSpacedRefreshMergeChunks(benchmark::State& state) { - const int nShards = state.range(0); - const int nChunks = state.range(1); - const int nUpdates = state.range(2); - auto metadata = makeChunkManagerWithOptimalBalancedDistribution(nShards, nChunks); - - auto lastVersion = metadata.getCollVersion(); + postMoveVersion.incMajor(); + newChunks.emplace_back(uuid, getRangeForChunk(1, nChunks), postMoveVersion, ShardId("shard0")); + postMoveVersion.incMajor(); + newChunks.emplace_back(uuid, getRangeForChunk(3, nChunks), postMoveVersion, ShardId("shard1")); - std::vector<ChunkType> newChunks; - newChunks.reserve(nUpdates); - invariant(nUpdates <= nShards); - const auto shardSpacing = nShards / (nUpdates + 1); - std::set<ShardId> shardsToMerge; - for (int i = 0; i < nUpdates; i++) { - invariant(i * shardSpacing <= nShards); - shardsToMerge.emplace(getShardId(i * shardSpacing)); - } - - ShardId shardId; - std::vector<ChunkRange> rangesToMerge; - - const auto flushRanges = [&] { - if (rangesToMerge.empty()) { - return; - } - - lastVersion.incMajor(); - newChunks.emplace_back( - metadata.getUUID(), - ChunkRange(rangesToMerge.front().getMin(), rangesToMerge.back().getMax()), - lastVersion, - shardId); - rangesToMerge.clear(); - }; - - for (int i = 0; i < nChunks; i++) { - auto nextShardId = pessimalShardSelector(i, nShards, nChunks); - if (nextShardId != shardId) { - flushRanges(); - shardId = nextShardId; - } - if (shardsToMerge.count(shardId) == 1) { - rangesToMerge.emplace_back(getRangeForChunk(i, nChunks)); - } - } - flushRanges(); - - std::mt19937 g; - g.seed(456); - std::shuffle(newChunks.begin(), newChunks.end(), g); - - for (auto _ : state) { + for (auto keepRunning : state) { benchmark::DoNotOptimize(runIncrementalUpdate(metadata, newChunks)); } } -/* - * Simulate chunks merge on a routing table of 10000 chunks partitioned among 4 shards. - * - * [ 0, 2500) -> shard0 - * [2500, 5000) -> shard1 - * [5000, 7500) -> shard2 - * [7500, 10000) -> shard3 - */ - -BENCHMARK(BM_IncrementalSpacedRefreshMergeChunks) - ->Args({4, 10000, 1}) // merge all chunks on shard2 - ->Args({4, 10000, 2}) // merge all chunks on shard2 and shard3 - ->Args({4, 10000, 3}) // merge all chunks on shard1, shard2 and shard3 - ->Args({4, 10000, 4}); // merge all chunks on shard1, shard2, shard3 and shard4 +BENCHMARK(BM_IncrementalRefreshOfPessimalBalancedDistribution) + ->Args({2, 50000}) + ->Args({2, 250000}) + ->Args({2, 500000}); template <typename ShardSelectorFn> auto BM_FullBuildOfChunkManager(benchmark::State& state, ShardSelectorFn selectShard) { @@ -285,11 +187,11 @@ auto BM_FullBuildOfChunkManager(benchmark::State& state, ShardSelectorFn selectS true, chunks); benchmark::DoNotOptimize( - CollectionMetadata(ChunkManager(getShardId(0), + CollectionMetadata(ChunkManager(ShardId("shard0"), DatabaseVersion(UUID::gen(), Timestamp(1, 0)), makeStandaloneRoutingTableHistory(std::move(rt)), boost::none), - getShardId(0))); + ShardId("shard0"))); } } @@ -511,14 +413,11 @@ MONGO_INITIALIZER(RegisterBenchmarks)(InitializerContext* context) { }; for (auto bmCase : bmCases) { - bmCase->Args({2, 2}) - ->Args({1, 10000}) - ->Args({10, 10000}) - ->Args({100, 10000}) - ->Args({1000, 10000}) - ->Args({10, 10}) - ->Args({10, 100}) - ->Args({10, 1000}); + bmCase->Args({2, 50000}) + ->Args({10, 50000}) + ->Args({100, 50000}) + ->Args({1000, 50000}) + ->Args({2, 2}); } } diff --git a/src/mongo/s/chunk_manager_targeter.cpp b/src/mongo/s/chunk_manager_targeter.cpp index f750554ea56..bdb386420c6 100644 --- a/src/mongo/s/chunk_manager_targeter.cpp +++ b/src/mongo/s/chunk_manager_targeter.cpp @@ -43,8 +43,6 @@ #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/collation/collation_index_key.h" #include "mongo/db/query/collation/collator_factory_interface.h" -#include "mongo/db/stats/counters.h" -#include "mongo/db/timeseries/metadata.h" #include "mongo/db/timeseries/timeseries_constants.h" #include "mongo/db/timeseries/timeseries_options.h" #include "mongo/db/timeseries/timeseries_update_delete_util.h" @@ -65,8 +63,6 @@ namespace mongo { namespace { -MONGO_FAIL_POINT_DEFINE(waitForDatabaseToBeDropped); - enum CompareResult { CompareResult_Unknown, CompareResult_GTE, CompareResult_LT }; constexpr auto kIdFieldName = "_id"_sd; @@ -230,8 +226,6 @@ bool isMetadataDifferent(const ChunkManager& managerA, const ChunkManager& manag } // namespace -const size_t ChunkManagerTargeter::kMaxDatabaseCreationAttempts = 3; - ChunkManagerTargeter::ChunkManagerTargeter(OperationContext* opCtx, const NamespaceString& nss, boost::optional<OID> targetEpoch) @@ -245,45 +239,13 @@ ChunkManagerTargeter::ChunkManagerTargeter(OperationContext* opCtx, * user request is on the view namespace, we implicity tranform the request to the buckets namepace. */ ChunkManager ChunkManagerTargeter::_init(OperationContext* opCtx, bool refresh) { - const auto createDatabaseAndGetRoutingInfo = [&opCtx, &refresh](const NamespaceString& nss) { - size_t attempts = 1; - while (true) { - try { - cluster::createDatabase(opCtx, nss.db()); - - if (refresh) { - uassertStatusOK( - Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfoWithRefresh(opCtx, - nss)); - } - - if (MONGO_unlikely(waitForDatabaseToBeDropped.shouldFail())) { - LOGV2(8314600, "Hanging due to waitForDatabaseToBeDropped fail point"); - waitForDatabaseToBeDropped.pauseWhileSet(opCtx); - } - - return uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, nss)); - } catch (const ExceptionFor<ErrorCodes::NamespaceNotFound>&) { - LOGV2_INFO(8314601, - "Failed initialization of routing info because the database has been " - "concurrently dropped", - logAttrs(nss), - "attemptNumber"_attr = attempts, - "maxAttempts"_attr = kMaxDatabaseCreationAttempts); - - if (attempts++ >= kMaxDatabaseCreationAttempts) { - // The maximum number of attempts has been reached, so the procedure fails as it - // could be a logical error. At this point, it is unlikely that the error is - // caused by concurrent drop database operations. - throw; - } - } - } - }; + cluster::createDatabase(opCtx, _nss.db()); - createDatabaseAndGetRoutingInfo(_nss); - - auto cm = createDatabaseAndGetRoutingInfo(_nss); + if (refresh) { + uassertStatusOK( + Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfoWithRefresh(opCtx, _nss)); + } + auto cm = uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, _nss)); // For a sharded time-series collection, only the underlying buckets collection is stored on the // config servers. If the user operation is on the time-series view namespace, we should check @@ -299,18 +261,27 @@ ChunkManager ChunkManagerTargeter::_init(OperationContext* opCtx, bool refresh) // back to the view namespace and reset '_isRequestOnTimeseriesViewNamespace'. if (!cm.isSharded() && !_nss.isTimeseriesBucketsCollection()) { auto bucketsNs = _nss.makeTimeseriesBucketsNamespace(); - auto bucketsPlacementInfo = createDatabaseAndGetRoutingInfo(bucketsNs); - if (bucketsPlacementInfo.isSharded()) { + if (refresh) { + uassertStatusOK(Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfoWithRefresh( + opCtx, bucketsNs)); + } + auto bucketsRoutingInfo = + uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, bucketsNs)); + if (bucketsRoutingInfo.isSharded()) { _nss = bucketsNs; - cm = std::move(bucketsPlacementInfo); + cm = std::move(bucketsRoutingInfo); _isRequestOnTimeseriesViewNamespace = true; } } else if (!cm.isSharded() && _isRequestOnTimeseriesViewNamespace) { // This can happen if a sharded time-series collection is dropped and re-created. Then we // need to reset the namepace to the original namespace. _nss = _nss.getTimeseriesViewNamespace(); - auto newCm = createDatabaseAndGetRoutingInfo(_nss); - cm = std::move(newCm); + + if (refresh) { + uassertStatusOK( + Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfoWithRefresh(opCtx, _nss)); + } + cm = uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, _nss)); _isRequestOnTimeseriesViewNamespace = false; } @@ -352,7 +323,7 @@ BSONObj ChunkManagerTargeter::extractBucketsShardKeyFromTimeseriesDoc( if (auto metaField = timeseriesOptions.getMetaField(); metaField) { if (auto metaElement = doc.getField(*metaField); !metaElement.eoo()) { - timeseries::metadata::normalize(metaElement, builder, timeseries::kBucketMetaFieldName); + builder.appendAs(metaElement, timeseries::kBucketMetaFieldName); } } @@ -415,10 +386,6 @@ std::vector<ShardEndpoint> ChunkManagerTargeter::targetUpdate(OperationContext* const auto& updateOp = itemRef.getUpdate(); - if (updateOp.getMulti()) { - updateManyCount.increment(1); - } - // If the collection is not sharded, forward the update to the primary shard. if (!_cm.isSharded()) { // TODO (SERVER-51070): Remove the boost::none when the config server can support @@ -535,11 +502,8 @@ std::vector<ShardEndpoint> ChunkManagerTargeter::targetDelete(OperationContext* itemRef.getLet(), itemRef.getLegacyRuntimeConstants()); - if (deleteOp.getMulti()) { - deleteManyCount.increment(1); - } - BSONObj deleteQuery = deleteOp.getQ(); + BSONObj shardKey; if (_cm.isSharded()) { if (_isRequestOnTimeseriesViewNamespace) { uassert(ErrorCodes::NotImplemented, @@ -565,13 +529,20 @@ std::vector<ShardEndpoint> ChunkManagerTargeter::targetDelete(OperationContext* deleteQuery = BSONObj(); } } + + // Sharded collections have the following further requirements for targeting: + // + // Limit-1 deletes must be targeted exactly by shard key *or* exact _id + shardKey = + uassertStatusOK(_cm.getShardKeyPattern().extractShardKeyFromQuery(expCtx, deleteQuery)); } - // We first try to target based on the delete's query. It is always valid to forward any delete - // to a single shard, so return immediately if we are able to target a single shard. - auto endpoints = uassertStatusOK(_targetQuery(expCtx, deleteQuery, collation)); - if (endpoints.size() == 1) { - return endpoints; + // Target the shard key or delete query + if (!shardKey.isEmpty()) { + auto swEndpoint = _targetShardKey(shardKey, collation); + if (swEndpoint.isOK()) { + return std::vector{std::move(swEndpoint.getValue())}; + } } // We failed to target a single shard. @@ -600,7 +571,7 @@ std::vector<ShardEndpoint> ChunkManagerTargeter::targetDelete(OperationContext* << ", shard key pattern: " << _cm.getShardKeyPattern().toString(), !_cm.isSharded() || deleteOp.getMulti() || isExactIdQuery(opCtx, *cq, _cm)); - return endpoints; + return uassertStatusOK(_targetQuery(expCtx, deleteQuery, collation)); } StatusWith<std::vector<ShardEndpoint>> ChunkManagerTargeter::_targetQuery( diff --git a/src/mongo/s/chunk_manager_targeter.h b/src/mongo/s/chunk_manager_targeter.h index 9de225a20a8..0950484eb7d 100644 --- a/src/mongo/s/chunk_manager_targeter.h +++ b/src/mongo/s/chunk_manager_targeter.h @@ -120,9 +120,6 @@ public: const TimeseriesOptions& timeseriesOptions); private: - // Maximum number of database creation attempts, which may fail due to a concurrent drop. - static const size_t kMaxDatabaseCreationAttempts; - ChunkManager _init(OperationContext* opCtx, bool refresh); /** diff --git a/src/mongo/s/chunk_manager_targeter_test.cpp b/src/mongo/s/chunk_manager_targeter_test.cpp index f7e3642579e..14070303a0f 100644 --- a/src/mongo/s/chunk_manager_targeter_test.cpp +++ b/src/mongo/s/chunk_manager_targeter_test.cpp @@ -303,29 +303,22 @@ TEST_F(ChunkManagerTargeterTest, TargetDeleteWithRangePrefixHashedShardKey) { << "hashed"), splitPoints); - // Can delete with partial shard key in the query if the query only targets one shard. - auto requestPartialKey = buildDelete(kNss, fromjson("{'a.b': {$gt : 101}}")); - auto res = cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestPartialKey, 0)); - ASSERT_EQUALS(res.size(), 1); - ASSERT_EQUALS(res[0].shardName, "4"); - - // Cannot delete with partial shard key in the query if the query targets multiple shards. - auto requestPartialKey2 = buildDelete(kNss, fromjson("{'a.b': {$gt: 0}}")); + // Cannot delete without full shardkey in the query. + auto requestPartialKey = buildDelete(kNss, fromjson("{'a.b': {$gt : 2}}")); ASSERT_THROWS_CODE( - cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestPartialKey2, 0)), + cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestPartialKey, 0)), DBException, ErrorCodes::ShardKeyNotFound); - // Cannot delete without at least a partial shard key. - auto requestNoShardKey = buildDelete(kNss, fromjson("{'k': 0}")); + auto requestPartialKey2 = buildDelete(kNss, fromjson("{'a.b': -101}")); ASSERT_THROWS_CODE( - cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestNoShardKey, 0)), + cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestPartialKey2, 0)), DBException, ErrorCodes::ShardKeyNotFound); // Delete targeted correctly with full shard key in query. auto requestFullKey = buildDelete(kNss, fromjson("{'a.b': -101, 'c.d': 5}")); - res = cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestFullKey, 0)); + auto res = cmTargeter.targetDelete(operationContext(), BatchItemRef(&requestFullKey, 0)); ASSERT_EQUALS(res.size(), 1); ASSERT_EQUALS(res[0].shardName, "1"); diff --git a/src/mongo/s/chunk_map_test.cpp b/src/mongo/s/chunk_map_test.cpp index 78b2ca42097..6514fc00745 100644 --- a/src/mongo/s/chunk_map_test.cpp +++ b/src/mongo/s/chunk_map_test.cpp @@ -27,104 +27,18 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - #include "mongo/platform/basic.h" -#include "mongo/logv2/log.h" -#include "mongo/platform/random.h" #include "mongo/s/chunk_manager.h" -#include "mongo/s/chunk_writes_tracker.h" -#include "mongo/s/chunks_test_util.h" #include "mongo/unittest/unittest.h" namespace mongo { -using chunks_test_util::assertEqualChunkInfo; -using chunks_test_util::calculateCollVersion; -using chunks_test_util::calculateIntermediateShardKey; -using chunks_test_util::genChunkVector; -using chunks_test_util::genRandomSplitPoints; -using chunks_test_util::performRandomChunkOperations; - namespace { -PseudoRandom _random{SecureRandom().nextInt64()}; - +const NamespaceString kNss("TestDB", "TestColl"); const ShardId kThisShard("testShard"); -ShardVersionMap getShardVersionMap(const ChunkMap& chunkMap) { - return chunkMap.getShardVersionsMap(); -} - -std::map<ShardId, ChunkVersion> calculateShardVersions( - const std::vector<std::shared_ptr<ChunkInfo>>& chunkVector) { - std::map<ShardId, ChunkVersion> svMap; - for (const auto& chunk : chunkVector) { - auto mapIt = svMap.find(chunk->getShardId()); - if (mapIt == svMap.end()) { - svMap.emplace(chunk->getShardId(), chunk->getLastmod()); - continue; - } - if (mapIt->second.isOlderThan(chunk->getLastmod())) { - mapIt->second = chunk->getLastmod(); - } - } - return svMap; -} - -std::vector<std::shared_ptr<ChunkInfo>> toChunkInfoPtrVector( - const std::vector<ChunkType>& chunkTypes, bool initializeWriteTrackerRandom = true) { - std::vector<std::shared_ptr<ChunkInfo>> chunkPtrs; - chunkPtrs.reserve(chunkTypes.size()); - for (const auto& chunkType : chunkTypes) { - auto chunkInfoPtr = std::make_shared<ChunkInfo>(chunkType); - if (initializeWriteTrackerRandom) { - chunkInfoPtr->getWritesTracker()->addBytesWritten(_random.nextInt64(30)); - } - chunkPtrs.push_back(std::move(chunkInfoPtr)); - } - return chunkPtrs; -} - -void validateChunkMap(const ChunkMap& chunkMap, - const std::vector<std::shared_ptr<ChunkInfo>>& chunkInfoVector) { - - // The chunkMap should contain all the chunks - ASSERT_EQ(chunkInfoVector.size(), chunkMap.size()); - - // Check collection version - const auto expectedShardVersions = calculateShardVersions(chunkInfoVector); - const auto expectedCollVersion = calculateCollVersion(expectedShardVersions); - ASSERT_EQ(expectedCollVersion, chunkMap.getVersion()); - - size_t i = 0; - chunkMap.forEach([&](const auto& chunkPtr) { - const auto& expectedChunkPtr = chunkInfoVector[i++]; - // Check that the chunk pointer is valid - ASSERT(chunkPtr.get() != nullptr); - assertEqualChunkInfo(*expectedChunkPtr, *chunkPtr); - return true; - }); - - // Validate all shard versions - const auto shardVersions = getShardVersionMap(chunkMap); - ASSERT_EQ(expectedShardVersions.size(), shardVersions.size()); - for (const auto& mapIt : shardVersions) { - ASSERT_EQ(expectedShardVersions.at(mapIt.first), mapIt.second.shardVersion); - } - - // Check that vectors are balanced in size - auto maxVectorSize = static_cast<size_t>(std::lround(chunkMap.getMaxChunkVectorSize() * 1.5)); - auto minVectorSize = std::min( - chunkMap.size(), static_cast<size_t>(std::lround(chunkMap.getMaxChunkVectorSize() / 2))); - - for (const auto& [maxKeyString, chunkVectorPtr] : chunkMap.getChunkVectorMap()) { - ASSERT_GTE(chunkVectorPtr->size(), minVectorSize); - ASSERT_LTE(chunkVectorPtr->size(), maxVectorSize); - } -} - class ChunkMapTest : public unittest::Test { public: const KeyPattern& getShardKeyPattern() const { @@ -135,36 +49,16 @@ public: return _uuid; } - const OID& collEpoch() const { - return _epoch; - } - - const Timestamp& collTimestamp() const { - return _collTimestamp; - } - - ChunkMap makeChunkMap(const std::vector<std::shared_ptr<ChunkInfo>>& chunks) const { - const auto chunkBucketSize = - static_cast<size_t>(_random.nextInt64(chunks.size() * 1.2) + 1); - LOGV2(7162701, "Creating new chunk map", "chunkBucketSize"_attr = chunkBucketSize); - return ChunkMap{collEpoch(), collTimestamp(), chunkBucketSize}.createMerged(chunks); - } - - std::vector<ChunkType> genRandomChunkVector(size_t maxNumChunks = 30, - size_t minNumChunks = 1) const { - return chunks_test_util::genRandomChunkVector( - _uuid, _epoch, _collTimestamp, maxNumChunks, minNumChunks); - } - private: - KeyPattern _shardKeyPattern{chunks_test_util::kShardKeyPattern}; + KeyPattern _shardKeyPattern{BSON("a" << 1)}; const UUID _uuid = UUID::gen(); - const OID _epoch{OID::gen()}; - const Timestamp _collTimestamp{1, 1}; }; +} // namespace + TEST_F(ChunkMapTest, TestAddChunk) { - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; + const OID epoch = OID::gen(); + ChunkVersion version{1, 0, epoch, Timestamp(1, 1)}; auto chunk = std::make_shared<ChunkInfo>( ChunkType{uuid(), @@ -172,190 +66,18 @@ TEST_F(ChunkMapTest, TestAddChunk) { version, kThisShard}); - auto newChunkMap = makeChunkMap({chunk}); + ChunkMap chunkMap{epoch, Timestamp(1, 1)}; + auto newChunkMap = chunkMap.createMerged({chunk}); ASSERT_EQ(newChunkMap.size(), 1); - - validateChunkMap(newChunkMap, {chunk}); -} - -TEST_F(ChunkMapTest, ConstructChunkMapRandom) { - auto chunkVector = toChunkInfoPtrVector(genRandomChunkVector()); - - const auto chunkMap = makeChunkMap(chunkVector); - - validateChunkMap(chunkMap, chunkVector); -} - -TEST_F(ChunkMapTest, ConstructChunkMapRandomAllChunksSameVersion) { - auto chunkVector = genRandomChunkVector(); - auto commonVersion = chunkVector.front().getVersion(); - - // Set same version on all chunks - for (auto& chunk : chunkVector) { - chunk.setVersion(commonVersion); - } - - auto chunkInfoVector = toChunkInfoPtrVector(chunkVector); - const auto expectedShardVersions = calculateShardVersions(chunkInfoVector); - const auto expectedCollVersion = calculateCollVersion(expectedShardVersions); - - ASSERT_EQ(commonVersion, expectedCollVersion); - - const auto chunkMap = makeChunkMap(chunkInfoVector); - validateChunkMap(chunkMap, chunkInfoVector); -} - -/* - * Check that constucting a ChunkMap with chunks that have mismatching timestamp fails. - */ -TEST_F(ChunkMapTest, ConstructChunkMapMismatchingTimestamp) { - auto chunkVector = toChunkInfoPtrVector(genRandomChunkVector()); - - // Set a different epoch in one of the chunks - const Timestamp wrongTimestamp{Date_t::now()}; - ASSERT_NE(wrongTimestamp, collTimestamp()); - const auto wrongChunkIdx = _random.nextInt32(chunkVector.size()); - const auto oldChunk = chunkVector.at(wrongChunkIdx); - const auto oldVersion = oldChunk->getLastmod(); - const ChunkVersion wrongVersion{ - oldVersion.majorVersion(), oldVersion.minorVersion(), collEpoch(), wrongTimestamp}; - chunkVector[wrongChunkIdx] = std::make_shared<ChunkInfo>( - ChunkType{uuid(), oldChunk->getRange(), wrongVersion, oldChunk->getShardId()}); - - ASSERT_THROWS_CODE( - makeChunkMap(chunkVector), AssertionException, ErrorCodes::ConflictingOperationInProgress); -} - -TEST_F(ChunkMapTest, UpdateMapNotLeaveSmallVectors) { - const ChunkVersion initialVersion{1, 0, collEpoch(), collTimestamp()}; - auto chunkVector = toChunkInfoPtrVector( - genChunkVector(uuid(), genRandomSplitPoints(8), initialVersion, 1 /*numShards*/)); - - const auto chunkBucketSize = 4; - LOGV2(7162703, "Constructing new chunk map", "chunkBucketSize"_attr = chunkBucketSize); - const auto initialChunkMap = - ChunkMap(collEpoch(), collTimestamp(), chunkBucketSize).createMerged(chunkVector); - - // Check that it contains all the chunks - ASSERT_EQ(chunkVector.size(), initialChunkMap.size()); - - auto mergedVersion = initialChunkMap.getVersion(); - mergedVersion.incMinor(); - - auto mergedChunk = std::make_shared<ChunkInfo>(ChunkType{ - uuid(), - ChunkRange{chunkVector[4]->getRange().getMin(), chunkVector.back()->getRange().getMax()}, - mergedVersion, - kThisShard}); - const auto chunkMap = initialChunkMap.createMerged({mergedChunk}); - - // Check that vectors are balanced in size - auto maxVectorSize = std::lround(chunkMap.getMaxChunkVectorSize() * 1.5); - auto minVectorSize = std::min( - chunkMap.size(), static_cast<size_t>(std::lround(chunkMap.getMaxChunkVectorSize() / 2))); - - for (const auto& [maxKeyString, chunkVectorPtr] : chunkMap.getChunkVectorMap()) { - ASSERT_GTE(chunkVectorPtr->size(), minVectorSize); - ASSERT_LTE(chunkVectorPtr->size(), maxVectorSize); - } - - // Check original map is sitll valid - validateChunkMap(initialChunkMap, chunkVector); -} - - -/* - * Check that updating a ChunkMap with chunks that have mismatching timestamp fails. - */ -TEST_F(ChunkMapTest, UpdateChunkMapMismatchingTimestamp) { - auto chunkVector = toChunkInfoPtrVector(genRandomChunkVector()); - - auto chunkMap = makeChunkMap(chunkVector); - auto collVersion = chunkMap.getVersion(); - - // Set a different epoch in one of the chunks - const Timestamp wrongTimestamp{Date_t::now()}; - const auto wrongChunkIdx = _random.nextInt32(chunkVector.size()); - const auto oldChunk = chunkVector.at(wrongChunkIdx); - const ChunkVersion wrongVersion{ - collVersion.majorVersion(), collVersion.minorVersion(), collEpoch(), wrongTimestamp}; - auto updateChunk = std::make_shared<ChunkInfo>( - ChunkType{uuid(), oldChunk->getRange(), wrongVersion, oldChunk->getShardId()}); - - ASSERT_THROWS_CODE(chunkMap.createMerged({updateChunk}), - AssertionException, - ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Check that updating a ChunkMap with chunks that have lower version fails. - */ -TEST_F(ChunkMapTest, UpdateChunkMapLowerVersion) { - auto chunkVector = toChunkInfoPtrVector(genRandomChunkVector()); - - auto chunkMap = makeChunkMap(chunkVector); - - const auto wrongChunkIdx = _random.nextInt32(chunkVector.size()); - const auto oldChunk = chunkVector.at(wrongChunkIdx); - const ChunkVersion wrongVersion{0, 1, collEpoch(), collTimestamp()}; - auto updateChunk = std::make_shared<ChunkInfo>( - ChunkType{uuid(), oldChunk->getRange(), wrongVersion, oldChunk->getShardId()}); - - ASSERT_THROWS_CODE(chunkMap.createMerged({updateChunk}), AssertionException, 626840); -} -/* - * Test update of ChunkMap with random chunk manipulation (splits/merges/moves); - */ -TEST_F(ChunkMapTest, UpdateChunkMapRandom) { - auto initialChunks = genRandomChunkVector(); - auto initialChunksInfo = toChunkInfoPtrVector(initialChunks); - - const auto initialChunkMap = makeChunkMap(initialChunksInfo); - - const auto initialShardVersions = calculateShardVersions(initialChunksInfo); - const auto initialCollVersion = calculateCollVersion(initialShardVersions); - - auto chunks = initialChunks; - - const auto maxNumChunkOps = 2 * initialChunks.size(); - const auto numChunkOps = _random.nextInt32(maxNumChunkOps); - performRandomChunkOperations(&chunks, numChunkOps); - - auto chunksInfo = toChunkInfoPtrVector(initialChunks, false /* initializeWriteTrackerRandom */); - - std::vector<std::shared_ptr<ChunkInfo>> updatedChunksInfo; - for (auto& chunkPtr : chunksInfo) { - // First overlapping chunk in the initial vector - const auto& overlapInitChunk = - **std::lower_bound(initialChunksInfo.begin(), - initialChunksInfo.end(), - ShardKeyPattern::toKeyString(chunkPtr->getRange().getMin()), - [](const auto& chunkInfo, const std::string& shardKeyString) { - return chunkInfo->getMaxKeyString() <= shardKeyString; - }); - // The new chunks inherits the written bytes from the first overlapping old chunk - chunkPtr->getWritesTracker()->addBytesWritten( - overlapInitChunk.getWritesTracker()->getBytesWritten()); - - if (!chunkPtr->getLastmod().isOlderOrEqualThan(initialCollVersion)) { - updatedChunksInfo.push_back(std::make_shared<ChunkInfo>(ChunkType{ - uuid(), chunkPtr->getRange(), chunkPtr->getLastmod(), chunkPtr->getShardId()})); - } - } - - // Create updated chunk map and validate it - auto chunkMap = initialChunkMap.createMerged(updatedChunksInfo); - validateChunkMap(chunkMap, chunksInfo); - - // Check that the initialChunkMap is still valid and usable - validateChunkMap(initialChunkMap, initialChunksInfo); } TEST_F(ChunkMapTest, TestEnumerateAllChunks) { - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; + const OID epoch = OID::gen(); + ChunkMap chunkMap{epoch, Timestamp(1, 1)}; + ChunkVersion version{1, 0, epoch, Timestamp(1, 1)}; - auto newChunkMap = makeChunkMap( + auto newChunkMap = chunkMap.createMerged( {std::make_shared<ChunkInfo>( ChunkType{uuid(), ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, @@ -385,11 +107,12 @@ TEST_F(ChunkMapTest, TestEnumerateAllChunks) { ASSERT_EQ(count, newChunkMap.size()); } - TEST_F(ChunkMapTest, TestIntersectingChunk) { - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; + const OID epoch = OID::gen(); + ChunkMap chunkMap{epoch, Timestamp(1, 1)}; + ChunkVersion version{1, 0, epoch, Timestamp(1, 1)}; - auto newChunkMap = makeChunkMap( + auto newChunkMap = chunkMap.createMerged( {std::make_shared<ChunkInfo>( ChunkType{uuid(), ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, @@ -412,33 +135,14 @@ TEST_F(ChunkMapTest, TestIntersectingChunk) { SimpleBSONObjComparator::kInstance.evaluate(intersectingChunk->getMin() == BSON("a" << 0))); ASSERT(SimpleBSONObjComparator::kInstance.evaluate(intersectingChunk->getMax() == BSON("a" << 100))); - - // findIntersectingChunks returns last chunk if invoked with MaxKey - intersectingChunk = - newChunkMap.findIntersectingChunk(BSON("a" << getShardKeyPattern().globalMax())); - ASSERT(SimpleBSONObjComparator::kInstance.evaluate(intersectingChunk->getMin() == - BSON("a" << 100))); - ASSERT(SimpleBSONObjComparator::kInstance.evaluate(intersectingChunk->getMax() == - getShardKeyPattern().globalMax())); -} - -TEST_F(ChunkMapTest, TestIntersectingChunkRandom) { - auto chunks = toChunkInfoPtrVector(genRandomChunkVector()); - - const auto chunkMap = makeChunkMap(chunks); - - auto targetChunkIt = chunks.begin() + _random.nextInt64(chunks.size()); - auto intermediateKey = calculateIntermediateShardKey( - (*targetChunkIt)->getMin(), (*targetChunkIt)->getMax(), 0.2 /* minKeyProb */); - - auto intersectingChunkPtr = chunkMap.findIntersectingChunk(intermediateKey); - assertEqualChunkInfo(**(targetChunkIt), *intersectingChunkPtr); } TEST_F(ChunkMapTest, TestEnumerateOverlappingChunks) { - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; + const OID epoch = OID::gen(); + ChunkMap chunkMap{epoch, Timestamp(1, 1)}; + ChunkVersion version{1, 0, epoch, Timestamp(1, 1)}; - auto newChunkMap = makeChunkMap( + auto newChunkMap = chunkMap.createMerged( {std::make_shared<ChunkInfo>( ChunkType{uuid(), ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, @@ -456,101 +160,14 @@ TEST_F(ChunkMapTest, TestEnumerateOverlappingChunks) { auto min = BSON("a" << -50); auto max = BSON("a" << 150); + int count = 0; newChunkMap.forEachOverlappingChunk(min, max, true, [&](const auto& chunk) { count++; return true; }); - ASSERT_EQ(count, 3); - min = BSON("a" << -50); - max = BSON("a" << getShardKeyPattern().globalMax()); - count = 0; - newChunkMap.forEachOverlappingChunk(min, max, false, [&](const auto& chunk) { - count++; - return true; - }); ASSERT_EQ(count, 3); - - min = BSON("a" << 50); - max = BSON("a" << 100); - count = 0; - newChunkMap.forEachOverlappingChunk(min, max, true, [&](const auto& chunk) { - count++; - return true; - }); - ASSERT_EQ(count, 2); - - min = BSON("a" << 50); - max = BSON("a" << 100); - count = 0; - newChunkMap.forEachOverlappingChunk(min, max, false, [&](const auto& chunk) { - count++; - return true; - }); - ASSERT_EQ(count, 1); -} - -TEST_F(ChunkMapTest, ForEachNoShardKey) { - auto chunks = toChunkInfoPtrVector(genRandomChunkVector()); - - const auto chunkMap = makeChunkMap(chunks); - - auto lastChunkIdx = std::max(_random.nextInt64(chunks.size()), static_cast<int64_t>(1)); - - int i = 0; - chunkMap.forEach([&](const auto& chunkInfo) { - assertEqualChunkInfo(*chunks[i], *chunkInfo); - return ++i < lastChunkIdx; - }); - - ASSERT_EQ(i, lastChunkIdx); -} - -TEST_F(ChunkMapTest, ForEachWithShardKey) { - auto chunks = toChunkInfoPtrVector(genRandomChunkVector()); - - const auto chunkMap = makeChunkMap(chunks); - - auto firstChunkIdx = static_cast<size_t>(_random.nextInt64(chunks.size())); - const auto& firstChunk = chunks[firstChunkIdx]; - auto skey = calculateIntermediateShardKey( - firstChunk->getMin(), firstChunk->getMax(), 0.2 /* minKeyProb */); - - size_t i = firstChunkIdx; - auto lastChunkIdx = firstChunkIdx + - std::max(_random.nextInt64(chunks.size() - firstChunkIdx), static_cast<int64_t>(1)); - chunkMap.forEach( - [&](const auto& chunkInfo) { - assertEqualChunkInfo(*chunks[i], *chunkInfo); - return ++i < lastChunkIdx; - }, - skey); - - ASSERT_EQ(i, lastChunkIdx); -} - -TEST_F(ChunkMapTest, TestEnumerateOverlappingChunksRandom) { - auto chunks = toChunkInfoPtrVector(genRandomChunkVector()); - - const auto chunkMap = makeChunkMap(chunks); - - auto firstChunkIt = chunks.begin() + _random.nextInt64(chunks.size()); - auto lastChunkIt = firstChunkIt + _random.nextInt64(std::distance(firstChunkIt, chunks.end())); - - auto minBound = calculateIntermediateShardKey( - (*firstChunkIt)->getMin(), (*firstChunkIt)->getMax(), 0.2 /* minKeyProb */); - auto maxBound = calculateIntermediateShardKey( - (*lastChunkIt)->getMin(), (*lastChunkIt)->getMax(), 0.2 /* minKeyProb */); - - auto it = firstChunkIt; - chunkMap.forEachOverlappingChunk(minBound, maxBound, true, [&](const auto& chunkInfoPtr) { - assertEqualChunkInfo(**(it++), *chunkInfoPtr); - return true; - }); - ASSERT_EQ(0, std::distance(it, std::next(lastChunkIt))); } -} // namespace - } // namespace mongo diff --git a/src/mongo/s/chunks_test_util.cpp b/src/mongo/s/chunks_test_util.cpp deleted file mode 100644 index 4c410ed260a..00000000000 --- a/src/mongo/s/chunks_test_util.cpp +++ /dev/null @@ -1,352 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -#include "mongo/s/chunks_test_util.h" -#include "mongo/db/namespace_string.h" -#include "mongo/logv2/log.h" -#include "mongo/platform/random.h" -#include "mongo/s/shard_key_pattern.h" -#include "mongo/unittest/unittest.h" - -namespace mongo::chunks_test_util { -namespace { - -PseudoRandom _random{SecureRandom().nextInt64()}; - -std::vector<ChunkHistory> genChunkHistory(const ShardId& currentShard, - const Timestamp& onCurrentShardSince, - size_t numShards, - size_t maxLenght) { - std::vector<ChunkHistory> history; - const auto historyLength = _random.nextInt64(maxLenght); - auto lastTime = onCurrentShardSince; - for (int64_t i = 0; i < historyLength; i++) { - auto shard = i == 0 ? currentShard : getShardId(_random.nextInt64(numShards)); - history.emplace_back(onCurrentShardSince, shard); - lastTime = lastTime - 1 - _random.nextInt64(10000); - } - return history; -} - -} // namespace - -void assertEqualChunkInfo(const ChunkInfo& x, const ChunkInfo& y) { - ASSERT_BSONOBJ_EQ(x.getMin(), y.getMin()); - ASSERT_BSONOBJ_EQ(x.getMax(), y.getMax()); - ASSERT_EQ(x.getMaxKeyString(), y.getMaxKeyString()); - ASSERT_EQ(x.getShardId(), y.getShardId()); - ASSERT_EQ(x.getLastmod(), y.getLastmod()); - ASSERT_EQ(x.isJumbo(), y.isJumbo()); - ASSERT_EQ(x.getWritesTracker()->getBytesWritten(), y.getWritesTracker()->getBytesWritten()); -} - -ShardId getShardId(int shardIdx) { - return {std::string(str::stream() << "shard_" << shardIdx)}; -} - -std::vector<BSONObj> genRandomSplitPoints(size_t numChunks) { - std::vector<BSONObj> splitPoints; - splitPoints.reserve(numChunks + 1); - splitPoints.emplace_back(kShardKeyPattern.globalMin()); - int64_t nextSplit{-1000}; - for (size_t i = 0; i < numChunks - 1; ++i) { - nextSplit += 10 * (_random.nextInt32(10) + 1); - splitPoints.emplace_back(BSON(kSKey << nextSplit)); - } - splitPoints.emplace_back(kShardKeyPattern.globalMax()); - return splitPoints; -} - -std::vector<ChunkVersion> genRandomVersions(size_t num, const ChunkVersion& initialVersion) { - std::vector<ChunkVersion> versions; - versions.reserve(num); - auto major = initialVersion.majorVersion(); - auto minor = initialVersion.minorVersion(); - - for (size_t i = 0; i < num; ++i) { - if (_random.nextInt32(2)) { - ++major; - minor = 0; - } else { - ++minor; - } - versions.emplace_back(major, minor, initialVersion.epoch(), initialVersion.getTimestamp()); - } - std::shuffle(versions.begin(), versions.end(), _random.urbg()); - return versions; -} - -std::vector<ChunkType> genChunkVector(const UUID& uuid, - const std::vector<BSONObj>& splitPoints, - const ChunkVersion& initialVersion, - size_t numShards) { - - return genChunkVector( - uuid, splitPoints, genRandomVersions(splitPoints.size() - 1, initialVersion), numShards); -} - -std::vector<ChunkType> genChunkVector(const UUID& uuid, - const std::vector<BSONObj>& splitPoints, - const std::vector<ChunkVersion>& versions, - size_t numShards) { - - invariant(SimpleBSONObjComparator::kInstance.evaluate(splitPoints.front() == - kShardKeyPattern.globalMin())); - invariant(SimpleBSONObjComparator::kInstance.evaluate(splitPoints.back() == - kShardKeyPattern.globalMax())); - const auto numChunks = splitPoints.size() - 1; - invariant(numChunks == versions.size()); - - std::vector<ChunkType> chunks; - chunks.reserve(numChunks); - auto minKey = splitPoints.front(); - for (size_t i = 0; i < numChunks; ++i) { - auto maxKey = splitPoints.at(i + 1); - const auto shard = getShardId(_random.nextInt64(numShards)); - const auto version = versions.at(i); - ChunkType chunk{uuid, ChunkRange{minKey, maxKey}, version, shard}; - chunk.setHistory( - genChunkHistory(shard, Timestamp{Date_t::now()}, numShards, 10 /* maxLenght */)); - chunks.emplace_back(std::move(chunk)); - minKey = std::move(maxKey); - } - return chunks; -} - -std::map<ShardId, Timestamp> calculateShardsMaxValidAfter( - const std::vector<ChunkType>& chunkVector) { - - std::map<ShardId, Timestamp> vaMap; - for (const auto& chunk : chunkVector) { - if (chunk.getHistory().empty()) - continue; - - const auto& chunkMaxValidAfter = chunk.getHistory().front().getValidAfter(); - auto mapIt = vaMap.find(chunk.getShard()); - if (mapIt == vaMap.end()) { - vaMap.emplace(chunk.getShard(), chunkMaxValidAfter); - continue; - } - if (chunkMaxValidAfter > mapIt->second) { - mapIt->second = chunkMaxValidAfter; - } - } - return vaMap; -} - -ChunkVersion calculateCollVersion(const std::map<ShardId, ChunkVersion>& shardVersions) { - return std::max_element(shardVersions.begin(), - shardVersions.end(), - [](const std::pair<ShardId, ChunkVersion>& p1, - const std::pair<ShardId, ChunkVersion>& p2) { - return p1.second.isOlderThan(p2.second); - }) - ->second; -} - -std::map<ShardId, ChunkVersion> calculateShardVersions(const std::vector<ChunkType>& chunkVector) { - std::map<ShardId, ChunkVersion> svMap; - for (const auto& chunk : chunkVector) { - auto mapIt = svMap.find(chunk.getShard()); - if (mapIt == svMap.end()) { - svMap.emplace(chunk.getShard(), chunk.getVersion()); - continue; - } - if (mapIt->second.isOlderThan(chunk.getVersion())) { - mapIt->second = chunk.getVersion(); - } - } - return svMap; -} - -std::vector<ChunkType> genRandomChunkVector(const UUID& uuid, - const OID& epoch, - const Timestamp& timestamp, - size_t maxNumChunks, - size_t minNumChunks) { - invariant(minNumChunks <= maxNumChunks); - const auto numChunks = minNumChunks + _random.nextInt32((maxNumChunks - minNumChunks) + 1); - const auto numShards = _random.nextInt32(numChunks) + 1; - const ChunkVersion initialVersion{1, 0, epoch, timestamp}; - - LOGV2(7162700, - "Generating random chunk vector", - "numChunks"_attr = numChunks, - "numShards"_attr = numShards); - - return genChunkVector(uuid, genRandomSplitPoints(numChunks), initialVersion, numShards); -} - -BSONObj calculateIntermediateShardKey(const BSONObj& leftKey, - const BSONObj& rightKey, - double minKeyProb, - double maxKeyProb) { - invariant(0 <= minKeyProb && minKeyProb <= 1, "minKeyProb out of range [0, 1]"); - invariant(0 <= maxKeyProb && maxKeyProb <= 1, "maxKeyProb out of range [0, 1]"); - - if (_random.nextInt32(100) < minKeyProb * 100) { - return leftKey; - } - - if (_random.nextInt32(100) < maxKeyProb * 100) { - return rightKey; - } - - const auto isMinKey = leftKey.woCompare(kShardKeyPattern.globalMin()) == 0; - const auto isMaxKey = rightKey.woCompare(kShardKeyPattern.globalMax()) == 0; - - int64_t splitPoint; - if (isMinKey && isMaxKey) { - // [min, max] -> split at 0 - splitPoint = 0; - } else if (!isMinKey && !isMaxKey) { - // [x, y] -> split in the middle - auto min = leftKey.firstElement().numberLong(); - auto max = rightKey.firstElement().numberLong(); - invariant(min + 1 < max, - str::stream() << "Can't split range [" << min << ", " << max << "]"); - splitPoint = min + ((max - min) / 2); - } else if (isMaxKey) { - // [x, maxKey] -> split at x*2; - auto prevBound = leftKey.firstElement().numberLong(); - auto increment = prevBound ? prevBound : _random.nextInt32(100) + 1; - splitPoint = prevBound + std::abs(increment); - } else if (isMinKey) { - // [minKey, x] -> split at x*2; - auto prevBound = rightKey.firstElement().numberLong(); - auto increment = prevBound ? prevBound : _random.nextInt32(100) + 1; - splitPoint = prevBound - std::abs(increment); - } else { - MONGO_UNREACHABLE; - } - - return BSON(kSKey << splitPoint); -} - -void performRandomChunkOperations(std::vector<ChunkType>* chunksPtr, size_t numOperations) { - auto& chunks = *chunksPtr; - auto collVersion = calculateCollVersion(calculateShardVersions(chunks)); - - auto moveChunk = [&] { - auto& chunkToMigrate = chunks[_random.nextInt32(chunks.size())]; - collVersion.incMajor(); - - auto controlChunkIt = std::find_if(chunks.begin(), chunks.end(), [&](const auto& chunk) { - return chunk.getShard() == chunkToMigrate.getShard() && - !chunk.getRange().overlaps(chunkToMigrate.getRange()); - }); - if (controlChunkIt != chunks.end()) { - controlChunkIt->setVersion(collVersion); - collVersion.incMinor(); - } - auto newShard = getShardId(_random.nextInt64(chunks.size())); - chunkToMigrate.setShard(newShard); - chunkToMigrate.setVersion(collVersion); - chunkToMigrate.setHistory([&] { - auto history = chunkToMigrate.getHistory(); - history.emplace(history.begin(), Timestamp{Date_t::now()}, newShard); - return history; - }()); - }; - - auto splitChunk = [&] { - auto chunkToSplitIt = chunks.begin() + _random.nextInt32(chunks.size()); - while (chunkToSplitIt != chunks.begin() && chunkToSplitIt != std::prev(chunks.end()) && - (chunkToSplitIt->getMax().firstElement().numberLong() - - chunkToSplitIt->getMin().firstElement().numberLong()) < 2) { - // If the chunk is unsplittable select another one - chunkToSplitIt = chunks.begin() + _random.nextInt32(chunks.size()); - } - - const auto& chunkToSplit = *chunkToSplitIt; - - auto splitKey = calculateIntermediateShardKey(chunkToSplit.getMin(), chunkToSplit.getMax()); - - collVersion.incMinor(); - const ChunkRange leftRange{chunkToSplit.getMin(), splitKey}; - ChunkType leftChunk{ - chunkToSplit.getCollectionUUID(), leftRange, collVersion, chunkToSplit.getShard()}; - leftChunk.setHistory(chunkToSplit.getHistory()); - - collVersion.incMinor(); - const ChunkRange rightRange{splitKey, chunkToSplit.getMax()}; - ChunkType rightChunk{ - chunkToSplit.getCollectionUUID(), rightRange, collVersion, chunkToSplit.getShard()}; - rightChunk.setHistory(chunkToSplit.getHistory()); - - auto it = chunks.erase(chunkToSplitIt); - it = chunks.insert(it, std::move(rightChunk)); - it = chunks.insert(it, std::move(leftChunk)); - }; - - auto mergeChunks = [&] { - const auto firstChunkIt = chunks.begin() + _random.nextInt32(chunks.size()); - const auto& shardId = firstChunkIt->getShard(); - auto lastChunkIt = std::find_if(firstChunkIt, chunks.end(), [&](const auto& chunk) { - return chunk.getShard() != shardId; - }); - const auto numContiguosChunks = std::distance(firstChunkIt, lastChunkIt); - if (numContiguosChunks < 2) { - // nothing to merge - return; - } - const auto numChunkToMerge = _random.nextInt32(numContiguosChunks - 1) + 2; - lastChunkIt = firstChunkIt + numChunkToMerge; - const auto& firstChunk = *firstChunkIt; - collVersion.incMinor(); - const ChunkRange mergedRange{firstChunk.getMin(), std::prev(lastChunkIt)->getMax()}; - ChunkType mergedChunk{ - firstChunk.getCollectionUUID(), mergedRange, collVersion, firstChunk.getShard()}; - mergedChunk.setHistory({ChunkHistory{Timestamp{Date_t::now()}, firstChunk.getShard()}}); - - auto it = chunks.erase(firstChunkIt, lastChunkIt); - it = chunks.insert(it, mergedChunk); - }; - - for (size_t i = 0; i < numOperations; i++) { - switch (_random.nextInt32(3)) { - case 0: - moveChunk(); - break; - case 1: - splitChunk(); - break; - case 2: - mergeChunks(); - break; - default: - MONGO_UNREACHABLE; - break; - } - } -} - -} // namespace mongo::chunks_test_util diff --git a/src/mongo/s/chunks_test_util.h b/src/mongo/s/chunks_test_util.h deleted file mode 100644 index b271939caa5..00000000000 --- a/src/mongo/s/chunks_test_util.h +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/s/chunk.h" -#include "mongo/s/chunk_writes_tracker.h" - -namespace mongo::chunks_test_util { - -static const std::string kSKey{"a"}; -static const KeyPattern kShardKeyPattern{BSON(kSKey << 1)}; - -/* - * Assert that all the fields contained in the provided ChunkInfo are equal. - * - * This is needed since ChunkInfo class does not provide an equal operator. - */ -void assertEqualChunkInfo(const ChunkInfo& x, const ChunkInfo& y); - -ShardId getShardId(int shardIdx); - -/** - * Return a vector of randomly generated split points. - * covering the entire shard key space including the boundaries [minKey, maxKey) - * - * e.g. {"a": <int>} - */ -std::vector<BSONObj> genRandomSplitPoints(size_t numChunks); - -/* - * Generate a shuffled list of random chunk versions. - * - * The generated versions are all strictly greater than the provided initialVersion. - */ -std::vector<ChunkVersion> genRandomVersions(size_t num, const ChunkVersion& initialVersion); - -/* - * Generate a vector of chunks whose boundaries are defined by the provided split points and random - * chunk versions. - */ -std::vector<ChunkType> genChunkVector(const UUID& uuid, - const std::vector<BSONObj>& splitPoints, - const ChunkVersion& initialVersion, - size_t numShards); - -/* - * Generate a vector of chunks. - */ -std::vector<ChunkType> genChunkVector(const UUID& uuid, - const std::vector<BSONObj>& splitPoints, - const std::vector<ChunkVersion>& versions, - size_t numShards); - -/* - * Return a randomly generated vector of chunks that are properly sorted based on their min value - * and cover the full space from [MinKey, MaxKey]. - */ -std::vector<ChunkType> genRandomChunkVector(const UUID& uuid, - const OID& epoch, - const Timestamp& timestamp, - size_t maxNumChunks, - size_t minNumChunks = 1); - -std::map<ShardId, ChunkVersion> calculateShardVersions(const std::vector<ChunkType>& chunkVector); - -std::map<ShardId, Timestamp> calculateShardsMaxValidAfter( - const std::vector<ChunkType>& chunkVector); - -ChunkVersion calculateCollVersion(const std::map<ShardId, ChunkVersion>& shardVersions); - -/* - * Return a shardkey that is in between the given range [leftKey, rightKey] - */ -BSONObj calculateIntermediateShardKey(const BSONObj& leftKey, - const BSONObj& rightKey, - double minKeyProb = 0.0, - double maxKeyProb = 0.0); - -/* - * Perform a series of random operations on the given list of chunks. - * - * The operations performed resemble all possible operations that could happen to a routing table in - * a production cluster (move, merge, split, etc...) - * - * @chunks: list of chunks ordered by minKey - * @numOperations: number of operations to perform - */ -void performRandomChunkOperations(std::vector<ChunkType>* chunks, size_t numOperations); - -} // namespace mongo::chunks_test_util diff --git a/src/mongo/s/client/shard.cpp b/src/mongo/s/client/shard.cpp index 1408b227b1e..ac360694af4 100644 --- a/src/mongo/s/client/shard.cpp +++ b/src/mongo/s/client/shard.cpp @@ -41,6 +41,7 @@ namespace mongo { namespace { const int kOnErrorNumRetries = 3; + } // namespace Status Shard::CommandResponse::getEffectiveStatus( @@ -193,6 +194,36 @@ StatusWith<Shard::QueryResponse> Shard::runExhaustiveCursorCommand( MONGO_UNREACHABLE; } +BatchedCommandResponse Shard::runBatchWriteCommand(OperationContext* opCtx, + const Milliseconds maxTimeMS, + const BatchedCommandRequest& batchRequest, + RetryPolicy retryPolicy) { + const StringData dbname = batchRequest.getNS().db(); + const BSONObj cmdObj = batchRequest.toBSON(); + + for (int retry = 1; retry <= kOnErrorNumRetries; ++retry) { + // Note: write commands can only be issued against a primary. + auto swResponse = _runCommand( + opCtx, ReadPreferenceSetting{ReadPreference::PrimaryOnly}, dbname, maxTimeMS, cmdObj); + + BatchedCommandResponse batchResponse; + auto writeStatus = CommandResponse::processBatchWriteResponse(swResponse, &batchResponse); + if (retry < kOnErrorNumRetries && isRetriableError(writeStatus.code(), retryPolicy)) { + LOGV2_DEBUG(22721, + 2, + "Batch write command to shard {shardId} failed with retryable error " + "and will be retried. Caused by {error}", + "Batch write command failed with retryable error and will be retried", + "shardId"_attr = getId(), + "error"_attr = redact(writeStatus)); + continue; + } + + return batchResponse; + } + MONGO_UNREACHABLE; +} + StatusWith<Shard::QueryResponse> Shard::exhaustiveFindOnConfig( OperationContext* opCtx, const ReadPreferenceSetting& readPref, @@ -219,34 +250,4 @@ StatusWith<Shard::QueryResponse> Shard::exhaustiveFindOnConfig( MONGO_UNREACHABLE; } -BatchedCommandResponse Shard::_submitBatchWriteCommand(OperationContext* opCtx, - const BSONObj& serialisedBatchRequest, - StringData dbName, - Milliseconds maxTimeMS, - RetryPolicy retryPolicy) { - for (int retry = 1; retry <= kOnErrorNumRetries; ++retry) { - // Note: write commands can only be issued against a primary. - auto swResponse = _runCommand(opCtx, - ReadPreferenceSetting{ReadPreference::PrimaryOnly}, - dbName, - maxTimeMS, - serialisedBatchRequest); - - BatchedCommandResponse batchResponse; - auto writeStatus = CommandResponse::processBatchWriteResponse(swResponse, &batchResponse); - if (retry < kOnErrorNumRetries && isRetriableError(writeStatus.code(), retryPolicy)) { - LOGV2_DEBUG(22721, - 2, - "Batch write command failed with retryable error and will be retried", - "shardId"_attr = getId(), - "error"_attr = redact(writeStatus)); - continue; - } - - return batchResponse; - } - MONGO_UNREACHABLE; -} - - } // namespace mongo diff --git a/src/mongo/s/client/shard.h b/src/mongo/s/client/shard.h index e545f0b2f6b..f690341fcb2 100644 --- a/src/mongo/s/client/shard.h +++ b/src/mongo/s/client/shard.h @@ -226,11 +226,10 @@ public: * commands return errors in a different format than regular commands do, so checking for * retriable errors must be done differently. */ - virtual BatchedCommandResponse runBatchWriteCommand(OperationContext* opCtx, - Milliseconds maxTimeMS, - const BatchedCommandRequest& batchRequest, - const WriteConcernOptions& writeConcern, - RetryPolicy retryPolicy) = 0; + BatchedCommandResponse runBatchWriteCommand(OperationContext* opCtx, + Milliseconds maxTimeMS, + const BatchedCommandRequest& batchRequest, + RetryPolicy retryPolicy); /** * Warning: This method exhausts the cursor and pulls all data into memory. @@ -293,22 +292,10 @@ public: protected: Shard(const ShardId& id); - /** - * Submits the batch request applying the specified retry policy and timeout and using the - * machinery provided by each implementation. - * Callers of this function must ensure to have configured the write concern settings - * accordingly to their specific semantics. - */ - BatchedCommandResponse _submitBatchWriteCommand(OperationContext* opCtx, - const BSONObj& serialisedBatchRequest, - StringData dbName, - Milliseconds maxTimeMS, - RetryPolicy retryPolicy); - private: /** - * Runs the specified command against the shard backed by this object with a timeout set to - * the minimum of maxTimeMSOverride or the timeout of the OperationContext. + * Runs the specified command against the shard backed by this object with a timeout set to the + * minimum of maxTimeMSOverride or the timeout of the OperationContext. * * The return value exposes RemoteShard's host for calls to updateReplSetMonitor. * diff --git a/src/mongo/s/client/shard_registry.cpp b/src/mongo/s/client/shard_registry.cpp index dfb3932f3c0..d722a9116ec 100644 --- a/src/mongo/s/client/shard_registry.cpp +++ b/src/mongo/s/client/shard_registry.cpp @@ -130,8 +130,12 @@ ShardRegistry::Cache::LookupResult ShardRegistry::_lookup(OperationContext* opCt // Check if we need to refresh from the configsvrs. If so, then do that and get the results, // otherwise (this is a lookup only to incorporate updated connection strings from the RSM), // then get the equivalent values from the previously cached data. - auto [returnData, returnTopologyTime, returnForceReloadIncrement, removedShards] = - [&]() -> std::tuple<ShardRegistryData, Timestamp, Increment, ShardRegistryData::ShardMap> { + auto [returnData, + returnTopologyTime, + returnForceReloadIncrement, + removedShards, + fetchedFromConfigServers] = [&]() + -> std::tuple<ShardRegistryData, Timestamp, Increment, ShardRegistryData::ShardMap, bool> { if (timeInStore.topologyTime > cachedData.getTime().topologyTime || timeInStore.forceReloadIncrement > cachedData.getTime().forceReloadIncrement) { auto [reloadedData, maxTopologyTime] = @@ -140,12 +144,14 @@ ShardRegistry::Cache::LookupResult ShardRegistry::_lookup(OperationContext* opCt auto [mergedData, removedShards] = ShardRegistryData::mergeExisting(*cachedData, reloadedData); - return {mergedData, maxTopologyTime, timeInStore.forceReloadIncrement, removedShards}; + return { + mergedData, maxTopologyTime, timeInStore.forceReloadIncrement, removedShards, true}; } else { return {*cachedData, cachedData.getTime().topologyTime, cachedData.getTime().forceReloadIncrement, - {}}; + {}, + false}; } }(); @@ -180,6 +186,11 @@ ShardRegistry::Cache::LookupResult ShardRegistry::_lookup(OperationContext* opCt } } + // The registry is "up" once there has been a successful lookup from the config servers. + if (fetchedFromConfigServers) { + _isUp.store(true); + } + Time returnTime{returnTopologyTime, rsmIncrementForConnStrings, returnForceReloadIncrement}; LOGV2_DEBUG(4620251, 2, @@ -207,9 +218,9 @@ void ShardRegistry::startupPeriodicReloader(OperationContext* opCtx) { AsyncTry([this] { LOGV2_DEBUG(22726, 1, "Reloading shardRegistry"); - return _reloadAsyncNoRetry(); + return _reloadInternal(); }) - .until([](auto&& sw) { + .until([](auto sw) { if (!sw.isOK()) { LOGV2(22727, "Error running periodic reload of shard registry", @@ -221,7 +232,7 @@ void ShardRegistry::startupPeriodicReloader(OperationContext* opCtx) { }) .withDelayBetweenIterations(kRefreshPeriod) // This call is optional. .on(_executor, CancellationToken::uncancelable()) - .getAsync([](auto&& sw) { + .getAsync([](auto sw) { LOGV2_DEBUG(22725, 1, "Exiting periodic shard registry reloader", @@ -284,49 +295,6 @@ StatusWith<std::shared_ptr<Shard>> ShardRegistry::getShard(OperationContext* opC return {ErrorCodes::ShardNotFound, str::stream() << "Shard " << shardId << " not found"}; } -SemiFuture<std::shared_ptr<Shard>> ShardRegistry::getShard(ExecutorPtr executor, - const ShardId& shardId) noexcept { - - // Fetch the shard registry data associated to the latest known topology time - return _getDataAsync() - .thenRunOn(executor) - .then([this, executor, shardId](auto&& cachedData) { - // First check if this is a non config shard lookup - if (auto shard = cachedData->findShard(shardId)) { - return SemiFuture<std::shared_ptr<Shard>>::makeReady(std::move(shard)); - } - - // then check if this is a config shard (this call is blocking in any case) - { - stdx::lock_guard<Latch> lk(_mutex); - if (auto shard = _configShardData.findShard(shardId)) { - return SemiFuture<std::shared_ptr<Shard>>::makeReady(std::move(shard)); - } - } - - // If the shard was not found, force reload the shard regitry data and try again. - // - // This is to cover the following scenario: - // 1. Primary of the replicaset fetch the list of shards and store it on disk - // 2. Primary crash before the latest VectorClock topology time is majority written to - // disk - // 3. A new primary with a stale ShardRegistry is elected and read the set of shards - // from disk and calls ShardRegistry::getShard - - return _reloadAsync() - .thenRunOn(executor) - .then([this, executor, shardId](auto&& cachedData) -> std::shared_ptr<Shard> { - auto shard = cachedData->findShard(shardId); - uassert(ErrorCodes::ShardNotFound, - str::stream() << "Shard " << shardId << " not found", - shard); - return shard; - }) - .semi(); - }) - .semi(); -} - std::vector<ShardId> ShardRegistry::getAllShardIds(OperationContext* opCtx) { auto shardIds = _getData(opCtx)->getAllShardIds(); if (shardIds.empty()) { @@ -412,18 +380,8 @@ std::unique_ptr<Shard> ShardRegistry::createConnection(const ConnectionString& c return _shardFactory->createUniqueShard(ShardId("<unnamed>"), connStr); } -bool ShardRegistry::isUp() { - if (_isUp.load()) - return true; - - // Before the first lookup is completed, the latest cached value is either empty or it is - // associated to the default constructed time - const auto latestCached = _cache->peekLatestCached(_kSingleton); - if (latestCached && latestCached.getTime() != Time()) { - _isUp.store(true); - return true; - } - return false; +bool ShardRegistry::isUp() const { + return _isUp.load(); } void ShardRegistry::toBSON(BSONObjBuilder* result) const { @@ -442,26 +400,23 @@ void ShardRegistry::toBSON(BSONObjBuilder* result) const { } void ShardRegistry::reload(OperationContext* opCtx) { - _reloadAsync().get(opCtx); -} - -SharedSemiFuture<ShardRegistry::Cache::ValueHandle> ShardRegistry::_reloadAsync() { if (MONGO_unlikely(TestingProctor::instance().isEnabled())) { // Some unit tests don't support running the reload's AsyncTry on the fixed executor. - return _reloadAsyncNoRetry(); + _reloadInternal().get(opCtx); } else { - return AsyncTry([=]() mutable { return _reloadAsyncNoRetry(); }) + AsyncTry([=]() mutable { return _reloadInternal(); }) .until([](auto sw) mutable { return sw.getStatus() != ErrorCodes::ReadConcernMajorityNotAvailableYet; }) .withBackoffBetweenIterations(kExponentialBackoff) - .on(Grid::get(getGlobalServiceContext())->getExecutorPool()->getFixedExecutor(), + .on(Grid::get(opCtx)->getExecutorPool()->getFixedExecutor(), CancellationToken::uncancelable()) - .share(); + .semi() + .get(opCtx); } } -SharedSemiFuture<ShardRegistry::Cache::ValueHandle> ShardRegistry::_reloadAsyncNoRetry() { +SharedSemiFuture<ShardRegistry::Cache::ValueHandle> ShardRegistry::_reloadInternal() { // Make the next acquire do a lookup. auto value = _forceReloadIncrement.addAndFetch(1); LOGV2_DEBUG(4620253, 2, "Forcing ShardRegistry reload", "newForceReloadIncrement"_attr = value); @@ -614,7 +569,7 @@ std::pair<ShardRegistryData, Timestamp> ShardRegistryData::createFromCatalogClie OperationContext* opCtx, ShardFactory* shardFactory) { auto const catalogClient = Grid::get(opCtx)->catalogClient(); - auto readConcern = repl::ReadConcernLevel::kSnapshotReadConcern; + auto readConcern = repl::ReadConcernLevel::kMajorityReadConcern; // ShardRemote requires a majority read. We can only allow a non-majority read if we are a // config server. diff --git a/src/mongo/s/client/shard_registry.h b/src/mongo/s/client/shard_registry.h index aceb45db9d2..2c329f11b4c 100644 --- a/src/mongo/s/client/shard_registry.h +++ b/src/mongo/s/client/shard_registry.h @@ -239,9 +239,6 @@ public: */ StatusWith<std::shared_ptr<Shard>> getShard(OperationContext* opCtx, const ShardId& shardId); - SemiFuture<std::shared_ptr<Shard>> getShard(ExecutorPtr executor, - const ShardId& shardId) noexcept; - /** * Returns a vector containing all known shard IDs. * The order of the elements is not guaranteed. @@ -275,7 +272,7 @@ public: * The ShardRegistry is "up" once a successful lookup from the config servers has been * completed. */ - bool isUp(); + bool isUp() const; void toBSON(BSONObjBuilder* result) const; @@ -441,8 +438,7 @@ private: void _initializeCacheIfNecessary() const; - SharedSemiFuture<Cache::ValueHandle> _reloadAsync(); - SharedSemiFuture<Cache::ValueHandle> _reloadAsyncNoRetry(); + SharedSemiFuture<Cache::ValueHandle> _reloadInternal(); /** * Factory to create shards. Never changed after startup so safe to access outside of _mutex. diff --git a/src/mongo/s/client/shard_remote.cpp b/src/mongo/s/client/shard_remote.cpp index 156dad7171a..b678c276510 100644 --- a/src/mongo/s/client/shard_remote.cpp +++ b/src/mongo/s/client/shard_remote.cpp @@ -378,16 +378,10 @@ StatusWith<Shard::QueryResponse> ShardRemote::_exhaustiveFindOnConfig( }(); BSONObj readConcernObj = [&] { - auto readConcern = [&] { - if (readConcernLevel == repl::ReadConcernLevel::kMajorityReadConcern) { - repl::OpTime configOpTime{configTime.asTimestamp(), - mongo::repl::OpTime::kUninitializedTerm}; - return repl::ReadConcernArgs{configOpTime, readConcernLevel}; - } else { - invariant(readConcernLevel == repl::ReadConcernLevel::kSnapshotReadConcern); - return repl::ReadConcernArgs{configTime, readConcernLevel}; - } - }(); + invariant(readConcernLevel == repl::ReadConcernLevel::kMajorityReadConcern); + repl::OpTime configOpTime{configTime.asTimestamp(), + mongo::repl::OpTime::kUninitializedTerm}; + repl::ReadConcernArgs readConcern{configOpTime, readConcernLevel}; BSONObjBuilder bob; readConcern.appendInfo(&bob); return bob.done().getObjectField(repl::ReadConcernArgs::kReadConcernFieldName).getOwned(); @@ -540,23 +534,6 @@ Status ShardRemote::runAggregation( } -BatchedCommandResponse ShardRemote::runBatchWriteCommand(OperationContext* opCtx, - const Milliseconds maxTimeMS, - const BatchedCommandRequest& batchRequest, - const WriteConcernOptions& writeConcern, - RetryPolicy retryPolicy) { - const auto dbName = batchRequest.getNS().db(); - const BSONObj cmdObj = [&] { - BSONObjBuilder cmdObjBuilder; - batchRequest.serialize(&cmdObjBuilder); - cmdObjBuilder.append(WriteConcernOptions::kWriteConcernField, writeConcern.toBSON()); - return cmdObjBuilder.obj(); - }(); - - return _submitBatchWriteCommand(opCtx, cmdObj, dbName, maxTimeMS, retryPolicy); -} - - StatusWith<ShardRemote::AsyncCmdHandle> ShardRemote::_scheduleCommand( OperationContext* opCtx, const ReadPreferenceSetting& readPref, diff --git a/src/mongo/s/client/shard_remote.h b/src/mongo/s/client/shard_remote.h index 6aec3f87da5..6c99a8a5247 100644 --- a/src/mongo/s/client/shard_remote.h +++ b/src/mongo/s/client/shard_remote.h @@ -91,12 +91,6 @@ public: std::function<bool(const std::vector<BSONObj>& batch, const boost::optional<BSONObj>& postBatchResumeToken)> callback); - BatchedCommandResponse runBatchWriteCommand(OperationContext* opCtx, - Milliseconds maxTimeMS, - const BatchedCommandRequest& batchRequest, - const WriteConcernOptions& writeConcern, - RetryPolicy retryPolicy) final; - private: struct AsyncCmdHandle { HostAndPort hostTargetted; diff --git a/src/mongo/s/client/sharding_connection_hook.cpp b/src/mongo/s/client/sharding_connection_hook.cpp index bc72c6ea064..0a7374a240e 100644 --- a/src/mongo/s/client/sharding_connection_hook.cpp +++ b/src/mongo/s/client/sharding_connection_hook.cpp @@ -73,14 +73,14 @@ void ShardingConnectionHook::onCreate(DBClientBase* conn) { if (conn->type() == ConnectionString::ConnectionType::kStandalone) { - BSONObj helloResponse; - if (!conn->runCommand("admin", BSON("hello" << 1), helloResponse)) { - uassertStatusOK(getStatusFromCommandResult(helloResponse)); + BSONObj isMasterResponse; + if (!conn->runCommand("admin", BSON("ismaster" << 1), isMasterResponse)) { + uassertStatusOK(getStatusFromCommandResult(isMasterResponse)); } long long configServerModeNumber; Status status = - bsonExtractIntegerField(helloResponse, "configsvr", &configServerModeNumber); + bsonExtractIntegerField(isMasterResponse, "configsvr", &configServerModeNumber); if (status == ErrorCodes::NoSuchKey) { // This isn't a config server we're talking to. diff --git a/src/mongo/s/client/sharding_network_connection_hook.cpp b/src/mongo/s/client/sharding_network_connection_hook.cpp index 60368d2eb0f..a6229316eaa 100644 --- a/src/mongo/s/client/sharding_network_connection_hook.cpp +++ b/src/mongo/s/client/sharding_network_connection_hook.cpp @@ -48,12 +48,12 @@ namespace mongo { Status ShardingNetworkConnectionHook::validateHost( const HostAndPort& remoteHost, const BSONObj&, - const executor::RemoteCommandResponse& helloReply) { - return validateHostImpl(remoteHost, helloReply); + const executor::RemoteCommandResponse& isMasterReply) { + return validateHostImpl(remoteHost, isMasterReply); } Status ShardingNetworkConnectionHook::validateHostImpl( - const HostAndPort& remoteHost, const executor::RemoteCommandResponse& helloReply) { + const HostAndPort& remoteHost, const executor::RemoteCommandResponse& isMasterReply) { auto shard = Grid::get(getGlobalServiceContext())->shardRegistry()->getShardForHostNoReload(remoteHost); if (!shard) { @@ -62,11 +62,11 @@ Status ShardingNetworkConnectionHook::validateHostImpl( } long long configServerModeNumber; - auto status = bsonExtractIntegerField(helloReply.data, "configsvr", &configServerModeNumber); + auto status = bsonExtractIntegerField(isMasterReply.data, "configsvr", &configServerModeNumber); switch (status.code()) { case ErrorCodes::OK: { - // The hello response indicates remoteHost is a config server. + // The ismaster response indicates remoteHost is a config server. if (!shard->isConfig()) { return {ErrorCodes::InvalidOptions, str::stream() << "Surprised to discover that " << remoteHost.toString() @@ -75,7 +75,7 @@ Status ShardingNetworkConnectionHook::validateHostImpl( return Status::OK(); } case ErrorCodes::NoSuchKey: { - // The hello response indicates that remoteHost is not a config server, or that + // The ismaster response indicates that remoteHost is not a config server, or that // the config server is running a version prior to the 3.1 development series. if (!shard->isConfig()) { return Status::OK(); @@ -86,7 +86,7 @@ Status ShardingNetworkConnectionHook::validateHostImpl( << " does not believe it is a config server"}; } default: - // The hello response was malformed. + // The ismaster response was malformed. return status; } } diff --git a/src/mongo/s/client/sharding_network_connection_hook.h b/src/mongo/s/client/sharding_network_connection_hook.h index d3715dcec1b..c77428e56d9 100644 --- a/src/mongo/s/client/sharding_network_connection_hook.h +++ b/src/mongo/s/client/sharding_network_connection_hook.h @@ -45,18 +45,18 @@ public: /** * Checks that the given host is valid to be used in this sharded cluster, based on its - * "hello" response. + * isMaster response. */ Status validateHost(const HostAndPort& remoteHost, const BSONObj& request, - const executor::RemoteCommandResponse& helloReply) override; + const executor::RemoteCommandResponse& isMasterReply) override; /** * Implementation of validateHost can be called without a ShardingNetworkConnectionHook * instance. */ static Status validateHostImpl(const HostAndPort& remoteHost, - const executor::RemoteCommandResponse& helloReply); + const executor::RemoteCommandResponse& isMasterReply); /** * Makes a SetShardVersion request for initializing sharding information on the new connection. diff --git a/src/mongo/s/cluster_commands_helpers.cpp b/src/mongo/s/cluster_commands_helpers.cpp index 1a84121bf9e..5c0469ab245 100644 --- a/src/mongo/s/cluster_commands_helpers.cpp +++ b/src/mongo/s/cluster_commands_helpers.cpp @@ -35,8 +35,6 @@ #include "mongo/s/cluster_commands_helpers.h" -#include "mongo/bson/mutable/algorithm.h" -#include "mongo/bson/mutable/document.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/db/catalog/collection_uuid_mismatch_info.h" #include "mongo/db/commands.h" @@ -48,7 +46,6 @@ #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/repl/read_concern_args.h" -#include "mongo/db/repl/read_concern_level.h" #include "mongo/executor/task_executor_pool.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/rpc/write_concern_error_detail.h" @@ -64,11 +61,6 @@ #include "mongo/s/transaction_router.h" #include "mongo/util/scopeguard.h" -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand - -using mongo::repl::ReadConcernArgs; -using mongo::repl::ReadConcernLevel; - namespace mongo { void appendWriteConcernErrorDetailToCmdResponse(const ShardId& shardId, @@ -139,14 +131,13 @@ namespace { * caller. */ std::vector<AsyncRequestsSender::Request> buildVersionedRequestsForTargetedShards( - boost::intrusive_ptr<ExpressionContext> expCtx, + OperationContext* opCtx, const NamespaceString& nss, const ChunkManager& cm, const std::set<ShardId>& shardsToSkip, const BSONObj& cmdObj, const BSONObj& query, const BSONObj& collation) { - auto opCtx = expCtx->opCtx; auto cmdToSend = cmdObj; @@ -179,6 +170,7 @@ std::vector<AsyncRequestsSender::Request> buildVersionedRequestsForTargetedShard CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collation)); } + auto expCtx = make_intrusive<ExpressionContext>(opCtx, std::move(collator), nss); cm.getShardIdsForQuery(expCtx, query, collation, &shardIds); for (const ShardId& shardId : shardIds) { @@ -400,20 +392,6 @@ std::vector<AsyncRequestsSender::Response> scatterGatherUnversionedTargetAllShar return gatherResponses(opCtx, dbName, readPref, retryPolicy, requests); } -std::vector<AsyncRequestsSender::Response> scatterGatherUnversionedTargetConfigServerAndShards( - OperationContext* opCtx, - StringData dbName, - const BSONObj& cmdObj, - const ReadPreferenceSetting& readPref, - Shard::RetryPolicy retryPolicy) { - std::vector<AsyncRequestsSender::Request> requests; - for (auto shardId : Grid::get(opCtx)->shardRegistry()->getAllShardIds(opCtx)) - requests.emplace_back(std::move(shardId), cmdObj); - auto configShardId = Grid::get(opCtx)->shardRegistry()->getConfigShard()->getId(); - requests.emplace_back(std::move(configShardId), cmdObj); - return gatherResponses(opCtx, dbName, readPref, retryPolicy, requests); -} - std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRoutingTable( OperationContext* opCtx, StringData dbName, @@ -423,28 +401,11 @@ std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRouting const ReadPreferenceSetting& readPref, Shard::RetryPolicy retryPolicy, const BSONObj& query, - const BSONObj& collation, - const boost::optional<BSONObj>& letParameters, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants) { - auto expCtx = makeExpressionContextWithDefaultsForTargeter( - opCtx, nss, collation, boost::none /*explainVerbosity*/, letParameters, runtimeConstants); - return scatterGatherVersionedTargetByRoutingTable( - expCtx, dbName, nss, cm, cmdObj, readPref, retryPolicy, query, collation); -} - -[[nodiscard]] std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRoutingTable( - boost::intrusive_ptr<ExpressionContext> expCtx, - StringData dbName, - const NamespaceString& nss, - const ChunkManager& cm, - const BSONObj& cmdObj, - const ReadPreferenceSetting& readPref, - Shard::RetryPolicy retryPolicy, - const BSONObj& query, const BSONObj& collation) { const auto requests = buildVersionedRequestsForTargetedShards( - expCtx, nss, cm, {} /* shardsToSkip */, cmdObj, query, collation); - return gatherResponses(expCtx->opCtx, dbName, readPref, retryPolicy, requests); + opCtx, nss, cm, {} /* shardsToSkip */, cmdObj, query, collation); + + return gatherResponses(opCtx, dbName, readPref, retryPolicy, requests); } std::vector<AsyncRequestsSender::Response> @@ -458,13 +419,9 @@ scatterGatherVersionedTargetByRoutingTableNoThrowOnStaleShardVersionErrors( const ReadPreferenceSetting& readPref, Shard::RetryPolicy retryPolicy, const BSONObj& query, - const BSONObj& collation, - const boost::optional<BSONObj>& letParameters, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants) { - auto expCtx = makeExpressionContextWithDefaultsForTargeter( - opCtx, nss, collation, boost::none /*explainVerbosity*/, letParameters, runtimeConstants); + const BSONObj& collation) { const auto requests = buildVersionedRequestsForTargetedShards( - expCtx, nss, cm, shardsToSkip, cmdObj, query, collation); + opCtx, nss, cm, shardsToSkip, cmdObj, query, collation); return gatherResponsesNoThrowOnStaleShardVersionErrors( opCtx, dbName, readPref, retryPolicy, requests); @@ -499,12 +456,6 @@ AsyncRequestsSender::Response executeCommandAgainstShardWithMinKeyChunk( const BSONObj& cmdObj, const ReadPreferenceSetting& readPref, Shard::RetryPolicy retryPolicy) { - auto expCtx = makeExpressionContextWithDefaultsForTargeter(opCtx, - nss, - BSONObj() /*collation*/, - boost::none /*explainVerbosity*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); const auto query = cm.isSharded() ? cm.getShardKeyPattern().getKeyPattern().globalMin() : BSONObj(); @@ -515,7 +466,7 @@ AsyncRequestsSender::Response executeCommandAgainstShardWithMinKeyChunk( readPref, retryPolicy, buildVersionedRequestsForTargetedShards( - expCtx, nss, cm, {} /* shardsToSkip */, cmdObj, query, BSONObj() /* collation */)); + opCtx, nss, cm, {} /* shardsToSkip */, cmdObj, query, BSONObj() /* collation */)); return std::move(responses.front()); } @@ -662,7 +613,7 @@ bool appendEmptyResultSet(OperationContext* opCtx, const std::string& ns) { invariant(!status.isOK()); - CurOp::get(opCtx)->debug().additiveMetrics.nreturned = 0; + CurOp::get(opCtx)->debug().nreturned = 0; CurOp::get(opCtx)->debug().nShards = 0; if (status == ErrorCodes::NamespaceNotFound) { @@ -698,14 +649,10 @@ std::vector<std::pair<ShardId, BSONObj>> getVersionedRequestsForTargetedShards( const ChunkManager& cm, const BSONObj& cmdObj, const BSONObj& query, - const BSONObj& collation, - const boost::optional<BSONObj>& letParameters, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants) { - auto expCtx = makeExpressionContextWithDefaultsForTargeter( - opCtx, nss, collation, boost::none /*explainVerbosity*/, letParameters, runtimeConstants); + const BSONObj& collation) { std::vector<std::pair<ShardId, BSONObj>> requests; auto ars_requests = buildVersionedRequestsForTargetedShards( - expCtx, nss, cm, {} /* shardsToSkip */, cmdObj, query, collation); + opCtx, nss, cm, {} /* shardsToSkip */, cmdObj, query, collation); std::transform(std::make_move_iterator(ars_requests.begin()), std::make_move_iterator(ars_requests.end()), std::back_inserter(requests), @@ -746,13 +693,6 @@ StatusWith<Shard::QueryResponse> loadIndexesFromAuthoritativeShard(OperationCont auto [indexShard, listIndexesCmd] = [&]() -> std::pair<std::shared_ptr<Shard>, BSONObj> { auto cmdNoVersion = applyReadWriteConcern( opCtx, true /* appendRC */, false /* appendWC */, BSON("listIndexes" << nss.coll())); - - // force the read concern level to "local" as other values are not supported for listIndexes - BSONObjBuilder bob(cmdNoVersion.removeField(ReadConcernArgs::kReadConcernFieldName)); - bob.append(ReadConcernArgs::kReadConcernFieldName, - BSON(ReadConcernArgs::kLevelFieldName << repl::readConcernLevels::kLocalName)); - cmdNoVersion = bob.obj(); - if (cm.isSharded()) { // For a sharded collection we must load indexes from a shard with chunks. For // consistency with cluster listIndexes, load from the shard that owns the minKey chunk. diff --git a/src/mongo/s/cluster_commands_helpers.h b/src/mongo/s/cluster_commands_helpers.h index 9f359220d61..f342b7f799e 100644 --- a/src/mongo/s/cluster_commands_helpers.h +++ b/src/mongo/s/cluster_commands_helpers.h @@ -86,8 +86,7 @@ boost::intrusive_ptr<ExpressionContext> makeExpressionContextWithDefaultsForTarg * Dispatches all the specified requests in parallel and waits until all complete, returning a * vector of the same size and positions as that of 'requests'. * - * Throws StaleConfig if any of the remotes returns that error, regardless of what the other errors - * are. + * Throws StaleConfigException if any remote returns a stale shardVersion error. */ std::vector<AsyncRequestsSender::Response> gatherResponses( OperationContext* opCtx, @@ -164,31 +163,10 @@ std::vector<AsyncRequestsSender::Response> scatterGatherUnversionedTargetAllShar Shard::RetryPolicy retryPolicy); /** - * Utility for dispatching unversioned commands to a dedicated config server if it exists and all - * shards in a cluster. - * - * Returns a non-OK status if a failure occurs on *this* node during execution. Otherwise, returns - * success and a list of responses from the config server and shards (including errors from the - * config server or shards or errors reaching the config server or shards). - * - * Note, if this mongos has not refreshed its shard list since - * 1) a shard has been *added* through a different mongos, a request will not be sent to the added - * shard - * 2) a shard has been *removed* through a different mongos, this function will return a - * ShardNotFound error status. - */ -std::vector<AsyncRequestsSender::Response> scatterGatherUnversionedTargetConfigServerAndShards( - OperationContext* opCtx, - StringData dbName, - const BSONObj& cmdObj, - const ReadPreferenceSetting& readPref, - Shard::RetryPolicy retryPolicy); - -/** * Utility for dispatching versioned commands on a namespace, deciding which shards to * target by applying the passed-in query and collation to the local routing table cache. * - * Does not retry on StaleConfig errors. + * Does not retry on StaleConfigException. */ std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRoutingTable( OperationContext* opCtx, @@ -199,32 +177,16 @@ std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRouting const ReadPreferenceSetting& readPref, Shard::RetryPolicy retryPolicy, const BSONObj& query, - const BSONObj& collation, - const boost::optional<BSONObj>& letParameters, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants); - -/** - * This overload is for callers which already have a fully initialized 'ExpressionContext' (e.g. - * callers from the aggregation framework). Most callers should prefer the overload above. - */ -[[nodiscard]] std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRoutingTable( - boost::intrusive_ptr<ExpressionContext> expCtx, - StringData dbName, - const NamespaceString& nss, - const ChunkManager& cm, - const BSONObj& cmdObj, - const ReadPreferenceSetting& readPref, - Shard::RetryPolicy retryPolicy, - const BSONObj& query, const BSONObj& collation); + /** * Utility for dispatching versioned commands on a namespace, deciding which shards to * target by applying the passed-in query and collation to the local routing table cache. * * Callers can specify shards to skip, even if these shards would be otherwise targeted. * - * Allows StaleConfig errors to append to the response list. + * Allows StaleConfigException errors to append to the response list. */ std::vector<AsyncRequestsSender::Response> scatterGatherVersionedTargetByRoutingTableNoThrowOnStaleShardVersionErrors( @@ -237,9 +199,7 @@ scatterGatherVersionedTargetByRoutingTableNoThrowOnStaleShardVersionErrors( const ReadPreferenceSetting& readPref, Shard::RetryPolicy retryPolicy, const BSONObj& query, - const BSONObj& collation, - const boost::optional<BSONObj>& letParameters, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants); + const BSONObj& collation); /** * Utility for dispatching commands against the primary of a database and attaching the appropriate @@ -257,7 +217,7 @@ AsyncRequestsSender::Response executeCommandAgainstDatabasePrimary( * Utility for dispatching commands against the shard with the MinKey chunk for the namespace and * attaching the appropriate shard version. * - * Does not retry on StaleConfig errors. + * Does not retry on StaleConfigException. */ AsyncRequestsSender::Response executeCommandAgainstShardWithMinKeyChunk( OperationContext* opCtx, @@ -331,9 +291,7 @@ std::vector<std::pair<ShardId, BSONObj>> getVersionedRequestsForTargetedShards( const ChunkManager& cm, const BSONObj& cmdObj, const BSONObj& query, - const BSONObj& collation, - const boost::optional<BSONObj>& letParameters, - const boost::optional<LegacyRuntimeConstants>& runtimeConstants); + const BSONObj& collation); /** * If the command is running in a transaction, returns the proper routing table to use for targeting diff --git a/src/mongo/s/cluster_ddl.cpp b/src/mongo/s/cluster_ddl.cpp index cae979d0aac..a5a4c03b0e3 100644 --- a/src/mongo/s/cluster_ddl.cpp +++ b/src/mongo/s/cluster_ddl.cpp @@ -35,7 +35,6 @@ #include "mongo/s/cluster_commands_helpers.h" #include "mongo/s/grid.h" -#include "mongo/s/transaction_router.h" namespace mongo { namespace cluster { @@ -95,10 +94,6 @@ CachedDatabaseInfo createDatabase(OperationContext* opCtx, if (suggestedPrimaryId) request.setPrimaryShardId(*suggestedPrimaryId); - if (auto txnRouter = TransactionRouter::get(opCtx)) { - txnRouter.annotateCreatedDatabase(dbName); - } - auto configShard = Grid::get(opCtx)->shardRegistry()->getConfigShard(); auto response = uassertStatusOK(configShard->runCommandWithFixedRetryAttempts( opCtx, diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 237324eb92a..be0eb7d5203 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -53,8 +53,6 @@ env.Library( 'cluster_find_cmd_s.cpp', 'cluster_fle2_compact_cmd.cpp', 'cluster_fsync_cmd.cpp', - 'cluster_fsync_unlock_cmd.cpp', - 'cluster_fsync_unlock_cmd.idl', 'cluster_ftdc_commands.cpp', 'cluster_get_cluster_parameter_cmd.cpp', 'cluster_get_last_error_cmd.cpp', @@ -86,6 +84,7 @@ env.Library( 'cluster_set_allow_migrations_cmd.cpp', 'cluster_set_cluster_parameter_cmd.cpp', 'cluster_set_feature_compatibility_version_cmd.cpp', + 'cluster_set_free_monitoring_cmd.cpp' if get_option("enable-free-mon") == 'on' else [], 'cluster_set_index_commit_quorum_cmd.cpp', 'cluster_set_user_write_block_mode_command.cpp', 'cluster_shard_collection_cmd.cpp', @@ -103,7 +102,6 @@ env.Library( LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/api_parameters', '$BUILD_DIR/mongo/db/auth/auth_checks', - '$BUILD_DIR/mongo/db/catalog/collection_uuid_mismatch_info', '$BUILD_DIR/mongo/db/change_stream_options_manager', '$BUILD_DIR/mongo/db/commands/cluster_server_parameter_cmds_idl', '$BUILD_DIR/mongo/db/commands/core', @@ -132,7 +130,6 @@ env.Library( '$BUILD_DIR/mongo/db/query/command_request_response', '$BUILD_DIR/mongo/db/query/cursor_response_idl', '$BUILD_DIR/mongo/db/query/map_reduce_output_format', - '$BUILD_DIR/mongo/db/query/query_shape/query_shape', '$BUILD_DIR/mongo/db/read_write_concern_defaults', '$BUILD_DIR/mongo/db/repl/hello_auth', '$BUILD_DIR/mongo/db/repl/hello_command', diff --git a/src/mongo/s/commands/cluster_coll_stats_cmd.cpp b/src/mongo/s/commands/cluster_coll_stats_cmd.cpp index d3067b4769e..f85ae09aeb2 100644 --- a/src/mongo/s/commands/cluster_coll_stats_cmd.cpp +++ b/src/mongo/s/commands/cluster_coll_stats_cmd.cpp @@ -232,10 +232,8 @@ public: opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObjToSend)), ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, - {} /*query*/, - {} /*collation*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + {}, + {}); BSONObjBuilder shardStats; std::map<std::string, long long> counts; diff --git a/src/mongo/s/commands/cluster_collection_mod_cmd.cpp b/src/mongo/s/commands/cluster_collection_mod_cmd.cpp index 42e318aaa57..d295ca18c71 100644 --- a/src/mongo/s/commands/cluster_collection_mod_cmd.cpp +++ b/src/mongo/s/commands/cluster_collection_mod_cmd.cpp @@ -33,7 +33,6 @@ #include "mongo/db/auth/authorization_checks.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/collection_uuid_mismatch_info.h" #include "mongo/db/coll_mod_gen.h" #include "mongo/db/coll_mod_reply_validation.h" #include "mongo/db/commands.h" @@ -94,17 +93,8 @@ public: "namespace"_attr = nss, "command"_attr = redact(cmdObj)); - auto swDbInfo = Grid::get(opCtx)->catalogCache()->getDatabase(opCtx, cmd.getDbName()); - if (swDbInfo == ErrorCodes::NamespaceNotFound) { - uassert(CollectionUUIDMismatchInfo(cmd.getDbName().toString(), - *cmd.getCollectionUUID(), - nss.coll().toString(), - boost::none), - "Database does not exist", - !cmd.getCollectionUUID()); - } - const auto dbInfo = uassertStatusOK(swDbInfo); - + const auto dbInfo = + uassertStatusOK(Grid::get(opCtx)->catalogCache()->getDatabase(opCtx, cmd.getDbName())); ShardsvrCollMod collModCommand(nss); collModCommand.setCollModRequest(cmd.getCollModRequest()); auto cmdResponse = diff --git a/src/mongo/s/commands/cluster_convert_to_capped_cmd.cpp b/src/mongo/s/commands/cluster_convert_to_capped_cmd.cpp index c6206bce5be..8c657bc453f 100644 --- a/src/mongo/s/commands/cluster_convert_to_capped_cmd.cpp +++ b/src/mongo/s/commands/cluster_convert_to_capped_cmd.cpp @@ -51,17 +51,8 @@ bool nonShardedCollectionCommandPassthrough(OperationContext* opCtx, str::stream() << "Can't do command: " << cmdName << " on a sharded collection", !cm.isSharded()); - auto responses = scatterGatherVersionedTargetByRoutingTable(opCtx, - dbName, - nss, - cm, - cmdObj, - ReadPreferenceSetting::get(opCtx), - retryPolicy, - {} /*query*/, - {} /*collation*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + auto responses = scatterGatherVersionedTargetByRoutingTable( + opCtx, dbName, nss, cm, cmdObj, ReadPreferenceSetting::get(opCtx), retryPolicy, {}, {}); invariant(responses.size() == 1); const auto cmdResponse = uassertStatusOK(std::move(responses.front().swResponse)); diff --git a/src/mongo/s/commands/cluster_count_cmd.cpp b/src/mongo/s/commands/cluster_count_cmd.cpp index 94e6c7c0eee..48545c7c0d7 100644 --- a/src/mongo/s/commands/cluster_count_cmd.cpp +++ b/src/mongo/s/commands/cluster_count_cmd.cpp @@ -131,9 +131,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, countRequest.getQuery(), - collation, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + collation); } catch (const ExceptionFor<ErrorCodes::CommandOnShardedViewNotSupportedOnMongod>& ex) { // Rewrite the count command as an aggregation. auto countRequest = CountCommandRequest::parse(IDLParserErrorContext("count"), cmdObj); @@ -237,9 +235,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, targetingQuery, - targetingCollation, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + targetingCollation); } catch (const ExceptionFor<ErrorCodes::CommandOnShardedViewNotSupportedOnMongod>& ex) { CountCommandRequest countRequest(NamespaceStringOrUUID(NamespaceString{})); try { @@ -288,14 +284,14 @@ private: BSONElement l = cmd["limit"]; if (s.isNumber()) { - num = num - s.safeNumberLong(); + num = num - s.numberLong(); if (num < 0) { num = 0; } } if (l.isNumber()) { - auto limit = l.safeNumberLong(); + long long limit = l.numberLong(); if (limit < 0) { limit = -limit; } diff --git a/src/mongo/s/commands/cluster_create_indexes_cmd.cpp b/src/mongo/s/commands/cluster_create_indexes_cmd.cpp index 8f4928444c4..8c4c8af8784 100644 --- a/src/mongo/s/commands/cluster_create_indexes_cmd.cpp +++ b/src/mongo/s/commands/cluster_create_indexes_cmd.cpp @@ -89,6 +89,8 @@ public: "namespace"_attr = nss, "command"_attr = redact(cmdObj)); + cluster::createDatabase(opCtx, dbName); + auto targeter = ChunkManagerTargeter(opCtx, nss); auto routingInfo = targeter.getRoutingInfo(); auto cmdToBeSent = cmdObj; @@ -106,10 +108,8 @@ public: applyReadWriteConcern(opCtx, this, cmdToBeSent)), ReadPreferenceSetting(ReadPreference::PrimaryOnly), Shard::RetryPolicy::kNoRetry, - BSONObj() /*query*/, - BSONObj() /*collation*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + BSONObj() /* query */, + BSONObj() /* collation */); std::string errmsg; const bool ok = diff --git a/src/mongo/s/commands/cluster_data_size_cmd.cpp b/src/mongo/s/commands/cluster_data_size_cmd.cpp index 1c87c6a98f0..7d645d1a51c 100644 --- a/src/mongo/s/commands/cluster_data_size_cmd.cpp +++ b/src/mongo/s/commands/cluster_data_size_cmd.cpp @@ -85,10 +85,8 @@ public: opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)), ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, - {} /*query*/, - {} /*collation*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + {}, + {}); // yes these are doubles... double size = 0; diff --git a/src/mongo/s/commands/cluster_distinct_cmd.cpp b/src/mongo/s/commands/cluster_distinct_cmd.cpp index f34377258e4..70ae5f4b671 100644 --- a/src/mongo/s/commands/cluster_distinct_cmd.cpp +++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp @@ -132,9 +132,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, targetingQuery, - targetingCollation, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + targetingCollation); } catch (const ExceptionFor<ErrorCodes::CommandOnShardedViewNotSupportedOnMongod>& ex) { auto parsedDistinct = ParsedDistinct::parse( opCtx, ex->getNamespace(), cmdObj, ExtensionsCallbackNoop(), true); @@ -219,9 +217,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, query, - collation, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + collation); } catch (const ExceptionFor<ErrorCodes::CommandOnShardedViewNotSupportedOnMongod>& ex) { auto parsedDistinct = ParsedDistinct::parse( opCtx, ex->getNamespace(), cmdObj, ExtensionsCallbackNoop(), true); diff --git a/src/mongo/s/commands/cluster_drop_collection_cmd.cpp b/src/mongo/s/commands/cluster_drop_collection_cmd.cpp index 40e9196371f..e16f473ab3c 100644 --- a/src/mongo/s/commands/cluster_drop_collection_cmd.cpp +++ b/src/mongo/s/commands/cluster_drop_collection_cmd.cpp @@ -33,7 +33,6 @@ #include "mongo/base/status.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/collection_uuid_mismatch_info.h" #include "mongo/db/commands.h" #include "mongo/db/drop_gen.h" #include "mongo/db/operation_context.h" @@ -117,13 +116,6 @@ public: // Ensure our reply conforms to the IDL-defined reply structure. return DropReply::parse({"drop"}, resultObj); } catch (const ExceptionFor<ErrorCodes::NamespaceNotFound>&) { - uassert(CollectionUUIDMismatchInfo(request().getDbName().toString(), - *request().getCollectionUUID(), - request().getNamespace().coll().toString(), - boost::none), - "Database does not exist", - !request().getCollectionUUID()); - // If the namespace isn't found, treat the drop as a success but inform about the // failure. DropReply reply; diff --git a/src/mongo/s/commands/cluster_filemd5_cmd.cpp b/src/mongo/s/commands/cluster_filemd5_cmd.cpp index 3248d9ce0ba..d351a7be7a3 100644 --- a/src/mongo/s/commands/cluster_filemd5_cmd.cpp +++ b/src/mongo/s/commands/cluster_filemd5_cmd.cpp @@ -98,9 +98,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, routingQuery, - CollationSpec::kSimpleSpec, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + CollationSpec::kSimpleSpec); invariant(shardResults.size() == 1); const auto shardResponse = uassertStatusOK(std::move(shardResults[0].swResponse)); uassertStatusOK(shardResponse.status); diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp index 195adef15e2..740d1561487 100644 --- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp @@ -69,8 +69,6 @@ namespace mongo { namespace { -constexpr size_t kMaxDatabaseCreationAttempts = 3u; - const ReadPreferenceSetting kPrimaryOnlyReadPreference(ReadPreference::PrimaryOnly); const char kLegacyRuntimeConstantsField[] = "runtimeConstants"; @@ -307,27 +305,6 @@ void handleWouldChangeOwningShardErrorTransactionLegacy(OperationContext* opCtx, } } -ShardId targetSingleShard(boost::intrusive_ptr<ExpressionContext> expCtx, - const ChunkManager& cm, - const BSONObj& query, - const BSONObj& collation) { - std::set<ShardId> shardIds; - - // For now, set bypassIsFieldHashedCheck to be true in order to skip the - // isFieldHashedCheck in the special case where _id is hashed and used as the shard - // key. This means that we always assume that a findAndModify request using _id is - // targetable to a single shard. - cm.getShardIdsForQuery(expCtx, query, collation, &shardIds, true); - - uassert(ErrorCodes::ShardKeyNotFound, - str::stream() << "Query with sharded findAndModify expected to only target one " - "shard, but the query targeted " - << shardIds.size() << " shard(s)", - shardIds.size() == 1); - - return *shardIds.begin(); -} - class FindAndModifyCmd : public BasicCommand { public: FindAndModifyCmd() @@ -415,14 +392,12 @@ public: const BSONObj collation = getCollation(cmdObj); const auto let = getLet(cmdObj); const auto rc = getLegacyRuntimeConstants(cmdObj); - ShardId shardId; - { - auto expCtx = makeExpressionContextWithDefaultsForTargeter( - opCtx, nss, collation, verbosity, let, rc); - shardId = targetSingleShard(expCtx, cm, query, collation); - } + const BSONObj shardKey = + getShardKey(opCtx, cm, nss, query, collation, verbosity, let, rc); + const auto chunk = cm.findIntersectingChunk(shardKey, collation); - shard = uassertStatusOK(Grid::get(opCtx)->shardRegistry()->getShard(opCtx, shardId)); + shard = uassertStatusOK( + Grid::get(opCtx)->shardRegistry()->getShard(opCtx, chunk.getShardId())); } else { shard = uassertStatusOK(Grid::get(opCtx)->shardRegistry()->getShard(opCtx, cm.dbPrimary())); @@ -484,50 +459,31 @@ public: // Collect metrics. _updateMetrics.collectMetrics(cmdObj); - const auto cm = [&]() { - size_t attempts = 1u; - while (true) { - try { - // Technically, findAndModify should only be creating database if upsert is - // true, but this would require that the parsing be pulled into this function. - cluster::createDatabase(opCtx, nss.db()); - return uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, nss)); - } catch (const ExceptionFor<ErrorCodes::NamespaceNotFound>&) { - LOGV2_INFO( - 8584300, - "Failed initialization of routing info because the database has been " - "concurrently dropped", - logAttrs(nss), - "attemptNumber"_attr = attempts, - "maxAttempts"_attr = kMaxDatabaseCreationAttempts); - - if (attempts++ >= kMaxDatabaseCreationAttempts) { - // The maximum number of attempts has been reached, so the procedure fails - // as it could be a logical error. At this point, it is unlikely that the - // error is caused by concurrent drop database operations. - throw; - } - } - } - }(); + // Technically, findAndModify should only be creating database if upsert is true, but this + // would require that the parsing be pulled into this function. + cluster::createDatabase(opCtx, nss.db()); // Append mongoS' runtime constants to the command object before forwarding it to the shard. auto cmdObjForShard = appendLegacyRuntimeConstantsToCommandObject(opCtx, cmdObj); + const auto cm = uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, nss)); if (cm.isSharded()) { const BSONObj query = cmdObjForShard.getObjectField("query"); const BSONObj collation = getCollation(cmdObjForShard); const auto let = getLet(cmdObjForShard); const auto rc = getLegacyRuntimeConstants(cmdObjForShard); - ShardId shardId; - { - auto expCtx = makeExpressionContextWithDefaultsForTargeter( - opCtx, nss, collation, boost::none, let, rc); - shardId = targetSingleShard(expCtx, cm, query, collation); - } + const BSONObj shardKey = + getShardKey(opCtx, cm, nss, query, collation, boost::none, let, rc); + + // For now, set bypassIsFieldHashedCheck to be true in order to skip the + // isFieldHashedCheck in the special case where _id is hashed and used as the shard key. + // This means that we always assume that a findAndModify request using _id is targetable + // to a single shard. + auto chunk = cm.findIntersectingChunk(shardKey, collation, true); + _runCommand(opCtx, - shardId, - cm.getVersion(shardId), + chunk.getShardId(), + cm.getVersion(chunk.getShardId()), boost::none, nss, applyReadWriteConcern(opCtx, this, cmdObjForShard), diff --git a/src/mongo/s/commands/cluster_find_cmd.h b/src/mongo/s/commands/cluster_find_cmd.h index 6476d883200..87d25934d0c 100644 --- a/src/mongo/s/commands/cluster_find_cmd.h +++ b/src/mongo/s/commands/cluster_find_cmd.h @@ -38,9 +38,6 @@ #include "mongo/db/fle_crud.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/query/cursor_response.h" -#include "mongo/db/query/query_shape/query_shape.h" -#include "mongo/db/query/query_stats/find_key.h" -#include "mongo/db/query/query_stats/query_stats.h" #include "mongo/db/stats/counters.h" #include "mongo/db/views/resolved_view.h" #include "mongo/rpc/get_status_from_command_result.h" @@ -155,9 +152,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, findCommand->getFilter(), - findCommand->getCollation(), - findCommand->getLet(), - findCommand->getLegacyRuntimeConstants()); + findCommand->getCollation()); millisElapsed = timer.millis(); const char* mongosStageName = @@ -204,23 +199,21 @@ public: Impl::checkCanRunHere(opCtx); - auto&& parsedFindResult = uassertStatusOK(parsed_find_command::parse( - opCtx, - _parseCmdObjectToFindCommandRequest(opCtx, ns(), _request.body), - ExtensionsCallbackNoop(), - MatchExpressionParser::kAllowAllSpecialFeatures)); - auto& expCtx = parsedFindResult.first; - auto& parsedFind = parsedFindResult.second; - - if (!_didDoFLERewrite) { - query_stats::registerRequest(opCtx, expCtx->ns, [&]() { - // This callback is either never invoked or invoked - // immediately within registerRequest, so - // use-after-move of parsedFind isn't an issue. - return std::make_unique<query_stats::FindKey>(expCtx, *parsedFind); - }); - } - auto cq = uassertStatusOK(CanonicalQuery::canonicalize(expCtx, std::move(parsedFind))); + ON_BLOCK_EXIT([opCtx] { + Grid::get(opCtx)->catalogCache()->checkAndRecordOperationBlockedByRefresh( + opCtx, mongo::LogicalOp::opQuery); + }); + + auto findCommand = _parseCmdObjectToFindCommandRequest(opCtx, ns(), _request.body); + + const boost::intrusive_ptr<ExpressionContext> expCtx; + auto cq = uassertStatusOK( + CanonicalQuery::canonicalize(opCtx, + std::move(findCommand), + false, /* isExplain */ + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures)); try { // Do the work to generate the first batch of results. This blocks waiting to get @@ -274,7 +267,7 @@ public: * were supplied with the command, and sets the constant runtime values that will be * forwarded to each shard. */ - std::unique_ptr<FindCommandRequest> _parseCmdObjectToFindCommandRequest( + static std::unique_ptr<FindCommandRequest> _parseCmdObjectToFindCommandRequest( OperationContext* opCtx, NamespaceString nss, BSONObj cmdObj) { auto findCommand = query_request_helper::makeFromFindCommand( std::move(cmdObj), @@ -301,7 +294,6 @@ public: invariant(findCommand->getNamespaceOrUUID().nss()); processFLEFindS( opCtx, findCommand->getNamespaceOrUUID().nss().get(), findCommand.get()); - _didDoFLERewrite = true; } return findCommand; @@ -309,7 +301,6 @@ public: const OpMsgRequest& _request; const StringData _dbName; - bool _didDoFLERewrite{false}; }; }; diff --git a/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp b/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp index 7abae202f17..b1cecbf555e 100644 --- a/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp +++ b/src/mongo/s/commands/cluster_fle2_compact_cmd.cpp @@ -32,7 +32,6 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" #include "mongo/db/commands/fle2_compact_gen.h" -#include "mongo/db/curop.h" #include "mongo/s/cluster_commands_helpers.h" #include "mongo/s/grid.h" diff --git a/src/mongo/s/commands/cluster_fsync_cmd.cpp b/src/mongo/s/commands/cluster_fsync_cmd.cpp index 55316c964ac..e82469acee3 100644 --- a/src/mongo/s/commands/cluster_fsync_cmd.cpp +++ b/src/mongo/s/commands/cluster_fsync_cmd.cpp @@ -31,18 +31,10 @@ #include "mongo/client/read_preference.h" #include "mongo/client/remote_command_targeter.h" -#include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/service_context.h" #include "mongo/s/client/shard.h" #include "mongo/s/client/shard_registry.h" -#include "mongo/s/cluster_commands_helpers.h" #include "mongo/s/grid.h" -#include "mongo/s/sharding_feature_flags_gen.h" -#include "mongo/util/assert_util.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand namespace mongo { namespace { @@ -75,47 +67,53 @@ public: out->push_back(Privilege(ResourcePattern::forClusterResource(), actions)); } - void unlockLockedShards(OperationContext* opCtx, const std::string& dbname) { - - auto request = OpMsgRequest::fromDBAndBody(dbname, BSON("fsyncUnlock" << 1)); - auto response = CommandHelpers::runCommandDirectly(opCtx, request); - } - bool errmsgRun(OperationContext* opCtx, const std::string& dbname, const BSONObj& cmdObj, std::string& errmsg, BSONObjBuilder& result) override { - - BSONObj fsyncCmdObj = cmdObj; if (cmdObj["lock"].trueValue()) { - auto forBackupField = BSON("forBackup" << true); - fsyncCmdObj = fsyncCmdObj.addFields(forBackupField); + errmsg = "can't do lock through mongos"; + return false; } - auto shardResults = scatterGatherUnversionedTargetConfigServerAndShards( - opCtx, - dbname, - applyReadWriteConcern( - opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(fsyncCmdObj)), - ReadPreferenceSetting(ReadPreference::PrimaryOnly), - Shard::RetryPolicy::kIdempotent); + BSONObjBuilder sub; - BSONObjBuilder rawResult; - const auto response = appendRawResponses(opCtx, &errmsg, &rawResult, shardResults); + bool ok = true; + + auto const shardRegistry = Grid::get(opCtx)->shardRegistry(); + const auto shardIds = shardRegistry->getAllShardIdsNoReload(); + + for (const ShardId& shardId : shardIds) { + auto shardStatus = shardRegistry->getShard(opCtx, shardId); + if (!shardStatus.isOK()) { + continue; + } + const auto s = shardStatus.getValue(); + + auto response = uassertStatusOK(s->runCommandWithFixedRetryAttempts( + opCtx, + ReadPreferenceSetting{ReadPreference::PrimaryOnly}, + "admin", + BSON("fsync" << 1), + Shard::RetryPolicy::kIdempotent)); + uassertStatusOK(response.commandStatus); + BSONObj x = std::move(response.response); + + sub.append(s->getId().toString(), x); + + if (!x["ok"].trueValue()) { + ok = false; + errmsg = x["errmsg"].String(); + } + } // This field has had dummy value since MMAP went away. It is undocumented. // Maintaining it so as not to cause unnecessary user pain across upgrades. result.append("numFiles", 1); - result.append("all", rawResult.obj()); - if (!response.responseOK) { - if (cmdObj["lock"].trueValue()) { - unlockLockedShards(opCtx, dbname); - } - return false; - } + result.append("all", sub.obj()); - return true; + return ok; } } clusterFsyncCmd; diff --git a/src/mongo/s/commands/cluster_fsync_unlock_cmd.cpp b/src/mongo/s/commands/cluster_fsync_unlock_cmd.cpp deleted file mode 100644 index 5275298a7bb..00000000000 --- a/src/mongo/s/commands/cluster_fsync_unlock_cmd.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (C) 2023-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/client/read_preference.h" -#include "mongo/client/remote_command_targeter.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/commands.h" -#include "mongo/s/async_requests_sender.h" -#include "mongo/s/client/shard.h" -#include "mongo/s/client/shard_registry.h" -#include "mongo/s/cluster_commands_helpers.h" -#include "mongo/s/commands/cluster_fsync_unlock_cmd_gen.h" -#include "mongo/s/grid.h" -#include "mongo/s/sharding_feature_flags_gen.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand - -namespace mongo { - -namespace { -class FsyncUnlockCommand : public TypedCommand<FsyncUnlockCommand> { -public: - using Request = ClusterFsyncUnlock; - - - class Invocation final : public InvocationBase { - public: - using InvocationBase::InvocationBase; - - /** - * Intermediate wrapper to interface with ReplyBuilderInterface. - */ - class Response { - public: - Response(BSONObj obj) : _obj(std::move(obj)) {} - - void serialize(BSONObjBuilder* builder) const { - builder->appendElements(_obj); - } - - private: - const BSONObj _obj; - }; - - Response typedRun(OperationContext* opCtx) { - BSONObj fsyncUnlockCmdObj = BSON("fsyncUnlock" << 1); - - auto responses = scatterGatherUnversionedTargetConfigServerAndShards( - opCtx, - NamespaceString::kAdminDb, - applyReadWriteConcern( - opCtx, - this, - CommandHelpers::filterCommandRequestForPassthrough(fsyncUnlockCmdObj)), - ReadPreferenceSetting::get(opCtx), - Shard::RetryPolicy::kIdempotent); - - BSONObjBuilder result; - std::string errmsg; - const auto rawResponsesResult = appendRawResponses(opCtx, &errmsg, &result, responses); - - if (!errmsg.empty()) { - CommandHelpers::appendSimpleCommandStatus( - result, rawResponsesResult.responseOK, errmsg); - } - - return Response(result.obj()); - } - - private: - NamespaceString ns() const override { - return {}; - } - - bool supportsWriteConcern() const override { - return false; - } - - void doCheckAuthorization(OperationContext* opCtx) const override { - uassert(ErrorCodes::Unauthorized, - "Unauthorized", - AuthorizationSession::get(opCtx->getClient()) - ->isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns()), - ActionType::fsyncUnlock)); - } - }; - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kAlways; - } - - bool maintenanceOk() const override { - return false; - } - - bool adminOnly() const override { - return true; - } - - std::string help() const override { - return "invoke fsync unlock on all shards belonging to the cluster"; - } -} fsyncUnlockCmd; - -} // namespace -} // namespace mongo diff --git a/src/mongo/s/commands/cluster_fsync_unlock_cmd.idl b/src/mongo/s/commands/cluster_fsync_unlock_cmd.idl deleted file mode 100644 index c0d1be0aa0c..00000000000 --- a/src/mongo/s/commands/cluster_fsync_unlock_cmd.idl +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2023-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# As a special exception, the copyright holders give permission to link the -# code of portions of this program with the OpenSSL library under certain -# conditions as described in each individual source file and distribute -# linked combinations including the program with the OpenSSL library. You -# must comply with the Server Side Public License in all respects for -# all of the code used other than as permitted herein. If you modify file(s) -# with this exception, you may extend this exception to your version of the -# file(s), but you are not obligated to do so. If you do not wish to do so, -# delete this exception statement from your version. If you delete this -# exception statement from all source files in the program, then also delete -# it in the license file. -# - -global: - cpp_namespace: "mongo" - -imports: - - "mongo/idl/basic_types.idl" - -commands: - clusterFsyncUnlock: - description: "The command for calling fsync unlock on all shards of a cluster." - command_name: fsyncUnlock - cpp_name: clusterFsyncUnlock - strict: false - namespace: ignored - api_version: "" diff --git a/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp b/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp index 4b6d49ebae1..1058e6ebafd 100644 --- a/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp +++ b/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp @@ -66,11 +66,6 @@ public: using InvocationBase::InvocationBase; Reply typedRun(OperationContext* opCtx) { - uassert(ErrorCodes::UnknownFeatureCompatibilityVersion, - "FCV is not yet initialized, retry the command after FCV initialization has " - "completed", - serverGlobalParams.featureCompatibility.isVersionInitialized()); - uassert( ErrorCodes::IllegalOperation, "featureFlagClusterWideConfig not enabled", diff --git a/src/mongo/s/commands/cluster_hello_cmd.cpp b/src/mongo/s/commands/cluster_hello_cmd.cpp index 13b199f8ad3..5600a29661d 100644 --- a/src/mongo/s/commands/cluster_hello_cmd.cpp +++ b/src/mongo/s/commands/cluster_hello_cmd.cpp @@ -133,12 +133,6 @@ public: // "hello" is exempt from error code rewrites. rpc::RewriteStateChangeErrors::setEnabled(opCtx, false); - // Negotiate compressors before logging metadata so we can include the result in the log - // line. - auto result = replyBuilder->getBodyBuilder(); - MessageCompressorManager::forSession(opCtx->getClient()->session()) - .serverNegotiate(cmd.getCompression(), &result); - auto client = opCtx->getClient(); if (ClientMetadata::tryFinalize(client)) { audit::logClientMetadata(client); @@ -172,6 +166,7 @@ public: !clientTopologyVersion && !maxAwaitTimeMS); } + auto result = replyBuilder->getBodyBuilder(); const auto* mongosTopCoord = MongosTopologyCoordinator::get(opCtx); auto mongosHelloResponse = @@ -224,6 +219,9 @@ public: sp->append(opCtx, result, kAutomationServiceDescriptorFieldName); } + MessageCompressorManager::forSession(opCtx->getClient()->session()) + .serverNegotiate(cmd.getCompression(), &result); + if (opCtx->isExhaust()) { LOGV2_DEBUG(23872, 3, "Using exhaust for hello protocol"); diff --git a/src/mongo/s/commands/cluster_index_filter_cmd.cpp b/src/mongo/s/commands/cluster_index_filter_cmd.cpp index 48aec39e5cb..f6c5bd37777 100644 --- a/src/mongo/s/commands/cluster_index_filter_cmd.cpp +++ b/src/mongo/s/commands/cluster_index_filter_cmd.cpp @@ -104,9 +104,7 @@ public: ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, query, - CollationSpec::kSimpleSpec, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + CollationSpec::kSimpleSpec); // Sort shard responses by shard id. std::sort(shardResponses.begin(), diff --git a/src/mongo/s/commands/cluster_map_reduce_agg.cpp b/src/mongo/s/commands/cluster_map_reduce_agg.cpp index 746ba39c7bc..52a61ab9325 100644 --- a/src/mongo/s/commands/cluster_map_reduce_agg.cpp +++ b/src/mongo/s/commands/cluster_map_reduce_agg.cpp @@ -187,7 +187,6 @@ bool runAggregationMapReduce(OperationContext* opCtx, cm, involvedNamespaces, false, // hasChangeStream - false, // startsWithDocuments true, // allowedToPassthrough false); // perShardCursor try { @@ -231,15 +230,14 @@ bool runAggregationMapReduce(OperationContext* opCtx, namespaces, privileges, &tempResults, - false, // hasChangeStream - false)); // startsWithDocuments + false)); // hasChangeStream break; } case cluster_aggregation_planner::AggregationTargeter::TargetingPolicy:: kSpecificShardOnly: { // It should not be possible to pass $_passthroughToShard to a map reduce command. - MONGO_UNREACHABLE_TASSERT(6273805); + MONGO_UNREACHABLE_TASSERT(6273803); } } } catch (DBException& e) { diff --git a/src/mongo/s/commands/cluster_move_range_cmd.cpp b/src/mongo/s/commands/cluster_move_range_cmd.cpp index fd2f77ea9f5..c06ca4a5c47 100644 --- a/src/mongo/s/commands/cluster_move_range_cmd.cpp +++ b/src/mongo/s/commands/cluster_move_range_cmd.cpp @@ -100,7 +100,7 @@ public: uassert(ErrorCodes::Unauthorized, "Unauthorized", AuthorizationSession::get(opCtx->getClient()) - ->isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns()), + ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::moveChunk)); } }; diff --git a/src/mongo/s/commands/cluster_plan_cache_clear_cmd.cpp b/src/mongo/s/commands/cluster_plan_cache_clear_cmd.cpp index e3411fbda2c..96499720ea6 100644 --- a/src/mongo/s/commands/cluster_plan_cache_clear_cmd.cpp +++ b/src/mongo/s/commands/cluster_plan_cache_clear_cmd.cpp @@ -110,9 +110,7 @@ bool ClusterPlanCacheClearCmd::run(OperationContext* opCtx, ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, query, - CollationSpec::kSimpleSpec, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + CollationSpec::kSimpleSpec); // Sort shard responses by shard id. std::sort(shardResponses.begin(), diff --git a/src/mongo/s/commands/cluster_profile_cmd.cpp b/src/mongo/s/commands/cluster_profile_cmd.cpp index 022b44011c0..c2564c19b1d 100644 --- a/src/mongo/s/commands/cluster_profile_cmd.cpp +++ b/src/mongo/s/commands/cluster_profile_cmd.cpp @@ -33,7 +33,6 @@ #include "mongo/db/commands.h" #include "mongo/db/commands/profile_common.h" #include "mongo/db/commands/profile_gen.h" -#include "mongo/db/commands/set_profiling_filter_globally_cmd.h" #include "mongo/db/profile_filter_impl.h" namespace mongo { @@ -54,6 +53,7 @@ protected: OperationContext* opCtx, const std::string& dbName, const ProfileCmdRequest& request) const final { + invariant(!opCtx->lockState()->isW()); const auto profilingLevel = request.getCommandParameter(); @@ -85,7 +85,5 @@ protected: } profileCmd; -SetProfilingFilterGloballyCmd setProfilingFilterGloballyCmd; - } // namespace } // namespace mongo diff --git a/src/mongo/s/commands/cluster_rename_collection_cmd.cpp b/src/mongo/s/commands/cluster_rename_collection_cmd.cpp index a5c3d9d385c..4d4d88907a6 100644 --- a/src/mongo/s/commands/cluster_rename_collection_cmd.cpp +++ b/src/mongo/s/commands/cluster_rename_collection_cmd.cpp @@ -32,7 +32,6 @@ #include "mongo/platform/basic.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/collection_uuid_mismatch_info.h" #include "mongo/db/commands.h" #include "mongo/db/commands/rename_collection_common.h" #include "mongo/db/commands/rename_collection_gen.h" @@ -75,21 +74,6 @@ public: "Can't rename a collection to itself", fromNss != toNss); - if (fromNss.isTimeseriesBucketsCollection()) { - uassert( - ErrorCodes::IllegalOperation, - "Renaming system.buckets collections is not allowed", - AuthorizationSession::get(opCtx->getClient()) - ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), - ActionType::setUserWriteBlockMode)); - - uassert(ErrorCodes::IllegalOperation, - str::stream() - << "Cannot rename time-series buckets collection {" << fromNss.ns() - << "} to a non-time-series buckets namespace {" << toNss.ns() << "}", - toNss.isTimeseriesBucketsCollection()); - } - RenameCollectionRequest renameCollReq(request().getTo()); renameCollReq.setStayTemp(request().getStayTemp()); renameCollReq.setExpectedSourceUUID(request().getCollectionUUID()); @@ -106,22 +90,9 @@ public: ShardsvrRenameCollection renameCollRequest(fromNss); renameCollRequest.setDbName(fromNss.db()); renameCollRequest.setRenameCollectionRequest(renameCollReq); - renameCollRequest.setAllowEncryptedCollectionRename( - AuthorizationSession::get(opCtx->getClient()) - ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), - ActionType::setUserWriteBlockMode)); auto catalogCache = Grid::get(opCtx)->catalogCache(); - auto swDbInfo = Grid::get(opCtx)->catalogCache()->getDatabase(opCtx, fromNss.db()); - if (swDbInfo == ErrorCodes::NamespaceNotFound) { - uassert(CollectionUUIDMismatchInfo(fromNss.db().toString(), - *request().getCollectionUUID(), - fromNss.coll().toString(), - boost::none), - "Database does not exist", - !request().getCollectionUUID()); - } - const auto dbInfo = uassertStatusOK(swDbInfo); + const auto dbInfo = uassertStatusOK(catalogCache->getDatabase(opCtx, fromNss.db())); auto cri = uassertStatusOK(catalogCache->getCollectionRoutingInfo(opCtx, fromNss)); auto shard = uassertStatusOK( diff --git a/src/mongo/s/commands/cluster_set_free_monitoring_cmd.cpp b/src/mongo/s/commands/cluster_set_free_monitoring_cmd.cpp new file mode 100644 index 00000000000..d54cd80b219 --- /dev/null +++ b/src/mongo/s/commands/cluster_set_free_monitoring_cmd.cpp @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" + +namespace mongo { +namespace { + +class ClusterSetFreeMonitoring : public BasicCommand { +public: + ClusterSetFreeMonitoring() : BasicCommand("setFreeMonitoring") {} + + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return AllowedOnSecondary::kNever; + } + + bool supportsWriteConcern(const BSONObj& cmd) const final { + return false; + } + + std::string help() const final { + return "setFreeMonitoring command must be run against mongod instances"; + } + + Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj) const final { + if (!AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::setFreeMonitoring)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + bool run(OperationContext* opCtx, + const std::string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder& result) final { + uasserted(ErrorCodes::CommandFailed, help()); + return true; + } + +} clusterSetFreeMonitoring; + +} // namespace +} // namespace mongo diff --git a/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp b/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp index f494e41a6a6..8727a9da078 100644 --- a/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp +++ b/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp @@ -117,10 +117,8 @@ public: opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)), ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kNotIdempotent, - BSONObj() /*query*/, - BSONObj() /*collation*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + BSONObj() /* query */, + BSONObj() /* collation */); std::string errmsg; const bool ok = diff --git a/src/mongo/s/commands/cluster_validate_cmd.cpp b/src/mongo/s/commands/cluster_validate_cmd.cpp index 285d23fc29d..52285704516 100644 --- a/src/mongo/s/commands/cluster_validate_cmd.cpp +++ b/src/mongo/s/commands/cluster_validate_cmd.cpp @@ -84,10 +84,8 @@ public: opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)), ReadPreferenceSetting::get(opCtx), Shard::RetryPolicy::kIdempotent, - {} /*query*/, - {} /*collation*/, - boost::none /*letParameters*/, - boost::none /*runtimeConstants*/); + {}, + {}); Status firstFailedShardStatus = Status::OK(); bool isValid = true; diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp index 8590832acc6..10d04ba0b82 100644 --- a/src/mongo/s/commands/cluster_write_cmd.cpp +++ b/src/mongo/s/commands/cluster_write_cmd.cpp @@ -145,6 +145,11 @@ boost::optional<WouldChangeOwningShardInfo> getWouldChangeOwningShardErrorInfo( void handleWouldChangeOwningShardErrorRetryableWrite(OperationContext* opCtx, BatchedCommandRequest* request, BatchedCommandResponse* response) { + // Strip write concern because this command will be sent as part of a + // transaction and the write concern has already been loaded onto the opCtx and + // will be picked up by the transaction API. + request->unsetWriteConcern(); + // Strip runtime constants because they will be added again when the API sends this command // through the service entry point. request->unsetLegacyRuntimeConstants(); @@ -313,6 +318,11 @@ bool handleWouldChangeOwningShardError(OperationContext* opCtx, auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); readConcernArgs = repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern); + // Ensure the retried operation does not include WC inside the transaction. The + // transaction commit will still use the WC, because it uses the WC from the opCtx + // (which has been set previously in Strategy). + request->unsetWriteConcern(); + documentShardKeyUpdateUtil::startTransactionForShardKeyUpdate(opCtx); // Clear the error details from the response object before sending the write again response->unsetErrDetails(); @@ -497,6 +507,29 @@ bool ClusterWriteCmd::InvocationBase::runImpl(OperationContext* opCtx, BatchWriteExecStats stats; BatchedCommandResponse response; + // The batched request will only have WC if it was supplied by the client. Otherwise, the + // batched request should use the WC from the opCtx. + if (!batchedRequest.hasWriteConcern()) { + if (opCtx->getWriteConcern().usedDefaultConstructedWC) { + // Pass writeConcern: {}, rather than {w: 1, wtimeout: 0}, so as to not override the + // configsvr w:majority upconvert. + batchedRequest.setWriteConcern(BSONObj()); + } else { + batchedRequest.setWriteConcern(opCtx->getWriteConcern().toBSON()); + } + } + + // Write ops are never allowed to have writeConcern inside transactions. Normally + // disallowing WC on non-terminal commands in a transaction is handled earlier, during + // command dispatch. However, if this is a regular write operation being automatically + // retried inside a transaction (such as changing a document's shard key across shards), + // then batchedRequest will have a writeConcern (added by the if() above) from when it was + // initially run outside a transaction. Thus it's necessary to unconditionally clear the + // writeConcern when in a transaction. + if (TransactionRouter::get(opCtx)) { + batchedRequest.unsetWriteConcern(); + } + cluster::write(opCtx, batchedRequest, &stats, &response); bool updatedShardKey = false; @@ -522,17 +555,22 @@ bool ClusterWriteCmd::InvocationBase::runImpl(OperationContext* opCtx, // TODO: increase opcounters by more than one auto& debug = CurOp::get(opCtx)->debug(); + auto catalogCache = Grid::get(opCtx)->catalogCache(); switch (_batchedRequest.getBatchType()) { case BatchedCommandRequest::BatchType_Insert: for (size_t i = 0; i < numAttempts; ++i) { globalOpCounters.gotInsert(); } + catalogCache->checkAndRecordOperationBlockedByRefresh(opCtx, + mongo::LogicalOp::opInsert); debug.additiveMetrics.ninserted = response.getN(); break; case BatchedCommandRequest::BatchType_Update: for (size_t i = 0; i < numAttempts; ++i) { globalOpCounters.gotUpdate(); } + catalogCache->checkAndRecordOperationBlockedByRefresh(opCtx, + mongo::LogicalOp::opUpdate); // The response.getN() count is the sum of documents matched and upserted. if (response.isUpsertDetailsSet()) { @@ -565,6 +603,8 @@ bool ClusterWriteCmd::InvocationBase::runImpl(OperationContext* opCtx, for (size_t i = 0; i < numAttempts; ++i) { globalOpCounters.gotDelete(); } + catalogCache->checkAndRecordOperationBlockedByRefresh(opCtx, + mongo::LogicalOp::opDelete); debug.additiveMetrics.ndeleted = response.getN(); break; } diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index e12b3385a13..13d061e9e6d 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -426,6 +426,14 @@ public: explicit RunInvocation(ParseAndRunCommand* parc) : _parc(parc) {} + ~RunInvocation() { + if (!_shouldAffectCommandCounter) + return; + auto opCtx = _parc->_rec->getOpCtx(); + Grid::get(opCtx)->catalogCache()->checkAndRecordOperationBlockedByRefresh( + opCtx, mongo::LogicalOp::opCommand); + } + Future<void> run(); private: @@ -434,6 +442,7 @@ private: ParseAndRunCommand* const _parc; boost::optional<RouterOperationContextSession> _routerSession; + bool _shouldAffectCommandCounter = false; }; /* @@ -698,53 +707,31 @@ Status ParseAndRunCommand::RunInvocation::_setup() { (opCtx->getClient()->session() && (opCtx->getClient()->session()->getTags() & transport::Session::kInternalClient)); - bool canApplyDefaultWC = supportsWriteConcern && + if (supportsWriteConcern && !clientSuppliedWriteConcern && (!TransactionRouter::get(opCtx) || isTransactionCommand(_parc->_commandName)) && - !opCtx->getClient()->isInDirectClient(); - - if (canApplyDefaultWC) { - auto getDefaultWC = ([&]() { - auto rwcDefaults = + !opCtx->getClient()->isInDirectClient()) { + if (isInternalClient) { + uassert( + 5569900, + "received command without explicit writeConcern on an internalClient connection {}"_format( + redact(request.body.toString())), + request.body.hasField(WriteConcernOptions::kWriteConcernField)); + } else { + // This command is not from a DBDirectClient or internal client, and supports WC, but + // wasn't given one - so apply the default, if there is one. + const auto rwcDefaults = ReadWriteConcernDefaults::get(opCtx->getServiceContext()).getDefault(opCtx); - auto wcDefault = rwcDefaults.getDefaultWriteConcern(); - const auto defaultWriteConcernSource = rwcDefaults.getDefaultWriteConcernSource(); - customDefaultWriteConcernWasApplied = defaultWriteConcernSource && - defaultWriteConcernSource == DefaultWriteConcernSourceEnum::kGlobal; - return wcDefault; - }); - - if (!clientSuppliedWriteConcern) { - if (isInternalClient) { - uassert( - 5569900, - "received command without explicit writeConcern on an internalClient connection {}"_format( - redact(request.body.toString())), - request.body.hasField(WriteConcernOptions::kWriteConcernField)); - } else { - // This command is not from a DBDirectClient or internal client, and supports WC, - // but wasn't given one - so apply the default, if there is one. - const auto wcDefault = getDefaultWC(); - // Default WC can be 'boost::none' if the implicit default is used and set to 'w:1'. - if (wcDefault) { - _parc->_wc = *wcDefault; - LOGV2_DEBUG(22766, - 2, - "Applying default writeConcern on command", - "command"_attr = request.getCommandName(), - "writeConcern"_attr = *wcDefault); - } - } - } - // Client supplied a write concern object without 'w' field. - else if (_parc->_wc->isExplicitWithoutWField()) { - const auto wcDefault = getDefaultWC(); - // Default WC can be 'boost::none' if the implicit default is used and set to 'w:1'. - if (wcDefault) { - clientSuppliedWriteConcern = false; - _parc->_wc->w = wcDefault->w; - if (_parc->_wc->syncMode == WriteConcernOptions::SyncMode::UNSET) { - _parc->_wc->syncMode = wcDefault->syncMode; - } + if (const auto wcDefault = rwcDefaults.getDefaultWriteConcern()) { + _parc->_wc = *wcDefault; + const auto defaultWriteConcernSource = rwcDefaults.getDefaultWriteConcernSource(); + customDefaultWriteConcernWasApplied = defaultWriteConcernSource && + defaultWriteConcernSource == DefaultWriteConcernSourceEnum::kGlobal; + LOGV2_DEBUG(22766, + 2, + "Applying default writeConcern on {command} of {writeConcern}", + "Applying default writeConcern on command", + "command"_attr = request.getCommandName(), + "writeConcern"_attr = *wcDefault); } } } @@ -911,6 +898,7 @@ Status ParseAndRunCommand::RunInvocation::_setup() { if (command->shouldAffectCommandCounter()) { globalOpCounters.gotCommand(); + _shouldAffectCommandCounter = true; } return Status::OK(); @@ -1048,21 +1036,10 @@ void ParseAndRunCommand::RunAndRetry::_onNeedRetargetting(Status& status) { auto opCtx = _parc->_rec->getOpCtx(); const auto staleNs = staleInfo->getNss(); - const auto& originalNs = _parc->_invocation->ns(); auto catalogCache = Grid::get(opCtx)->catalogCache(); catalogCache->invalidateShardOrEntireCollectionEntryForShardedCollection( staleNs, staleInfo->getVersionWanted(), staleInfo->getShardId()); - if ((staleNs.isTimeseriesBucketsCollection() || originalNs.isTimeseriesBucketsCollection()) && - staleNs != originalNs) { - // A timeseries might've been created, so we need to invalidate the original namespace - // version. - Grid::get(opCtx) - ->catalogCache() - ->invalidateShardOrEntireCollectionEntryForShardedCollection( - originalNs, boost::none, staleInfo->getShardId()); - } - catalogCache->setOperationShouldBlockBehindCatalogCacheRefresh(opCtx, true); _checkRetryForTransaction(status); @@ -1193,13 +1170,6 @@ public: Future<DbResponse> run(); private: - std::string _getDatabaseStringForLogging() const try { - // `getDatabase` throws if the request doesn't have a '$db' field. - return _rec->getRequest().getDatabase().toString(); - } catch (const DBException& ex) { - return ex.toString(); - } - void _parseMessage(); Future<void> _execute(); @@ -1242,7 +1212,7 @@ Future<void> ClientCommand::_execute() { 3, "Command begin db: {db} msg id: {headerId}", "Command begin", - "db"_attr = _getDatabaseStringForLogging(), + "db"_attr = _rec->getRequest().getDatabase().toString(), "headerId"_attr = _rec->getMessage().header().getId()); return future_util::makeState<ParseAndRunCommand>(_rec, _errorBuilder) @@ -1252,7 +1222,7 @@ Future<void> ClientCommand::_execute() { 3, "Command end db: {db} msg id: {headerId}", "Command end", - "db"_attr = _getDatabaseStringForLogging(), + "db"_attr = _rec->getRequest().getDatabase().toString(), "headerId"_attr = _rec->getMessage().header().getId()); }) .tapError([this](Status status) { @@ -1261,7 +1231,7 @@ Future<void> ClientCommand::_execute() { 1, "Exception thrown while processing command on {db} msg id: {headerId} {error}", "Exception thrown while processing command", - "db"_attr = _getDatabaseStringForLogging(), + "db"_attr = _rec->getRequest().getDatabase().toString(), "headerId"_attr = _rec->getMessage().header().getId(), "error"_attr = redact(status)); diff --git a/src/mongo/s/commands/strategy.h b/src/mongo/s/commands/strategy.h index 73916229f7f..1e04130f657 100644 --- a/src/mongo/s/commands/strategy.h +++ b/src/mongo/s/commands/strategy.h @@ -41,8 +41,8 @@ public: /** * Executes a command from either OP_QUERY or OP_MSG wire protocols. * - * Catches StaleConfig errors and retries the command automatically after refreshing the - * metadata for the failing namespace. + * Catches StaleConfigException errors and retries the command automatically after refreshing + * the metadata for the failing namespace. */ static Future<DbResponse> clientCommand(std::shared_ptr<RequestExecutionContext> rec); }; diff --git a/src/mongo/s/concurrency/locker_mongos.h b/src/mongo/s/concurrency/locker_mongos.h index 61055d26595..f4c3d72894e 100644 --- a/src/mongo/s/concurrency/locker_mongos.h +++ b/src/mongo/s/concurrency/locker_mongos.h @@ -115,10 +115,7 @@ public: MONGO_UNREACHABLE; } - void lockRSTLComplete(OperationContext* opCtx, - LockMode mode, - Date_t deadline, - const LockTimeoutCallback& onTimeout) override { + void lockRSTLComplete(OperationContext* opCtx, LockMode mode, Date_t deadline) override { MONGO_UNREACHABLE; } @@ -168,7 +165,7 @@ public: return boost::none; } - void saveLockStateAndUnlock(LockSnapshot* stateOut) override { + bool saveLockStateAndUnlock(LockSnapshot* stateOut) override { MONGO_UNREACHABLE; } @@ -180,7 +177,7 @@ public: MONGO_UNREACHABLE; } - void releaseWriteUnitOfWorkAndUnlock(LockSnapshot* stateOut) override { + bool releaseWriteUnitOfWorkAndUnlock(LockSnapshot* stateOut) override { MONGO_UNREACHABLE; } @@ -254,10 +251,6 @@ public: bool isGlobalLockedRecursively() override { return false; } - - bool canSaveLockState() override { - return false; - } }; } // namespace mongo diff --git a/src/mongo/s/config_server_catalog_cache_loader.cpp b/src/mongo/s/config_server_catalog_cache_loader.cpp index 84cc73b987a..3910e1e4c88 100644 --- a/src/mongo/s/config_server_catalog_cache_loader.cpp +++ b/src/mongo/s/config_server_catalog_cache_loader.cpp @@ -114,10 +114,6 @@ void ConfigServerCatalogCacheLoader::onStepUp() { MONGO_UNREACHABLE; } -void ConfigServerCatalogCacheLoader::onReplicationRollback() { - MONGO_UNREACHABLE; -} - void ConfigServerCatalogCacheLoader::shutDown() { _executor->shutdown(); _executor->join(); diff --git a/src/mongo/s/config_server_catalog_cache_loader.h b/src/mongo/s/config_server_catalog_cache_loader.h index 86af7a6b14b..8c1384946f8 100644 --- a/src/mongo/s/config_server_catalog_cache_loader.h +++ b/src/mongo/s/config_server_catalog_cache_loader.h @@ -46,7 +46,6 @@ public: void initializeReplicaSetRole(bool isPrimary) override; void onStepDown() override; void onStepUp() override; - void onReplicationRollback() override; void shutDown() override; void notifyOfCollectionVersionUpdate(const NamespaceString& nss) override; void waitForCollectionFlush(OperationContext* opCtx, const NamespaceString& nss) override; diff --git a/src/mongo/s/database_version.idl b/src/mongo/s/database_version.idl index 68134191b6a..9b073f5a34e 100644 --- a/src/mongo/s/database_version.idl +++ b/src/mongo/s/database_version.idl @@ -48,8 +48,7 @@ structs: optional: true timestamp: type: timestamp - description: "Unique identifier to distinguish major events in the lifetime of a - database, such as create/drop and change of primary." + description: "Uniquely identifies to distinguish different incarnations of this database." lastMod: type: int description: "an integer which is bumped whenever the database's primary shard changes" diff --git a/src/mongo/s/mongod_and_mongos_server_parameters.idl b/src/mongo/s/mongod_and_mongos_server_parameters.idl index 0a760d967d8..9d9929174e0 100644 --- a/src/mongo/s/mongod_and_mongos_server_parameters.idl +++ b/src/mongo/s/mongod_and_mongos_server_parameters.idl @@ -69,13 +69,3 @@ server_parameters: cpp_vartype: bool cpp_varname: "gEnableFinerGrainedCatalogCacheRefresh" default: false - - routingTableCacheChunkBucketSize: - description: >- - Size of the routing table cache buckets used to implement chunk grouping optimization. - set_at: [ startup ] - cpp_vartype: long long - cpp_varname: "gRoutingTableCacheChunkBucketSize" - default: 500 - validator: - gte: 0 diff --git a/src/mongo/s/mongos_main.cpp b/src/mongo/s/mongos_main.cpp index 665f3fe08d4..0504b714aec 100644 --- a/src/mongo/s/mongos_main.cpp +++ b/src/mongo/s/mongos_main.cpp @@ -127,10 +127,6 @@ #include "mongo/util/text.h" #include "mongo/util/version.h" -#ifdef __linux__ -#include <sys/prctl.h> -#endif - namespace mongo { using logv2::LogComponent; @@ -316,10 +312,7 @@ void cleanupTask(const ShutdownTaskArgs& shutdownArgs) { ReplicaSetMonitor::shutdown(); - { - stdx::lock_guard lg(client); - opCtx->setIsExecutingShutdown(); - } + opCtx->setIsExecutingShutdown(); if (serviceContext) { serviceContext->setKillAllOperations(); @@ -348,7 +341,6 @@ void cleanupTask(const ShutdownTaskArgs& shutdownArgs) { } if (auto pool = Grid::get(opCtx)->getExecutorPool()) { - LOGV2_OPTIONS(7698300, {LogComponent::kSharding}, "Shutting down the ExecutorPool"); pool->shutdownAndJoin(); } @@ -357,13 +349,6 @@ void cleanupTask(const ShutdownTaskArgs& shutdownArgs) { } if (Grid::get(serviceContext)->isShardingInitialized()) { - // The CatalogCache must be shuted down before shutting down the CatalogCacheLoader as - // the CatalogCache may try to schedule work on CatalogCacheLoader and fail. - LOGV2_OPTIONS(7698301, {LogComponent::kSharding}, "Shutting down the CatalogCache"); - Grid::get(serviceContext)->catalogCache()->shutDownAndJoin(); - - LOGV2_OPTIONS( - 7698302, {LogComponent::kSharding}, "Shutting down the CatalogCacheLoader"); CatalogCacheLoader::get(serviceContext).shutDown(); } @@ -860,18 +845,6 @@ std::unique_ptr<AuthzManagerExternalState> createAuthzManagerExternalStateMongos return std::make_unique<AuthzManagerExternalStateMongos>(); } -void disableMongosTHPUnderTestingEnvironment() { -#ifdef __linux__ - if (TestingProctor::instance().isEnabled()) { - if (prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0) == -1) { - LOGV2_WARNING(8751802, "Could not disable THP on mongos"); - } else { - LOGV2_INFO(8751803, "Successfully disabled THP on mongos"); - } - } -#endif -} - ExitCode main(ServiceContext* serviceContext) { serviceContext->setFastClockSource(FastClockSourceFactory::create(Milliseconds{10})); @@ -946,8 +919,6 @@ ExitCode mongos_main(int argc, char* argv[]) { return EXIT_ABRUPT; } - disableMongosTHPUnderTestingEnvironment(); - try { auto serviceContextHolder = ServiceContext::make(); serviceContextHolder->registerClientObserver( diff --git a/src/mongo/s/mongos_options.cpp b/src/mongo/s/mongos_options.cpp index 8b995415368..d333d1f7fc2 100644 --- a/src/mongo/s/mongos_options.cpp +++ b/src/mongo/s/mongos_options.cpp @@ -115,6 +115,13 @@ Status storeMongosOptions(const moe::Environment& params) { return ret; } + if (params.count("net.port")) { + int port = params["net.port"].as<int>(); + if (port <= 0 || port > 65535) { + return Status(ErrorCodes::BadValue, "error: port number must be between 1 and 65535"); + } + } + if (params.count("security.javascriptEnabled")) { mongosGlobalParams.scriptingEnabled = params["security.javascriptEnabled"].as<bool>(); } diff --git a/src/mongo/s/multi_statement_transaction_requests_sender.cpp b/src/mongo/s/multi_statement_transaction_requests_sender.cpp index d63b830d9a4..c62b4e97da4 100644 --- a/src/mongo/s/multi_statement_transaction_requests_sender.cpp +++ b/src/mongo/s/multi_statement_transaction_requests_sender.cpp @@ -40,9 +40,7 @@ namespace mongo { namespace { std::vector<AsyncRequestsSender::Request> attachTxnDetails( - OperationContext* opCtx, - const std::vector<AsyncRequestsSender::Request>& requests, - const StringData& dbName) { + OperationContext* opCtx, const std::vector<AsyncRequestsSender::Request>& requests) { auto txnRouter = TransactionRouter::get(opCtx); if (!txnRouter) { return requests; @@ -54,7 +52,7 @@ std::vector<AsyncRequestsSender::Request> attachTxnDetails( for (auto request : requests) { newRequests.emplace_back( request.shardId, - txnRouter.attachTxnFieldsIfNeeded(opCtx, request.shardId, request.cmdObj, dbName)); + txnRouter.attachTxnFieldsIfNeeded(opCtx, request.shardId, request.cmdObj)); } return newRequests; @@ -88,7 +86,7 @@ MultiStatementTransactionRequestsSender::MultiStatementTransactionRequestsSender opCtx, std::move(executor), dbName, - attachTxnDetails(opCtx, requests, dbName), + attachTxnDetails(opCtx, requests), readPreference, retryPolicy, TransactionRouterResourceYielder::makeForRemoteCommand())) {} diff --git a/src/mongo/s/query/SConscript b/src/mongo/s/query/SConscript index f8179e37eeb..f6f1ac53b05 100644 --- a/src/mongo/s/query/SConscript +++ b/src/mongo/s/query/SConscript @@ -8,17 +8,17 @@ env.Library( target="cluster_query", source=[ "cluster_find.cpp", - "cluster_query_knobs.idl", - "store_possible_cursor.cpp", + 'cluster_query_knobs.idl', ], LIBDEPS=[ '$BUILD_DIR/mongo/db/commands', + '$BUILD_DIR/mongo/db/curop', '$BUILD_DIR/mongo/db/curop_failpoint_helpers', '$BUILD_DIR/mongo/db/query/query_common', - '$BUILD_DIR/mongo/db/query/query_stats/query_stats', '$BUILD_DIR/mongo/s/sharding_router_api', "cluster_client_cursor", "cluster_cursor_cleanup_job", + "store_possible_cursor", ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/catalog/collection_uuid_mismatch_info', @@ -37,14 +37,12 @@ env.Library( '$BUILD_DIR/mongo/db/pipeline/pipeline', '$BUILD_DIR/mongo/db/pipeline/process_interface/mongos_process_interface', '$BUILD_DIR/mongo/db/pipeline/sharded_agg_helpers', - '$BUILD_DIR/mongo/db/query/query_shape/query_shape', '$BUILD_DIR/mongo/db/views/view_catalog_helpers', '$BUILD_DIR/mongo/db/views/views', '$BUILD_DIR/mongo/s/query/cluster_client_cursor', 'cluster_query', ], LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/db/catalog/collection_uuid_mismatch_info', '$BUILD_DIR/mongo/db/timeseries/timeseries_options', ] ) @@ -94,11 +92,24 @@ env.Library( ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/catalog/collection_uuid_mismatch_info', - '$BUILD_DIR/mongo/executor/async_multicaster', ] ) env.Library( + target="store_possible_cursor", + source=[ + "store_possible_cursor.cpp" + ], + LIBDEPS=[ + "$BUILD_DIR/mongo/base", + "$BUILD_DIR/mongo/db/curop", + "$BUILD_DIR/mongo/db/query/command_request_response", + "cluster_client_cursor", + "cluster_cursor_manager", + ], +) + +env.Library( target="cluster_cursor_manager", source=[ "cluster_cursor_manager.cpp", @@ -107,12 +118,12 @@ env.Library( '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/authprivilege', + '$BUILD_DIR/mongo/db/curop', '$BUILD_DIR/mongo/db/generic_cursor', '$BUILD_DIR/mongo/db/kill_sessions', '$BUILD_DIR/mongo/db/logical_session_cache', '$BUILD_DIR/mongo/db/logical_session_id', '$BUILD_DIR/mongo/db/query/query_knobs', - '$BUILD_DIR/mongo/db/query/query_stats/query_stats', ], ) @@ -159,7 +170,7 @@ env.CppUnitTest( "cluster_aggregate", "cluster_client_cursor", "cluster_cursor_manager", - "cluster_query", "router_exec_stage", + "store_possible_cursor", ], ) diff --git a/src/mongo/s/query/async_results_merger.cpp b/src/mongo/s/query/async_results_merger.cpp index 50fb6310888..363e151fbfd 100644 --- a/src/mongo/s/query/async_results_merger.cpp +++ b/src/mongo/s/query/async_results_merger.cpp @@ -846,8 +846,7 @@ void AsyncResultsMerger::_scheduleKillCursors(WithLock, OperationContext* opCtx) invariant(_killCompleteInfo); for (const auto& remote : _remotes) { - if ((remote.status.isOK() || remote.status == ErrorCodes::MaxTimeMSExpired) && - remote.cursorId && !remote.exhausted()) { + if (remote.status.isOK() && remote.cursorId && !remote.exhausted()) { BSONObj cmdObj = KillCursorsCommandRequest(_params.getNss(), {remote.cursorId}).toBSON(BSONObj{}); diff --git a/src/mongo/s/query/async_results_merger_params.idl b/src/mongo/s/query/async_results_merger_params.idl index 5382c9cc718..2b5857e6c9b 100644 --- a/src/mongo/s/query/async_results_merger_params.idl +++ b/src/mongo/s/query/async_results_merger_params.idl @@ -50,19 +50,15 @@ types: structs: RemoteCursor: description: A description of a cursor opened on a remote server. - query_shape_component: true fields: shardId: type: string description: The shardId of the shard on which the cursor resides. - query_shape: anonymize hostAndPort: type: HostAndPort description: The exact host (within the shard) on which the cursor resides. - query_shape: anonymize cursorResponse: type: CursorResponse - query_shape: literal description: The response after establishing a cursor on the remote shard, including the first batch. @@ -70,47 +66,33 @@ structs: description: The parameters needed to establish an AsyncResultsMerger. chained_structs: OperationSessionInfoFromClient : OperationSessionInfo - query_shape_component: true fields: sort: type: object description: The sort requested on the merging operation. Empty if there is no sort. optional: true - query_shape: literal compareWholeSortKey: type: bool default: false - query_shape: literal description: >- When 'compareWholeSortKey' is true, $sortKey is a scalar value, rather than an object. We extract the sort key {$sortKey: <value>}. The sort key pattern is verified to be {$sortKey: 1}. - remotes: - type: array<RemoteCursor> - query_shape: literal + remotes: array<RemoteCursor> tailableMode: type: TailableMode optional: true description: If set, the tailability mode of this cursor. - query_shape: parameter batchSize: type: safeInt64 optional: true description: The batch size for this cursor. - query_shape: literal - nss: - type: namespacestring - query_shape: custom + nss: namespacestring allowPartialResults: type: bool default: false description: If set, error responses are ignored. - query_shape: parameter recordRemoteOpWaitTime: type: bool default: false - query_shape: parameter - description: >- - This parameter is not used anymore but should stay for a while for backward - compatibility. - TODO SERVER-73120 Remove this parameter when releasing 8.0. + description: If set, records the total time spent waiting for remote operations to complete. diff --git a/src/mongo/s/query/blocking_results_merger.cpp b/src/mongo/s/query/blocking_results_merger.cpp index fc56a0d9e3b..c1a957a311f 100644 --- a/src/mongo/s/query/blocking_results_merger.cpp +++ b/src/mongo/s/query/blocking_results_merger.cpp @@ -42,6 +42,7 @@ BlockingResultsMerger::BlockingResultsMerger(OperationContext* opCtx, std::shared_ptr<executor::TaskExecutor> executor, std::unique_ptr<ResourceYielder> resourceYielder) : _tailableMode(armParams.getTailableMode().value_or(TailableModeEnum::kNormal)), + _recordRemoteOpWaitTime(armParams.getRecordRemoteOpWaitTime()), _executor(executor), _arm(opCtx, std::move(executor), std::move(armParams)), _resourceYielder(std::move(resourceYielder)) {} @@ -145,7 +146,9 @@ StatusWith<ClusterQueryResult> BlockingResultsMerger::blockUntilNext(OperationCo return _arm.nextReady(); } StatusWith<ClusterQueryResult> BlockingResultsMerger::next(OperationContext* opCtx) { - CurOp::get(opCtx)->ensureRecordRemoteOpWait(); + if (_recordRemoteOpWaitTime) { + CurOp::get(opCtx)->enableRecordRemoteOpWait(); + } // Non-tailable and tailable non-awaitData cursors always block until ready(). AwaitData // cursors wait for ready() only until a specified time limit is exceeded. diff --git a/src/mongo/s/query/blocking_results_merger.h b/src/mongo/s/query/blocking_results_merger.h index c05cecc5da8..4a368d9471a 100644 --- a/src/mongo/s/query/blocking_results_merger.h +++ b/src/mongo/s/query/blocking_results_merger.h @@ -118,6 +118,7 @@ private: const std::function<StatusWith<stdx::cv_status>()>& waitFn) noexcept; TailableModeEnum _tailableMode; + bool _recordRemoteOpWaitTime; std::shared_ptr<executor::TaskExecutor> _executor; // In a case where we have a tailable, awaitData cursor, a call to 'next()' will block waiting diff --git a/src/mongo/s/query/cluster_aggregate.cpp b/src/mongo/s/query/cluster_aggregate.cpp index 6374bcfd494..b3df71582da 100644 --- a/src/mongo/s/query/cluster_aggregate.cpp +++ b/src/mongo/s/query/cluster_aggregate.cpp @@ -27,7 +27,6 @@ * it in the license file. */ -#include "mongo/s/chunk_manager.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand #include "mongo/platform/basic.h" @@ -38,7 +37,6 @@ #include "mongo/db/api_parameters.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/catalog/collection_uuid_mismatch_info.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/curop.h" @@ -57,9 +55,6 @@ #include "mongo/db/query/explain_common.h" #include "mongo/db/query/find_common.h" #include "mongo/db/query/fle/server_rewrite.h" -#include "mongo/db/query/query_stats/agg_key.h" -#include "mongo/db/query/query_stats/key.h" -#include "mongo/db/query/query_stats/query_stats.h" #include "mongo/db/timeseries/timeseries_options.h" #include "mongo/db/views/resolved_view.h" #include "mongo/db/views/view.h" @@ -99,7 +94,7 @@ namespace { // definition. It's okay that this is incorrect, we will repopulate the real namespace map on the // mongod. Note that this function must be called before forwarding an aggregation command on an // unsharded collection, in order to verify that the involved namespaces are allowed to be sharded. -auto resolveInvolvedNamespaces(const stdx::unordered_set<NamespaceString>& involvedNamespaces) { +auto resolveInvolvedNamespaces(stdx::unordered_set<NamespaceString> involvedNamespaces) { StringMap<ExpressionContext::ResolvedNamespace> resolvedNamespaces; for (auto&& nss : involvedNamespaces) { resolvedNamespaces.try_emplace(nss.coll(), nss, std::vector<BSONObj>{}); @@ -262,68 +257,6 @@ std::vector<BSONObj> rebuildPipelineWithTimeSeriesGranularity(const std::vector< return newPipeline; } -/** - * Builds an expCtx with which to parse the request's pipeline, then parses the pipeline and - * registers the pre-optimized pipeline with query stats collection. - */ -std::unique_ptr<Pipeline, PipelineDeleter> parsePipelineAndRegisterQueryStats( - OperationContext* opCtx, - const stdx::unordered_set<NamespaceString>& involvedNamespaces, - const NamespaceString& executionNss, - AggregateCommandRequest& request, - const boost::optional<ChunkManager>& cm, - const LiteParsedPipeline& liteParsedPipeline, - bool hasChangeStream, - bool shouldDoFLERewrite) { - // Populate the collection UUID and the appropriate collation to use. - auto [collationObj, uuid] = [&]() -> std::pair<BSONObj, boost::optional<UUID>> { - // If this is a change stream, take the user-defined collation if one exists, or an - // empty BSONObj otherwise. Change streams never inherit the collection's default - // collation, and since collectionless aggregations generally run on the 'admin' - // database, the standard logic would attempt to resolve its non-existent UUID and - // collation by sending a specious 'listCollections' command to the config servers. - if (hasChangeStream) { - return {request.getCollation().value_or(BSONObj()), boost::none}; - } - - return cluster_aggregation_planner::getCollationAndUUID( - opCtx, cm, executionNss, request.getCollation().value_or(BSONObj())); - }(); - - // Build an ExpressionContext for the pipeline. This instantiates an appropriate collator, - // resolves all involved namespaces, and creates a shared MongoProcessInterface for use by the - // pipeline's stages. - boost::intrusive_ptr<ExpressionContext> expCtx = - makeExpressionContext(opCtx, - request, - collationObj, - uuid, - resolveInvolvedNamespaces(involvedNamespaces), - hasChangeStream); - - // A pipeline with $changeStreamSplitLargeEvent requires the use of resume token format v2, - // since the 'fragmentNum' field only exists in this version and later. - if (hasChangeStream && liteParsedPipeline.endsWithChangeStreamSplitLargeEvent()) { - expCtx->changeStreamTokenVersion = 2; - } - - // Parse and optimize the full pipeline. - auto pipeline = Pipeline::parse(request.getPipeline(), expCtx); - - // Skip query stats recording for queryable encryption queries. - if (!shouldDoFLERewrite) { - query_stats::registerRequest( - opCtx, - executionNss, - [&]() { - return std::make_unique<query_stats::AggKey>( - request, *pipeline, expCtx, involvedNamespaces, executionNss); - }, - hasChangeStream); - } - return pipeline; -} - } // namespace Status ClusterAggregate::runAggregate(OperationContext* opCtx, @@ -376,7 +309,6 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, auto hasChangeStream = liteParsedPipeline.hasChangeStream(); auto involvedNamespaces = liteParsedPipeline.getInvolvedNamespaces(); auto shouldDoFLERewrite = ::mongo::shouldDoFLERewrite(request); - auto startsWithDocuments = liteParsedPipeline.startsWithDocuments(); // If the routing table is not already taken by the higher level, fill it now. if (!cm) { @@ -391,14 +323,6 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, sharded_agg_helpers::getExecutionNsRoutingInfo(opCtx, namespaces.executionNss); if (!executionNsRoutingInfoStatus.isOK()) { - uassert(CollectionUUIDMismatchInfo(request.getDbName().toString(), - *request.getCollectionUUID(), - request.getNamespace().coll().toString(), - boost::none), - "Database does not exist", - executionNsRoutingInfoStatus != ErrorCodes::NamespaceNotFound || - !request.getCollectionUUID()); - if (liteParsedPipeline.startsWithCollStats()) { uassertStatusOKWithContext(executionNsRoutingInfoStatus, "Unable to retrieve information for $collStats stage"); @@ -407,7 +331,7 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, if (executionNsRoutingInfoStatus.isOK()) { cm = std::move(executionNsRoutingInfoStatus.getValue()); - } else if (!((hasChangeStream || startsWithDocuments) && + } else if (!(hasChangeStream && executionNsRoutingInfoStatus == ErrorCodes::NamespaceNotFound)) { appendEmptyResultSetWithStatus( opCtx, namespaces.requestedNss, executionNsRoutingInfoStatus.getStatus(), result); @@ -417,15 +341,33 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, boost::intrusive_ptr<ExpressionContext> expCtx; const auto pipelineBuilder = [&]() { - auto pipeline = parsePipelineAndRegisterQueryStats(opCtx, - involvedNamespaces, - namespaces.executionNss, - request, - cm, - liteParsedPipeline, - hasChangeStream, - shouldDoFLERewrite); - expCtx = pipeline->getContext(); + // Populate the collection UUID and the appropriate collation to use. + auto [collationObj, uuid] = [&]() -> std::pair<BSONObj, boost::optional<UUID>> { + // If this is a change stream, take the user-defined collation if one exists, or an + // empty BSONObj otherwise. Change streams never inherit the collection's default + // collation, and since collectionless aggregations generally run on the 'admin' + // database, the standard logic would attempt to resolve its non-existent UUID and + // collation by sending a specious 'listCollections' command to the config servers. + if (hasChangeStream) { + return {request.getCollation().value_or(BSONObj()), boost::none}; + } + + return cluster_aggregation_planner::getCollationAndUUID( + opCtx, cm, namespaces.executionNss, request.getCollation().value_or(BSONObj())); + }(); + + // Build an ExpressionContext for the pipeline. This instantiates an appropriate collator, + // resolves all involved namespaces, and creates a shared MongoProcessInterface for use by + // the pipeline's stages. + expCtx = makeExpressionContext(opCtx, + request, + collationObj, + uuid, + resolveInvolvedNamespaces(involvedNamespaces), + hasChangeStream); + + // Parse and optimize the full pipeline. + auto pipeline = Pipeline::parse(request.getPipeline(), expCtx); // If the aggregate command supports encrypted collections, do rewrites of the pipeline to // support querying against encrypted fields. @@ -439,11 +381,6 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, } pipeline->optimizePipeline(); - - // Validate the pipeline post-optimization. - const bool alreadyOptimized = true; - pipeline->validateCommon(alreadyOptimized); - return pipeline; }; @@ -458,7 +395,6 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, cm, involvedNamespaces, hasChangeStream, - startsWithDocuments, allowedToPassthrough, request.getPassthroughToShard().has_value()); @@ -471,48 +407,15 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, cluster_aggregation_planner::AggregationTargeter::TargetingPolicy::kMongosRequired); if (!expCtx) { - // When the AggregationTargeter chooses a "passthrough" or "specific shard only" policy, it - // does not call the 'pipelineBuilder' function, so we've yet to construct an expression - // context or register query stats. Because this is a passthrough, we only need a bare - // minimum expression context on mongos. + // When the AggregationTargeter chooses a "passthrough" policy, it does not call the + // 'pipelineBuilder' function, so we never get an expression context. Because this is a + // passthrough, we only need a bare minimum expression context anyway. invariant(targeter.policy == cluster_aggregation_planner::AggregationTargeter::kPassthrough || targeter.policy == cluster_aggregation_planner::AggregationTargeter::kSpecificShardOnly); - expCtx = make_intrusive<ExpressionContext>( opCtx, nullptr, namespaces.executionNss, boost::none, request.getLet()); - expCtx->addResolvedNamespaces(involvedNamespaces); - - - // We might need 'inMongos' temporarily set to true for query stats parsing, but we don't - // want to modify the value of 'expCtx' for future code execution so we will set it back to - // its original value. - ON_BLOCK_EXIT([&expCtx, originalInMongosVal = expCtx->inMongos]() { - expCtx->inMongos = originalInMongosVal; - }); - - // In order to parse a change stream request for query stats, 'inMongos' needs - // to be set to true. - if (hasChangeStream) { - expCtx->inMongos = true; - } - - // Skip query stats recording for queryable encryption queries. - if (!shouldDoFLERewrite) { - // We want to hold off parsing the pipeline until it's clear we must. Because of that, - // we wait to parse the pipeline until this callback is invoked within - // query_stats::registerRequest. - query_stats::registerRequest( - opCtx, - namespaces.executionNss, - [&]() { - auto pipeline = Pipeline::parse(request.getPipeline(), expCtx); - return std::make_unique<query_stats::AggKey>( - request, *pipeline, expCtx, involvedNamespaces, namespaces.executionNss); - }, - hasChangeStream); - } } if (request.getExplain()) { @@ -540,11 +443,10 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, // If this is an explain write the explain output and return. auto expCtx = targeter.pipeline->getContext(); if (expCtx->explain) { - auto opts = SerializationOptions{}; - opts.verbosity = boost::make_optional(ExplainOptions::Verbosity::kQueryPlanner); *result << "splitPipeline" << BSONNULL << "mongos" << Document{{"host", getHostNameCachedAndPort()}, - {"stages", targeter.pipeline->writeExplainOps(opts)}}; + {"stages", + targeter.pipeline->writeExplainOps(*expCtx->explain)}}; return Status::OK(); } @@ -567,8 +469,7 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, namespaces, privileges, result, - hasChangeStream, - startsWithDocuments); + hasChangeStream); } case cluster_aggregation_planner::AggregationTargeter::TargetingPolicy:: kSpecificShardOnly: { @@ -613,12 +514,11 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx, updateHostsTargetedMetrics(opCtx, namespaces.executionNss, cm, involvedNamespaces); // Report usage statistics for each stage in the pipeline. liteParsedPipeline.tickGlobalStageCounters(); + // Add 'command' object to explain output. if (expCtx->explain) { explain_common::appendIfRoom( aggregation_request_helper::serializeToCommandObj(request), "command", result); - collectQueryStatsMongos(opCtx, - std::move(CurOp::get(opCtx)->debug().queryStatsInfo.key)); } } return status; diff --git a/src/mongo/s/query/cluster_aggregation_planner.cpp b/src/mongo/s/query/cluster_aggregation_planner.cpp index 70adf70404a..3f970f2f8c1 100644 --- a/src/mongo/s/query/cluster_aggregation_planner.cpp +++ b/src/mongo/s/query/cluster_aggregation_planner.cpp @@ -65,7 +65,6 @@ namespace cluster_aggregation_planner { MONGO_FAIL_POINT_DEFINE(shardedAggregateFailToDispatchExchangeConsumerPipeline); MONGO_FAIL_POINT_DEFINE(shardedAggregateFailToEstablishMergingShardCursor); -MONGO_FAIL_POINT_DEFINE(shardedAggregateHangBeforeDispatchMergingPipeline); using sharded_agg_helpers::DispatchShardPipelineResults; using sharded_agg_helpers::SplitPipeline; @@ -172,13 +171,9 @@ Status dispatchMergingPipeline(const boost::intrusive_ptr<ExpressionContext>& ex const PrivilegeVector& privileges, bool hasChangeStream) { // We should never be in a situation where we call this function on a non-merge pipeline. - tassert(6525900, - "tried to dispatch merge pipeline but the pipeline was not split", - shardDispatchResults.splitPipeline); + invariant(shardDispatchResults.splitPipeline); auto* mergePipeline = shardDispatchResults.splitPipeline->mergePipeline.get(); - tassert(6525901, - "tried to dispatch merge pipeline but there was no merge portion of the split pipeline", - mergePipeline); + invariant(mergePipeline); auto* opCtx = expCtx->opCtx; std::vector<ShardId> targetedShards; @@ -237,13 +232,9 @@ Status dispatchMergingPipeline(const boost::intrusive_ptr<ExpressionContext>& ex privileges, expCtx->tailableMode)); - // If the mergingShard returned an error and did not accept ownership it is our responsibility - // to kill the cursors. - uassertStatusOK(getStatusFromCommandResult(mergeResponse.swResponse.getValue().data)); - - // If we didn't get an error from the merging shard, ownership for the shard cursors has been - // transferred to the merging shard. Dismiss the ownership in the current merging pipeline such - // that when it goes out of scope it does not attempt to kill the cursors. + // Ownership for the shard cursors has been transferred to the merging shard. Dismiss the + // ownership in the current merging pipeline such that when it goes out of scope it does not + // attempt to kill the cursors. auto mergeCursors = static_cast<DocumentSourceMergeCursors*>(mergePipeline->peekFront()); mergeCursors->dismissCursorOwnership(); @@ -345,27 +336,12 @@ BSONObj establishMergingMongosCursor(OperationContext* opCtx, responseBuilder.setPostBatchResumeToken(ccc->getPostBatchResumeToken()); } - bool exhausted = cursorState != ClusterCursorManager::CursorState::NotExhausted; - int nShards = ccc->getNumRemotes(); - - auto&& opDebug = CurOp::get(opCtx)->debug(); - // Fill out the aggregation metrics in CurOp, and record queryStats metrics, before detaching - // the cursor from its opCtx. - opDebug.nShards = std::max(opDebug.nShards, nShards); - opDebug.cursorExhausted = exhausted; - opDebug.additiveMetrics.nBatches = 1; - CurOp::get(opCtx)->setEndOfOpMetrics(responseBuilder.numDocs()); - if (exhausted) { - collectQueryStatsMongos(opCtx, ccc->takeKey()); - } else { - collectQueryStatsMongos(opCtx, ccc); - } - ccc->detachFromOperationContext(); + int nShards = ccc->getNumRemotes(); CursorId clusterCursorId = 0; - if (!exhausted) { + if (cursorState == ClusterCursorManager::CursorState::NotExhausted) { auto authUsers = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(); clusterCursorId = uassertStatusOK(Grid::get(opCtx)->getCursorManager()->registerCursor( opCtx, @@ -374,9 +350,16 @@ BSONObj establishMergingMongosCursor(OperationContext* opCtx, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, authUsers)); - opDebug.cursorid = clusterCursorId; } + // Fill out the aggregation metrics in CurOp. + if (clusterCursorId > 0) { + CurOp::get(opCtx)->debug().cursorid = clusterCursorId; + } + CurOp::get(opCtx)->debug().nShards = std::max(CurOp::get(opCtx)->debug().nShards, nShards); + CurOp::get(opCtx)->debug().cursorExhausted = (clusterCursorId == 0); + CurOp::get(opCtx)->debug().nreturned = responseBuilder.numDocs(); + responseBuilder.done(clusterCursorId, requestedNss.ns()); auto bodyBuilder = replyBuilder.getBodyBuilder(); @@ -391,9 +374,6 @@ DispatchShardPipelineResults dispatchExchangeConsumerPipeline( const NamespaceString& executionNss, Document serializedCommand, DispatchShardPipelineResults* shardDispatchResults) { - tassert(7163600, - "dispatchExchangeConsumerPipeline() must not be called for explain operation", - !expCtx->explain); auto opCtx = expCtx->opCtx; if (MONGO_unlikely(shardedAggregateFailToDispatchExchangeConsumerPipeline.shouldFail())) { @@ -430,8 +410,7 @@ DispatchShardPipelineResults dispatchExchangeConsumerPipeline( serializedCommand, consumerPipelines.back(), boost::none, /* exchangeSpec */ - false /* needsMerge */, - boost::none /* explain */); + false /* needsMerge */); requests.emplace_back(shardDispatchResults->exchangeSpec->consumerShards[idx], consumerCmdObj); @@ -456,11 +435,8 @@ DispatchShardPipelineResults dispatchExchangeConsumerPipeline( SplitPipeline splitPipeline{nullptr, std::move(mergePipeline), boost::none}; - // Relinquish ownership of the consumer pipelines' cursors. These cursors are now set up to be - // merged by a set of $mergeCursors pipelines that we just dispatched to the shards above. Now - // that we've established those pipelines on the shards, we are no longer responsible for - // ensuring they are cleaned up. If there was a problem establishing the cursors then - // establishCursors() would have thrown and mongos would kill all the consumer cursors itself. + // Relinquish ownership of the local consumer pipelines' cursors as each shard is now + // responsible for its own producer cursors. for (const auto& pipeline : consumerPipelines) { const auto& mergeCursors = static_cast<DocumentSourceMergeCursors*>(pipeline.shardsPipeline->peekFront()); @@ -587,7 +563,6 @@ AggregationTargeter AggregationTargeter::make( boost::optional<ChunkManager> cm, stdx::unordered_set<NamespaceString> involvedNamespaces, bool hasChangeStream, - bool startsWithDocuments, bool allowedToPassthrough, bool perShardCursor) { if (perShardCursor) { @@ -607,13 +582,11 @@ AggregationTargeter AggregationTargeter::make( }(); // Determine whether this aggregation must be dispatched to all shards in the cluster. - const bool mustRunOnAllShards = sharded_agg_helpers::checkIfMustRunOnAllShards( - executionNss, hasChangeStream, startsWithDocuments); + const bool mustRunOnAll = + sharded_agg_helpers::mustRunOnAllShards(executionNss, hasChangeStream); - // If we don't have a routing table, then this is either a $changeStream which must run on all - // shards or a $documents stage which must not. - invariant(cm || (mustRunOnAllShards && hasChangeStream) || - (startsWithDocuments && !mustRunOnAllShards)); + // If we don't have a routing table, then this is a $changeStream which must run on all shards. + invariant(cm || (mustRunOnAll && hasChangeStream)); // A pipeline is allowed to passthrough to the primary shard iff the following conditions are // met: @@ -625,7 +598,7 @@ AggregationTargeter AggregationTargeter::make( // $currentOp. // 4. Doesn't need transformation via DocumentSource::serialize(). For example, list sessions // needs to include information about users that can only be deduced on mongos. - if (cm && !cm->isSharded() && !mustRunOnAllShards && allowedToPassthrough && + if (cm && !cm->isSharded() && !mustRunOnAll && allowedToPassthrough && !involvesShardedCollections) { return AggregationTargeter{TargetingPolicy::kPassthrough, nullptr, cm}; } else { @@ -690,16 +663,11 @@ Status dispatchPipelineAndMerge(OperationContext* opCtx, const ClusterAggregate::Namespaces& namespaces, const PrivilegeVector& privileges, BSONObjBuilder* result, - bool hasChangeStream, - bool startsWithDocuments) { + bool hasChangeStream) { auto expCtx = targeter.pipeline->getContext(); // If not, split the pipeline as necessary and dispatch to the relevant shards. - auto shardDispatchResults = - sharded_agg_helpers::dispatchShardPipeline(serializedCommand, - hasChangeStream, - startsWithDocuments, - std::move(targeter.pipeline), - expCtx->explain); + auto shardDispatchResults = sharded_agg_helpers::dispatchShardPipeline( + serializedCommand, hasChangeStream, std::move(targeter.pipeline)); // If the operation is an explain, then we verify that it succeeded on all targeted // shards, write the results to the output builder, and return immediately. @@ -733,8 +701,6 @@ Status dispatchPipelineAndMerge(OperationContext* opCtx, expCtx, namespaces.executionNss, serializedCommand, &shardDispatchResults); } - shardedAggregateHangBeforeDispatchMergingPipeline.pauseWhileSet(); - // If we reach here, we have a merge pipeline to dispatch. return dispatchMergingPipeline(expCtx, namespaces, @@ -867,7 +833,6 @@ Status runPipelineOnSpecificShardOnly(const boost::intrusive_ptr<ExpressionConte if (explain) { // If this was an explain, then we get back an explain result object rather than a cursor. result = response.swResponse.getValue().data; - collectQueryStatsMongos(opCtx, std::move(CurOp::get(opCtx)->debug().queryStatsInfo.key)); } else { result = uassertStatusOK(storePossibleCursor( opCtx, diff --git a/src/mongo/s/query/cluster_aggregation_planner.h b/src/mongo/s/query/cluster_aggregation_planner.h index 78c919192db..046d8eab6ca 100644 --- a/src/mongo/s/query/cluster_aggregation_planner.h +++ b/src/mongo/s/query/cluster_aggregation_planner.h @@ -82,7 +82,6 @@ struct AggregationTargeter { boost::optional<ChunkManager> cm, stdx::unordered_set<NamespaceString> involvedNamespaces, bool hasChangeStream, - bool startsWithDocuments, bool allowedToPassthrough, bool perShardCursor); @@ -126,8 +125,7 @@ Status dispatchPipelineAndMerge(OperationContext* opCtx, const ClusterAggregate::Namespaces& namespaces, const PrivilegeVector& privileges, BSONObjBuilder* result, - bool hasChangeStream, - bool startsWithDocuments); + bool hasChangeStream); /** * Similar to runPipelineOnPrimaryShard but allows $changeStreams. Intended for use by per shard diff --git a/src/mongo/s/query/cluster_client_cursor.h b/src/mongo/s/query/cluster_client_cursor.h index 23a3367416d..8ff611eb308 100644 --- a/src/mongo/s/query/cluster_client_cursor.h +++ b/src/mongo/s/query/cluster_client_cursor.h @@ -211,30 +211,15 @@ public: */ virtual boost::optional<uint32_t> getQueryHash() const = 0; - virtual boost::optional<std::size_t> getQueryStatsKeyHash() const = 0; - - virtual bool getQueryStatsWillNeverExhaust() const = 0; - /** * Returns the number of batches returned by this cursor. */ - std::uint64_t getNBatches() const { - return _metrics.nBatches.value_or(0); - } + virtual std::uint64_t getNBatches() const = 0; /** * Increment the number of batches returned so far by one. */ - void incNBatches() { - _metrics.incrementNBatches(); - } - - void incrementCursorMetrics(OpDebug::AdditiveMetrics newMetrics) { - _metrics.add(newMetrics); - if (!_firstResponseExecutionTime) { - _firstResponseExecutionTime = _metrics.executionTime; - } - } + virtual void incNBatches() = 0; // // maxTimeMS support. @@ -260,20 +245,6 @@ public: _leftoverMaxTimeMicros = leftoverMaxTimeMicros; } - /** - * Returns and releases ownership of the Key associated with the request this - * cursor is handling. - */ - virtual std::unique_ptr<query_stats::Key> takeKey() = 0; - -protected: - // Metrics that are accumulated over the lifetime of the cursor, incremented with each getMore. - // Useful for diagnostics like queryStats. - OpDebug::AdditiveMetrics _metrics; - - // The execution time collected from the initial operation prior to any getMore requests. - boost::optional<Microseconds> _firstResponseExecutionTime; - private: // Unused maxTime budget for this cursor. Microseconds _leftoverMaxTimeMicros = Microseconds::max(); diff --git a/src/mongo/s/query/cluster_client_cursor_impl.cpp b/src/mongo/s/query/cluster_client_cursor_impl.cpp index 6b094a604f4..73be5a7512a 100644 --- a/src/mongo/s/query/cluster_client_cursor_impl.cpp +++ b/src/mongo/s/query/cluster_client_cursor_impl.cpp @@ -27,8 +27,6 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kQuery - #include "mongo/platform/basic.h" #include "mongo/s/query/cluster_client_cursor_impl.h" @@ -36,8 +34,6 @@ #include <memory> #include "mongo/db/curop.h" -#include "mongo/db/query/query_stats/query_stats.h" -#include "mongo/logv2/log.h" #include "mongo/s/query/router_stage_limit.h" #include "mongo/s/query/router_stage_merge.h" #include "mongo/s/query/router_stage_remove_metadata_fields.h" @@ -79,10 +75,7 @@ ClusterClientCursorImpl::ClusterClientCursorImpl(OperationContext* opCtx, _opCtx(opCtx), _createdDate(opCtx->getServiceContext()->getPreciseClockSource()->now()), _lastUseDate(_createdDate), - _queryHash(CurOp::get(opCtx)->debug().queryHash), - _queryStatsKeyHash(CurOp::get(opCtx)->debug().queryStatsInfo.keyHash), - _queryStatsKey(std::move(CurOp::get(opCtx)->debug().queryStatsInfo.key)), - _queryStatsWillNeverExhaust(CurOp::get(opCtx)->debug().queryStatsInfo.willNeverExhaust) { + _queryHash(CurOp::get(opCtx)->debug().queryHash) { dassert(!_params.compareWholeSortKeyOnRouter || SimpleBSONObjComparator::kInstance.evaluate( _params.sortToApplyOnRouter == AsyncResultsMerger::kWholeSortKeySortPattern)); @@ -99,11 +92,7 @@ ClusterClientCursorImpl::ClusterClientCursorImpl(OperationContext* opCtx, _opCtx(opCtx), _createdDate(opCtx->getServiceContext()->getPreciseClockSource()->now()), _lastUseDate(_createdDate), - _queryHash(CurOp::get(opCtx)->debug().queryHash), - _queryStatsKeyHash(CurOp::get(opCtx)->debug().queryStatsInfo.keyHash), - _queryStatsKey(std::move(CurOp::get(opCtx)->debug().queryStatsInfo.key)), - _queryStatsWillNeverExhaust( - std::move(CurOp::get(opCtx)->debug().queryStatsInfo.willNeverExhaust)) { + _queryHash(CurOp::get(opCtx)->debug().queryHash) { dassert(!_params.compareWholeSortKeyOnRouter || SimpleBSONObjComparator::kInstance.evaluate( _params.sortToApplyOnRouter == AsyncResultsMerger::kWholeSortKeySortPattern)); @@ -111,7 +100,7 @@ ClusterClientCursorImpl::ClusterClientCursorImpl(OperationContext* opCtx, } ClusterClientCursorImpl::~ClusterClientCursorImpl() { - if (_metrics.nBatches && *_metrics.nBatches > 1) + if (_nBatchesReturned > 1) mongosCursorStatsMoreThanOneBatch.increment(); } @@ -139,25 +128,7 @@ StatusWith<ClusterQueryResult> ClusterClientCursorImpl::next() { } void ClusterClientCursorImpl::kill(OperationContext* opCtx) { - if (_hasBeenKilled) { - LOGV2_DEBUG(7372700, - 3, - "Kill called on cluster client cursor after cursor has already been killed, so " - "ignoring"); - return; - } - - query_stats::writeQueryStatsOnCursorDisposeOrKill( - opCtx, - _queryStatsKeyHash, - std::move(_queryStatsKey), - _queryStatsWillNeverExhaust, - _metrics.executionTime.value_or(Microseconds{0}).count(), - _firstResponseExecutionTime.value_or(Microseconds{0}).count(), - _metrics.nreturned.value_or(0)); - _root->kill(opCtx); - _hasBeenKilled = true; } void ClusterClientCursorImpl::reattachToOperationContext(OperationContext* opCtx) { @@ -246,12 +217,12 @@ boost::optional<uint32_t> ClusterClientCursorImpl::getQueryHash() const { return _queryHash; } -boost::optional<std::size_t> ClusterClientCursorImpl::getQueryStatsKeyHash() const { - return _queryStatsKeyHash; +std::uint64_t ClusterClientCursorImpl::getNBatches() const { + return _nBatchesReturned; } -bool ClusterClientCursorImpl::getQueryStatsWillNeverExhaust() const { - return _queryStatsWillNeverExhaust; +void ClusterClientCursorImpl::incNBatches() { + ++_nBatchesReturned; } APIParameters ClusterClientCursorImpl::getAPIParameters() const { @@ -294,7 +265,4 @@ std::unique_ptr<RouterExecStage> ClusterClientCursorImpl::buildMergerPlan( return root; } -std::unique_ptr<query_stats::Key> ClusterClientCursorImpl::takeKey() { - return std::move(_queryStatsKey); -} } // namespace mongo diff --git a/src/mongo/s/query/cluster_client_cursor_impl.h b/src/mongo/s/query/cluster_client_cursor_impl.h index 8064a45595b..2529254cfce 100644 --- a/src/mongo/s/query/cluster_client_cursor_impl.h +++ b/src/mongo/s/query/cluster_client_cursor_impl.h @@ -32,7 +32,6 @@ #include <memory> #include <queue> -#include "mongo/bson/bsonobj.h" #include "mongo/executor/task_executor.h" #include "mongo/s/query/cluster_client_cursor.h" #include "mongo/s/query/cluster_client_cursor_guard.h" @@ -117,11 +116,9 @@ public: boost::optional<uint32_t> getQueryHash() const final; - boost::optional<std::size_t> getQueryStatsKeyHash() const final; + std::uint64_t getNBatches() const final; - bool getQueryStatsWillNeverExhaust() const final; - - std::unique_ptr<query_stats::Key> takeKey() final; + void incNBatches() final; public: /** @@ -178,19 +175,8 @@ private: // The hash of the query shape to be used for slow query logging; boost::optional<uint32_t> _queryHash; - // If boost::none, queryStats should not be collected for this cursor. - boost::optional<std::size_t> _queryStatsKeyHash; - - // The Key used by query stats to generate the query stats store key. - std::unique_ptr<query_stats::Key> _queryStatsKey; - - bool _queryStatsWillNeverExhaust = false; - - // Tracks if kill() has been called on the cursor. Multiple calls to kill() are treated as a - // noop. - // TODO SERVER-74482 investigate where kill() is called multiple times and remove unnecessary - // calls - bool _hasBeenKilled = false; + // The number of batches returned by this cursor. + std::uint64_t _nBatchesReturned = 0; }; } // namespace mongo diff --git a/src/mongo/s/query/cluster_client_cursor_mock.cpp b/src/mongo/s/query/cluster_client_cursor_mock.cpp index 103951694a0..567f3450499 100644 --- a/src/mongo/s/query/cluster_client_cursor_mock.cpp +++ b/src/mongo/s/query/cluster_client_cursor_mock.cpp @@ -89,6 +89,14 @@ long long ClusterClientCursorMock::getNumReturnedSoFar() const { return _numReturnedSoFar; } +std::uint64_t ClusterClientCursorMock::getNBatches() const { + return _nBatchesReturned; +} + +void ClusterClientCursorMock::incNBatches() { + ++_nBatchesReturned; +} + Date_t ClusterClientCursorMock::getCreatedDate() const { return _createdDate; } @@ -105,14 +113,6 @@ boost::optional<uint32_t> ClusterClientCursorMock::getQueryHash() const { return boost::none; } -boost::optional<std::size_t> ClusterClientCursorMock::getQueryStatsKeyHash() const { - return boost::none; -} - -bool ClusterClientCursorMock::getQueryStatsWillNeverExhaust() const { - return false; -} - void ClusterClientCursorMock::kill(OperationContext* opCtx) { _killed = true; if (_killCallback) { @@ -168,8 +168,4 @@ boost::optional<repl::ReadConcernArgs> ClusterClientCursorMock::getReadConcern() return boost::none; } -std::unique_ptr<query_stats::Key> ClusterClientCursorMock::takeKey() { - return nullptr; -} - } // namespace mongo diff --git a/src/mongo/s/query/cluster_client_cursor_mock.h b/src/mongo/s/query/cluster_client_cursor_mock.h index 64ec06d750f..bc2991ecf89 100644 --- a/src/mongo/s/query/cluster_client_cursor_mock.h +++ b/src/mongo/s/query/cluster_client_cursor_mock.h @@ -33,7 +33,7 @@ #include <functional> #include <queue> -#include "mongo/db/query/query_stats/key.h" +#include "mongo/db/logical_session_id.h" #include "mongo/s/query/cluster_client_cursor.h" namespace mongo { @@ -106,9 +106,9 @@ public: boost::optional<uint32_t> getQueryHash() const final; - boost::optional<std::size_t> getQueryStatsKeyHash() const final; + std::uint64_t getNBatches() const final; - bool getQueryStatsWillNeverExhaust() const final; + void incNBatches() final; /** * Returns false unless the mock cursor has been fully iterated. @@ -120,8 +120,6 @@ public: */ void queueError(Status status); - std::unique_ptr<query_stats::Key> takeKey() final; - private: bool _killed = false; std::queue<StatusWith<ClusterQueryResult>> _resultsQueue; diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp index 4452e6a8811..1209d709e22 100644 --- a/src/mongo/s/query/cluster_cursor_manager.cpp +++ b/src/mongo/s/query/cluster_cursor_manager.cpp @@ -41,7 +41,6 @@ #include "mongo/db/kill_sessions_common.h" #include "mongo/db/logical_session_cache.h" #include "mongo/db/query/query_knobs_gen.h" -#include "mongo/db/query/query_stats/query_stats.h" #include "mongo/logv2/log.h" #include "mongo/util/clock_source.h" #include "mongo/util/str.h" @@ -247,7 +246,6 @@ StatusWith<ClusterCursorManager::PinnedCursor> ClusterCursorManager::checkOutCur cursorGuard->reattachToOperationContext(opCtx); CurOp::get(opCtx)->debug().queryHash = cursorGuard->getQueryHash(); - CurOp::get(opCtx)->debug().queryStatsInfo.keyHash = cursorGuard->getQueryStatsKeyHash(); return PinnedCursor(this, std::move(cursorGuard), entry->getNamespace(), cursorId); } @@ -576,60 +574,4 @@ StatusWith<ClusterClientCursorGuard> ClusterCursorManager::_detachCursor(WithLoc return std::move(cursor); } - -void collectQueryStatsMongos(OperationContext* opCtx, std::unique_ptr<query_stats::Key> key) { - // If we haven't registered a cursor to prepare for getMore requests, we record - // queryStats directly. - auto&& opDebug = CurOp::get(opCtx)->debug(); - int64_t execTime = opDebug.additiveMetrics.executionTime.value_or(Microseconds{0}).count(); - query_stats::writeQueryStats(opCtx, - opDebug.queryStatsInfo.keyHash, - std::move(key), - execTime, - execTime, - opDebug.additiveMetrics.nreturned.value_or(0)); -} - -void collectQueryStatsMongos(OperationContext* opCtx, ClusterClientCursorGuard& cursor) { - cursor->incrementCursorMetrics(CurOp::get(opCtx)->debug().additiveMetrics); - - // For a change stream query that never ends, we want to collect query stats on the initial - // query and each getMore. Here we record the initial query. - // TODO SERVER-89058 Modify comment to include tailable cursors. - if (cursor->getQueryStatsWillNeverExhaust()) { - auto& opDebug = CurOp::get(opCtx)->debug(); - - int64_t execTime = opDebug.additiveMetrics.executionTime.value_or(Microseconds{0}).count(); - - query_stats::writeQueryStats(opCtx, - opDebug.queryStatsInfo.keyHash, - cursor->takeKey(), - execTime, - execTime, - opDebug.additiveMetrics.nreturned.value_or(0), - cursor->getQueryStatsWillNeverExhaust()); - } -} - -void collectQueryStatsMongos(OperationContext* opCtx, ClusterCursorManager::PinnedCursor& cursor) { - cursor->incrementCursorMetrics(CurOp::get(opCtx)->debug().additiveMetrics); - - // For a change stream query that never ends, we want to update query stats for every getMore on - // the cursor. - // TODO SERVER-89058 Modify comment to include tailable cursors. - if (cursor->getQueryStatsWillNeverExhaust()) { - auto& opDebug = CurOp::get(opCtx)->debug(); - - int64_t execTime = opDebug.additiveMetrics.executionTime.value_or(Microseconds{0}).count(); - - query_stats::writeQueryStats(opCtx, - opDebug.queryStatsInfo.keyHash, - nullptr, - execTime, - execTime, - opDebug.additiveMetrics.nreturned.value_or(0), - cursor->getQueryStatsWillNeverExhaust()); - } -} - } // namespace mongo diff --git a/src/mongo/s/query/cluster_cursor_manager.h b/src/mongo/s/query/cluster_cursor_manager.h index 73d07d91476..be10b0d60bd 100644 --- a/src/mongo/s/query/cluster_cursor_manager.h +++ b/src/mongo/s/query/cluster_cursor_manager.h @@ -599,19 +599,4 @@ private: size_t _cursorsTimedOut = 0; }; -/** - * Record metrics for the current operation on opDebug and aggregates those metrics for queryStats - * use. If a cursor is provided (via ClusterClientCursorGuard or - * ClusterCursorManager::PinnedCursor), metrics are aggregated on the cursor; otherwise, metrics are - * written directly to the queryStats store. - * NOTE: Metrics are taken from opDebug.additiveMetrics, so CurOp::setEndOfOpMetrics must be called - * *prior* to calling these. - * - * Currently, queryStats is only collected for find and aggregate requests (and their subsequent - * getMore requests), so these should only be called from those request paths. - */ -void collectQueryStatsMongos(OperationContext* opCtx, std::unique_ptr<query_stats::Key> key); -void collectQueryStatsMongos(OperationContext* opCtx, ClusterClientCursorGuard& cursor); -void collectQueryStatsMongos(OperationContext* opCtx, ClusterCursorManager::PinnedCursor& cursor); - } // namespace mongo diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp index 01c78f9c3c5..bd186639684 100644 --- a/src/mongo/s/query/cluster_find.cpp +++ b/src/mongo/s/query/cluster_find.cpp @@ -33,7 +33,6 @@ #include "mongo/s/query/cluster_find.h" -#include "mongo/db/query/query_stats/query_stats.h" #include <fmt/format.h> #include <memory> @@ -55,7 +54,6 @@ #include "mongo/db/query/find_common.h" #include "mongo/db/query/getmore_command_gen.h" #include "mongo/db/query/query_planner_common.h" -#include "mongo/db/query/query_stats/query_stats.h" #include "mongo/executor/task_executor_pool.h" #include "mongo/logv2/log.h" #include "mongo/platform/overflow_arithmetic.h" @@ -63,7 +61,6 @@ #include "mongo/s/client/num_hosts_targeted_metrics.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/cluster_commands_helpers.h" -#include "mongo/s/collection_uuid_mismatch.h" #include "mongo/s/grid.h" #include "mongo/s/query/async_results_merger.h" #include "mongo/s/query/cluster_client_cursor_impl.h" @@ -85,6 +82,11 @@ static const BSONObj kSortKeyMetaProjection = BSON("$meta" << "sortKey"); static const BSONObj kGeoNearDistanceMetaProjection = BSON("$meta" << "geoNearDistance"); +// We must allow some amount of overhead per result document, since when we make a cursor response +// the documents are elements of a BSONArray. The overhead is 1 byte/doc for the type + 1 byte/doc +// for the field name's null terminator + 1 byte per digit in the array index. The index can be no +// more than 8 decimal digits since the response is at most 16MB, and 16 * 1024 * 1024 < 1 * 10^8. +static const int kPerDocumentOverheadBytesUpperBound = 10; const char kFindCmdName[] = "find"; @@ -301,9 +303,12 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, if (ex.code() == ErrorCodes::CollectionUUIDMismatch && !ex.extraInfo<CollectionUUIDMismatchInfo>()->actualCollection() && !shardIds.count(cm.dbPrimary())) { - // We received CollectionUUIDMismatch but it does not contain the actual namespace, and - // we did not attempt to establish a cursor on the primary shard. - uassertStatusOK(populateCollectionUUIDMismatch(opCtx, ex.toStatus())); + // We received CollectionUUIDMismatchInfo but it does not contain the actual + // namespace, and we did not attempt to establish a cursor on the primary shard. + // Attempt to do so now in case the collection corresponding to the provided UUID is + // unsharded. This should throw CollectionUUIDMismatchInfo, StaleShardVersion, or + // StaleDbVersion. + establishCursorsOnShards({cm.dbPrimary()}); MONGO_UNREACHABLE; } @@ -336,7 +341,7 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, FindCommon::waitInFindBeforeMakingBatch(opCtx, query); auto cursorState = ClusterCursorManager::CursorState::NotExhausted; - FindCommon::BSONArrayResponseSizeTracker responseSizeTracker; + size_t bytesBuffered = 0; // This loop will not result in actually calling getMore against shards, but just loading // results from the initial batches (that were obtained while establishing cursors) into @@ -359,13 +364,14 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, // If adding this object will cause us to exceed the message size limit, then we stash it // for later. - if (!responseSizeTracker.haveSpaceForNext(nextObj)) { + if (!FindCommon::haveSpaceForNext(nextObj, results->size(), bytesBuffered)) { ccc->queueResult(nextObj); break; } - // Add doc to the batch. - responseSizeTracker.add(nextObj); + // Add doc to the batch. Account for the space overhead associated with returning this doc + // inside a BSON array. + bytesBuffered += (nextObj.objsize() + kPerDocumentOverheadBytesUpperBound); results->push_back(std::move(nextObj)); } @@ -375,26 +381,23 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, cursorState = ClusterCursorManager::CursorState::Exhausted; } - auto&& opDebug = CurOp::get(opCtx)->debug(); // Fill out query exec properties. - opDebug.nShards = ccc->getNumRemotes(); - opDebug.additiveMetrics.nBatches = 1; + CurOp::get(opCtx)->debug().nShards = ccc->getNumRemotes(); + CurOp::get(opCtx)->debug().nreturned = results->size(); // If the caller wants to know whether the cursor returned partial results, set it here. if (partialResultsReturned) { *partialResultsReturned = ccc->partialResultsReturned(); } - CurOp::get(opCtx)->setEndOfOpMetrics(results->size()); // If the cursor is exhausted, then there are no more results to return and we don't need to // allocate a cursor id. if (cursorState == ClusterCursorManager::CursorState::Exhausted) { - opDebug.cursorExhausted = true; + CurOp::get(opCtx)->debug().cursorExhausted = true; if (shardIds.size() > 0) { updateNumHostsTargetedMetrics(opCtx, cm, shardIds.size()); } - collectQueryStatsMongos(opCtx, ccc->takeKey()); return CursorId(0); } @@ -405,13 +408,13 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, ? ClusterCursorManager::CursorLifetime::Immortal : ClusterCursorManager::CursorLifetime::Mortal; auto authUsers = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(); - collectQueryStatsMongos(opCtx, ccc); + ccc->incNBatches(); auto cursorId = uassertStatusOK(cursorManager->registerCursor( opCtx, ccc.releaseCursor(), query.nss(), cursorType, cursorLifetime, authUsers)); // Record the cursorID in CurOp. - opDebug.cursorid = cursorId; + CurOp::get(opCtx)->debug().cursorid = cursorId; if (shardIds.size() > 0) { updateNumHostsTargetedMetrics(opCtx, cm, shardIds.size()); @@ -471,19 +474,6 @@ Status setUpOperationContextStateForGetMore(OperationContext* opCtx, return Status::OK(); } -CursorId earlyExitWithNoResults(OperationContext* opCtx, - const CanonicalQuery& query, - const FindCommandRequest& findCommand) { - uassert(CollectionUUIDMismatchInfo(query.nss().db().toString(), - *findCommand.getCollectionUUID(), - query.nss().coll().toString(), - boost::none), - "Database does not exist", - !findCommand.getCollectionUUID()); - collectQueryStatsMongos(opCtx, std::move(CurOp::get(opCtx)->debug().queryStatsInfo.key)); - - return CursorId(0); -} } // namespace const size_t ClusterFind::kMaxRetries = 10; @@ -526,7 +516,7 @@ CursorId ClusterFind::runQuery(OperationContext* opCtx, if (swCM == ErrorCodes::NamespaceNotFound) { // If the database doesn't exist, we successfully return an empty result set without // creating a cursor. - return earlyExitWithNoResults(opCtx, query, findCommand); + return CursorId(0); } const auto cm = uassertStatusOK(std::move(swCM)); @@ -773,7 +763,7 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx, } std::vector<BSONObj> batch; - FindCommon::BSONArrayResponseSizeTracker responseSizeTracker; + size_t bytesBuffered = 0; long long batchSize = cmd.getBatchSize().value_or(0); auto cursorState = ClusterCursorManager::CursorState::NotExhausted; BSONObj postBatchResumeToken; @@ -826,7 +816,8 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx, break; } - if (!responseSizeTracker.haveSpaceForNext(*next.getValue().getResult())) { + if (!FindCommon::haveSpaceForNext( + *next.getValue().getResult(), batch.size(), bytesBuffered)) { pinnedCursor.getValue()->queueResult(*next.getValue().getResult()); stashedResult = true; break; @@ -835,8 +826,10 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx, // As soon as we get a result, this operation no longer waits. awaitDataState(opCtx).shouldWaitForInserts = false; - // Add doc to the batch. - responseSizeTracker.add(*next.getValue().getResult()); + // Add doc to the batch. Account for the space overhead associated with returning this doc + // inside a BSON array. + bytesBuffered += + (next.getValue().getResult()->objsize() + kPerDocumentOverheadBytesUpperBound); batch.push_back(std::move(*next.getValue().getResult())); // Update the postBatchResumeToken. For non-$changeStream aggregations, this will be empty. @@ -853,20 +846,17 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx, postBatchResumeToken = pinnedCursor.getValue()->getPostBatchResumeToken(); } - auto&& opDebug = CurOp::get(opCtx)->debug(); - // Set nReturned and whether the cursor has been exhausted. - opDebug.cursorExhausted = (idToReturn == 0); - opDebug.additiveMetrics.nBatches = 1; - CurOp::get(opCtx)->setEndOfOpMetrics(batch.size()); - const bool partialResultsReturned = pinnedCursor.getValue()->partialResultsReturned(); pinnedCursor.getValue()->setLeftoverMaxTimeMicros(opCtx->getRemainingMaxTimeMicros()); - collectQueryStatsMongos(opCtx, pinnedCursor.getValue()); - + pinnedCursor.getValue()->incNBatches(); // Upon successful completion, transfer ownership of the cursor back to the cursor manager. If // the cursor has been exhausted, the cursor manager will clean it up for us. pinnedCursor.getValue().returnCursor(cursorState); + // Set nReturned and whether the cursor has been exhausted. + CurOp::get(opCtx)->debug().cursorExhausted = (idToReturn == 0); + CurOp::get(opCtx)->debug().nreturned = batch.size(); + if (MONGO_unlikely(waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch.shouldFail())) { CurOpFailpointHelpers::waitWhileFailPointEnabled( &waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch, diff --git a/src/mongo/s/query/document_source_merge_cursors.cpp b/src/mongo/s/query/document_source_merge_cursors.cpp index 34f6b1f11e6..c6f8f3fbaf6 100644 --- a/src/mongo/s/query/document_source_merge_cursors.cpp +++ b/src/mongo/s/query/document_source_merge_cursors.cpp @@ -52,6 +52,7 @@ DocumentSourceMergeCursors::DocumentSourceMergeCursors( : DocumentSource(kStageName, expCtx), _armParamsObj(std::move(ownedParamsSpec)), _armParams(std::move(armParams)) { + _armParams->setRecordRemoteOpWaitTime(true); // Populate the shard ids from the 'RemoteCursor'. recordRemoteCursorShardIds(_armParams->getRemotes()); @@ -82,6 +83,7 @@ bool DocumentSourceMergeCursors::remotesExhausted() const { void DocumentSourceMergeCursors::populateMerger() { invariant(!_blockingResultsMerger); invariant(_armParams); + invariant(_armParams->getRecordRemoteOpWaitTime()); _blockingResultsMerger.emplace( pExpCtx->opCtx, @@ -114,10 +116,11 @@ DocumentSource::GetNextResult DocumentSourceMergeCursors::doGetNext() { return Document::fromBsonWithMetaData(*next.getResult()); } -Value DocumentSourceMergeCursors::serialize(const SerializationOptions& opts) const { +Value DocumentSourceMergeCursors::serialize( + boost::optional<ExplainOptions::Verbosity> explain) const { invariant(!_blockingResultsMerger); invariant(_armParams); - return Value(Document{{kStageName, _armParams->toBSON(opts)}}); + return Value(Document{{kStageName, _armParams->toBSON()}}); } boost::intrusive_ptr<DocumentSource> DocumentSourceMergeCursors::createFromBson( diff --git a/src/mongo/s/query/document_source_merge_cursors.h b/src/mongo/s/query/document_source_merge_cursors.h index 4eb75bd34bd..33050bf45ab 100644 --- a/src/mongo/s/query/document_source_merge_cursors.h +++ b/src/mongo/s/query/document_source_merge_cursors.h @@ -80,7 +80,7 @@ public: /** * Serializes this stage to be sent to perform the merging on a different host. */ - Value serialize(const SerializationOptions& opts = SerializationOptions{}) const final override; + Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final; StageConstraints constraints(Pipeline::SplitState pipeState) const final { StageConstraints constraints(StreamType::kStreaming, diff --git a/src/mongo/s/query/establish_cursors.cpp b/src/mongo/s/query/establish_cursors.cpp index 18196d76751..82ec1df2809 100644 --- a/src/mongo/s/query/establish_cursors.cpp +++ b/src/mongo/s/query/establish_cursors.cpp @@ -42,11 +42,8 @@ #include "mongo/db/cursor_id.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/query/kill_cursors_gen.h" -#include "mongo/db/query/query_knobs_gen.h" -#include "mongo/executor/async_multicaster.h" #include "mongo/executor/remote_command_request.h" #include "mongo/executor/remote_command_response.h" -#include "mongo/executor/task_executor.h" #include "mongo/logv2/log.h" #include "mongo/s/grid.h" #include "mongo/s/multi_statement_transaction_requests_sender.h" @@ -105,13 +102,12 @@ public: return std::exchange(_remoteCursors, {}); }; - static void killOpOnShards(ServiceContext* srvCtx, - std::shared_ptr<executor::TaskExecutor> executor, - OperationKey opKey, - std::set<HostAndPort> remotes) noexcept; - private: void _handleFailure(const AsyncRequestsSender::Response& response, Status status) noexcept; + static void _killOpOnShards(ServiceContext* srvCtx, + std::shared_ptr<executor::TaskExecutor> executor, + OperationKey opKey, + std::set<HostAndPort> remotes) noexcept; /** * Favors the status with 'CollectionUUIDMismatch' error to be saved in '_maybeFailure' to be @@ -133,26 +129,22 @@ private: std::vector<HostAndPort> _remotesToClean; }; -// Attach our OperationKey to a request. This will allow us to kill any outstanding -// requests in case we're interrupted or one of the remotes returns an error. Note that although -// the opCtx may have an OperationKey set on it already, do not inherit it here because we may -// target ourselves which implies the same node receiving multiple operations with the same -// opKey. -BSONObj appendOpKey(const OperationKey& opKey, const BSONObj& request) { - BSONObjBuilder newCmd(request); - opKey.appendToBuilder(&newCmd, "clientOperationKey"); - return newCmd.obj(); -} - void CursorEstablisher::sendRequests(const ReadPreferenceSetting& readPref, const std::vector<std::pair<ShardId, BSONObj>>& remotes, Shard::RetryPolicy retryPolicy) { // Construct the requests std::vector<AsyncRequestsSender::Request> requests; + // Attach our OperationKey to each remote request. This will allow us to kill any outstanding + // requests in case we're interrupted or one of the remotes returns an error. Note that although + // the opCtx may have an OperationKey set on it already, do not inherit it here because we may + // target ourselves which implies the same node receiving multiple operations with the same + // opKey. // TODO SERVER-47261 management of the opKey should move to the ARS. for (const auto& remote : remotes) { - requests.emplace_back(remote.first, appendOpKey(_opKey, remote.second)); + BSONObjBuilder requestWithOpKey(remote.second); + _opKey.appendToBuilder(&requestWithOpKey, "clientOperationKey"); + requests.emplace_back(remote.first, requestWithOpKey.obj()); } LOGV2_DEBUG(4625502, @@ -190,9 +182,11 @@ void CursorEstablisher::waitForResponse() noexcept { hadValidCursor = true; - _remoteCursors.emplace_back(RemoteCursor(response.shardId.toString(), - *response.shardHostAndPort, - std::move(cursor.getValue()))); + RemoteCursor remoteCursor; + remoteCursor.setCursorResponse(std::move(cursor.getValue())); + remoteCursor.setShardId(response.shardId); + remoteCursor.setHostAndPort(*response.shardHostAndPort); + _remoteCursors.emplace_back(std::move(remoteCursor)); } if (response.shardHostAndPort && !hadValidCursor) { @@ -205,41 +199,16 @@ void CursorEstablisher::waitForResponse() noexcept { } } -// Schedule killOperations against all cursors that were established. Make sure to -// capture arguments by value since the cleanup work may get scheduled after -// returning from this function. -StatusWith<executor::TaskExecutor::CallbackHandle> scheduleCursorCleanup( - std::shared_ptr<executor::TaskExecutor> executor, - ServiceContext* svcCtx, - OperationKey opKey, - std::set<HostAndPort>&& remotesToClean) { - return executor->scheduleWork([svcCtx = svcCtx, - executor = executor, - opKey = opKey, - remotesToClean = std::move(remotesToClean)]( - const executor::TaskExecutor::CallbackArgs& args) mutable { - if (!args.status.isOK()) { - LOGV2_WARNING( - 7355702, "Failed to schedule remote cursor cleanup", "error"_attr = args.status); - return; - } - CursorEstablisher::killOpOnShards( - svcCtx, std::move(executor), std::move(opKey), std::move(remotesToClean)); - }); -} - void CursorEstablisher::checkForFailedRequests() { if (!_maybeFailure) { // If we saw no failures, there is nothing to do. return; } - if (!(_maybeFailure->code() == ErrorCodes::CommandOnShardedViewNotSupportedOnMongod)) { - LOGV2(4625501, - "Unable to establish remote cursors", - "error"_attr = *_maybeFailure, - "nRemotes"_attr = _remotesToClean.size()); - } + LOGV2(4625501, + "Unable to establish remote cursors", + "error"_attr = *_maybeFailure, + "nRemotes"_attr = _remotesToClean.size()); if (_remotesToClean.empty()) { // If we don't have any remotes to clean, throw early. @@ -249,8 +218,21 @@ void CursorEstablisher::checkForFailedRequests() { // Filter out duplicate hosts. auto remotes = std::set<HostAndPort>(_remotesToClean.begin(), _remotesToClean.end()); - uassertStatusOK( - scheduleCursorCleanup(_executor, _opCtx->getServiceContext(), _opKey, std::move(remotes))); + // Schedule killOperations against all cursors that were established. Make sure to + // capture arguments by value since the cleanup work may get scheduled after + // returning from this function. + uassertStatusOK(_executor->scheduleWork( + [svcCtx = _opCtx->getServiceContext(), + executor = _executor, + opKey = _opKey, + remotes = std::move(remotes)](const executor::TaskExecutor::CallbackArgs& args) mutable { + if (!args.status.isOK()) { + LOGV2_WARNING( + 48038, "Failed to schedule remote cursor cleanup", "error"_attr = args.status); + return; + } + _killOpOnShards(svcCtx, std::move(executor), std::move(opKey), std::move(remotes)); + })); // Throw our failure. uassertStatusOK(*_maybeFailure); @@ -312,10 +294,10 @@ void CursorEstablisher::_handleFailure(const AsyncRequestsSender::Response& resp _maybeFailure = std::move(status); } -void CursorEstablisher::killOpOnShards(ServiceContext* srvCtx, - std::shared_ptr<executor::TaskExecutor> executor, - OperationKey opKey, - std::set<HostAndPort> remotes) noexcept try { +void CursorEstablisher::_killOpOnShards(ServiceContext* srvCtx, + std::shared_ptr<executor::TaskExecutor> executor, + OperationKey opKey, + std::set<HostAndPort> remotes) noexcept try { ThreadClient tc("establishCursors cleanup", srvCtx); auto opCtx = tc->makeOperationContext(); @@ -345,16 +327,6 @@ void CursorEstablisher::killOpOnShards(ServiceContext* srvCtx, } catch (const AssertionException& ex) { LOGV2_DEBUG(4625503, 2, "Failed to cleanup remote operations", "error"_attr = ex.toStatus()); } -/** - * Returns a copy of 'cmdObj' with the $readPreference mode set to secondaryPreferred. - */ -BSONObj appendReadPreferenceNearest(BSONObj cmdObj) { - BSONObjBuilder cmdWithReadPrefBob(std::move(cmdObj)); - cmdWithReadPrefBob.append("$readPreference", - BSON("mode" - << "nearest")); - return cmdWithReadPrefBob.obj(); -} } // namespace @@ -386,101 +358,4 @@ void killRemoteCursor(OperationContext* opCtx, executor->scheduleRemoteCommand(request, [](auto const&) {}).getStatus().ignore(); } -std::pair<std::vector<HostAndPort>, StringMap<ShardId>> getHostInfos( - OperationContext* opCtx, const std::set<ShardId>& shardIds) { - std::vector<HostAndPort> servers; - StringMap<ShardId> hostToShardId; - - // Get the host/port of every node in each shard. - auto registry = Grid::get(opCtx)->shardRegistry(); - for (const auto& shardId : shardIds) { - auto shard = uassertStatusOK(registry->getShard(opCtx, shardId)); - auto cs = shard->getConnString(); - auto shardServers = cs.getServers(); - for (auto& host : shardServers) { - hostToShardId.emplace(host.toString(), shardId); - } - servers.insert(servers.end(), shardServers.begin(), shardServers.end()); - } - return {std::move(servers), hostToShardId}; -} - -std::vector<RemoteCursor> establishCursorsOnAllHosts( - OperationContext* opCtx, - std::shared_ptr<executor::TaskExecutor> executor, - const NamespaceString& nss, - const std::set<ShardId>& shardIds, - BSONObj cmdObj, - bool allowPartialResults, - Shard::RetryPolicy retryPolicy) { - auto [servers, hostToShardId] = getHostInfos(opCtx, shardIds); - OperationKey opKey = UUID::gen(); - - // Operation key will allow us to kill any outstanding requests in case we're interrupted. - // Secondaries will reject aggregation commands with a default read preference (primary). The - // actual semantics of read preference don't make as much sense when broadcasting to all - // shards, but we will set read preference to 'nearest' since it does not imply preference for - // primary or secondary. - BSONObj cmd = appendOpKey(opKey, appendReadPreferenceNearest(cmdObj)); - - executor::AsyncMulticaster::Options options; - options.maxConcurrency = internalQueryAggMulticastMaxConcurrency; - auto results = executor::AsyncMulticaster(executor, options) - .multicast(servers, - nss.db().toString(), - cmd, - opCtx, - Milliseconds(internalQueryAggMulticastTimeoutMS)); - std::vector<RemoteCursor> remoteCursors; - std::set<HostAndPort> remotesToClean; - - boost::optional<Status> failure; - - for (auto&& [hostAndPort, result] : results) { - if (result.isOK()) { - auto cursors = CursorResponse::parseFromBSONMany(result.data); - bool hadValidCursor = false; - - auto it = hostToShardId.find(hostAndPort.toString()); - tassert(7355701, "Host must have shard ID.", it != hostToShardId.end()); - auto shardId = it->second; - - for (auto& cursor : cursors) { - if (!cursor.isOK()) { - failure = cursor.getStatus(); - continue; - } - hadValidCursor = true; - - remoteCursors.emplace_back( - RemoteCursor(shardId.toString(), hostAndPort, std::move(cursor.getValue()))); - } - - if (hadValidCursor) { - remotesToClean.insert(hostAndPort); - } - } else { - LOGV2_DEBUG(7355700, - 3, - "Experienced a failure while establishing cursors", - "error"_attr = result.status); - failure = result.status; - } - } - if (failure.has_value() && !allowPartialResults) { - LOGV2(7355705, - "Unable to establish remote cursors", - "error"_attr = *failure, - "nRemotes"_attr = remoteCursors.size()); - - if (!remotesToClean.empty()) { - uassertStatusOK(scheduleCursorCleanup( - executor, opCtx->getServiceContext(), opKey, std::move(remotesToClean))); - } - - uassertStatusOK(failure.value()); - } - return remoteCursors; -} - } // namespace mongo diff --git a/src/mongo/s/query/establish_cursors.h b/src/mongo/s/query/establish_cursors.h index cd19af7eea9..3a904adcadd 100644 --- a/src/mongo/s/query/establish_cursors.h +++ b/src/mongo/s/query/establish_cursors.h @@ -73,26 +73,6 @@ std::vector<RemoteCursor> establishCursors( Shard::RetryPolicy retryPolicy = Shard::RetryPolicy::kIdempotent); /** - * Establishes cursors on every host in the remote shards by issuing requests in parallel with the - * AsyncMulticaster. - * - * If any of the cursors fail to be established, this function performs cleanup by sending - * killCursors to any cursors that were established, then throws the error. If the namespace - * represents a view, an exception containing a ResolvedView is thrown. - * - * On success, the ownership of the cursors is transferred to the caller. This means the caller is - * now responsible for either exhausting the cursors or sending killCursors to them. - */ -std::vector<RemoteCursor> establishCursorsOnAllHosts( - OperationContext* opCtx, - std::shared_ptr<executor::TaskExecutor> executor, - const NamespaceString& nss, - const std::set<ShardId>& shardIds, - BSONObj cmdObj, - bool allowPartialResults, - Shard::RetryPolicy retryPolicy = Shard::RetryPolicy::kIdempotent); - -/** * Schedules a remote killCursor command for 'cursor'. * * Note that this method is optimistic and does not check the return status for the killCursors diff --git a/src/mongo/s/query/router_stage_remove_metadata_fields.cpp b/src/mongo/s/query/router_stage_remove_metadata_fields.cpp index 7f3d89beef1..320c441ef46 100644 --- a/src/mongo/s/query/router_stage_remove_metadata_fields.cpp +++ b/src/mongo/s/query/router_stage_remove_metadata_fields.cpp @@ -53,14 +53,9 @@ StatusWith<ClusterQueryResult> RouterStageRemoveMetadataFields::next() { } BSONObjIterator iterator(*childResult.getValue().getResult()); - // Find the first field that we need to remove. - for (; iterator.more(); ++iterator) { - // To save some time, we ensure that the current field name starts with a $ - // before checking if it's actually a metadata field in the map. - if ((*iterator).fieldName()[0] == '$' && _metaFields.contains((*iterator).fieldName())) { - break; - } + while (iterator.more() && (*iterator).fieldName()[0] != '$') { + ++iterator; } if (!iterator.more()) { diff --git a/src/mongo/s/query/router_stage_remove_metadata_fields_test.cpp b/src/mongo/s/query/router_stage_remove_metadata_fields_test.cpp index 9fa199a2ac9..a18aa0cbb31 100644 --- a/src/mongo/s/query/router_stage_remove_metadata_fields_test.cpp +++ b/src/mongo/s/query/router_stage_remove_metadata_fields_test.cpp @@ -194,60 +194,6 @@ TEST(RouterStageRemoveMetadataFieldsTest, ForwardsAwaitDataTimeout) { ASSERT_EQ(789, durationCount<Milliseconds>(awaitDataTimeout.getValue())); } -// Grabs the next document from a stage and ensure it matches expectedDoc. -// Assumes that remotes are exhausted after one document. -void verifyNextDocument(RouterExecStage* stage, const BSONObj& expectedDoc) { - auto result = stage->next(); - ASSERT_OK(result.getStatus()); - ASSERT(result.getValue().getResult()); - ASSERT_BSONOBJ_EQ(*result.getValue().getResult(), expectedDoc); - ASSERT_TRUE(stage->remotesExhausted()); -} - -TEST(RouterStageRemoveMetadataFieldsTest, AllowsNonMetaDataDollars) { - auto mockStage = std::make_unique<RouterStageMock>(opCtx); - mockStage->queueResult(BSON("$a" << 1 << "$sortKey" << 1 << "b" << 1)); - mockStage->queueResult(BSON("a" << 2 << "$sortKey" << 1 << "$b" << 2)); - mockStage->markRemotesExhausted(); - - auto sortKeyStage = std::make_unique<RouterStageRemoveMetadataFields>( - opCtx, std::move(mockStage), StringDataSet{"$sortKey"_sd}); - ASSERT_TRUE(sortKeyStage->remotesExhausted()); - - verifyNextDocument(sortKeyStage.get(), BSON("$a" << 1 << "b" << 1)); - verifyNextDocument(sortKeyStage.get(), BSON("a" << 2 << "$b" << 2)); - - auto endResult = sortKeyStage->next(); - ASSERT_OK(endResult.getStatus()); - ASSERT(endResult.getValue().isEOF()); - ASSERT_TRUE(sortKeyStage->remotesExhausted()); -} - -// For every keyword, ensure it's removed if it's in the first, middle, or -// last position, and that the remainder of the document is undisturbed. -TEST(RouterStageRemoveMetadataFieldsTest, RemovesAllMetaDataDollars) { - for (auto& keyword : Document::allMetadataFieldNames) { - auto mockStage = std::make_unique<RouterStageMock>(opCtx); - mockStage->queueResult(BSON(keyword << 1 << "$a" << 1)); - mockStage->queueResult(BSON("$a" << 1 << keyword << 1)); - mockStage->queueResult(BSON("$a" << 1 << keyword << 1 << "$b" << 1)); - mockStage->markRemotesExhausted(); - - auto sortKeyStage = std::make_unique<RouterStageRemoveMetadataFields>( - opCtx, std::move(mockStage), Document::allMetadataFieldNames); - ASSERT_TRUE(sortKeyStage->remotesExhausted()); - - verifyNextDocument(sortKeyStage.get(), BSON("$a" << 1)); - verifyNextDocument(sortKeyStage.get(), BSON("$a" << 1)); - verifyNextDocument(sortKeyStage.get(), BSON("$a" << 1 << "$b" << 1)); - - auto endResult = sortKeyStage->next(); - ASSERT_OK(endResult.getStatus()); - ASSERT(endResult.getValue().isEOF()); - ASSERT_TRUE(sortKeyStage->remotesExhausted()); - } -} - } // namespace } // namespace mongo diff --git a/src/mongo/s/query/store_possible_cursor.cpp b/src/mongo/s/query/store_possible_cursor.cpp index 723cafff2e4..c778daa9d84 100644 --- a/src/mongo/s/query/store_possible_cursor.cpp +++ b/src/mongo/s/query/store_possible_cursor.cpp @@ -88,17 +88,15 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx, return incomingCursorResponse.getStatus(); } - auto&& opDebug = CurOp::get(opCtx)->debug(); - opDebug.additiveMetrics.nBatches = 1; + CurOp::get(opCtx)->debug().nreturned = incomingCursorResponse.getValue().getBatch().size(); + // If nShards has already been set, then we are storing the forwarding $mergeCursors cursor from // a split aggregation pipeline, and the shards half of that pipeline may have targeted multiple // shards. In that case, leave the current value as-is. - opDebug.nShards = std::max(opDebug.nShards, 1); - CurOp::get(opCtx)->setEndOfOpMetrics(incomingCursorResponse.getValue().getBatch().size()); + CurOp::get(opCtx)->debug().nShards = std::max(CurOp::get(opCtx)->debug().nShards, 1); if (incomingCursorResponse.getValue().getCursorId() == CursorId(0)) { - opDebug.cursorExhausted = true; - collectQueryStatsMongos(opCtx, std::move(opDebug.queryStatsInfo.key)); + CurOp::get(opCtx)->debug().cursorExhausted = true; return cmdResult; } @@ -130,7 +128,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx, } auto ccc = ClusterClientCursorImpl::make(opCtx, std::move(executor), std::move(params)); - collectQueryStatsMongos(opCtx, ccc); + ccc->incNBatches(); // We don't expect to use this cursor until a subsequent getMore, so detach from the current // OperationContext until then. ccc->detachFromOperationContext(); @@ -146,7 +144,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx, return clusterCursorId.getStatus(); } - opDebug.cursorid = clusterCursorId.getValue(); + CurOp::get(opCtx)->debug().cursorid = clusterCursorId.getValue(); CursorResponse outgoingCursorResponse( requestedNss, diff --git a/src/mongo/s/request_types/auto_split_vector.idl b/src/mongo/s/request_types/auto_split_vector.idl index 3e55eb01d8e..abcbece2019 100644 --- a/src/mongo/s/request_types/auto_split_vector.idl +++ b/src/mongo/s/request_types/auto_split_vector.idl @@ -75,7 +75,3 @@ commands: maxChunkSizeBytes: type: safeInt64 description: "Max chunk size of the collection expressed in bytes" - limit: - type: int - description: "Max number of split points to look for" - optional: true diff --git a/src/mongo/s/request_types/balancer_collection_status.idl b/src/mongo/s/request_types/balancer_collection_status.idl index a821222f933..53e86886562 100644 --- a/src/mongo/s/request_types/balancer_collection_status.idl +++ b/src/mongo/s/request_types/balancer_collection_status.idl @@ -40,7 +40,7 @@ structs: strict: false fields: chunkSize: - type: safeDouble + type: safeInt64 description: "Configured chunk size in MiB for this collection" balancerCompliant: type: bool diff --git a/src/mongo/s/request_types/commit_chunk_migration_request_type.h b/src/mongo/s/request_types/commit_chunk_migration_request_type.h index 073ef9404f6..16d5f0ef8ce 100644 --- a/src/mongo/s/request_types/commit_chunk_migration_request_type.h +++ b/src/mongo/s/request_types/commit_chunk_migration_request_type.h @@ -81,11 +81,6 @@ public: const Timestamp& getCollectionTimestamp() { return _collectionTimestamp; } - /* - * NOTE: v7.0 is deprecating the _validAfter field of CommitChunkMigrationRequest - * (see SERVER-74469 for details). - * This method should not be used to develop new functionality. - */ const boost::optional<Timestamp>& getValidAfter() { return _validAfter; } diff --git a/src/mongo/s/request_types/get_stats_for_balancing.idl b/src/mongo/s/request_types/get_stats_for_balancing.idl deleted file mode 100644 index 7ee96da4eaa..00000000000 --- a/src/mongo/s/request_types/get_stats_for_balancing.idl +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (C) 2022-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# As a special exception, the copyright holders give permission to link the -# code of portions of this program with the OpenSSL library under certain -# conditions as described in each individual source file and distribute -# linked combinations including the program with the OpenSSL library. You -# must comply with the Server Side Public License in all respects for -# all of the code used other than as permitted herein. If you modify file(s) -# with this exception, you may extend this exception to your version of the -# file(s), but you are not obligated to do so. If you do not wish to do so, -# delete this exception statement from your version. If you delete this -# exception statement from all source files in the program, then also delete -# it in the license file. -# - -global: - cpp_namespace: "mongo" - -imports: - - "mongo/idl/basic_types.idl" - -structs: - NamespaceWithOptionalUUID: - description: 'Namespace with an optional collection UUID' - strict: false - fields: - ns: - description: 'Namespace of the collection' - type: namespacestring - UUID: - description: 'Collection UUID' - type: uuid - optional: true # optional because the caller may not attach the collection UUID - - CollStatsForBalancing: - description: 'Collection stats for a specific collection' - strict: false - fields: - namespace: - description: 'Namespace of the collection' - type: namespacestring - cpp_name: ns - collSize: - description: 'size of data currently owned by this shard for this collection' - type: safeInt64 - - ShardsvrGetStatsForBalancingReply: - description: 'Response for ShardsvrGetStatsForBalancing command' - strict: false - fields: - stats: - description: 'List of stats for each of the requested collection' - type: array<CollStatsForBalancing> - -commands: - _shardsvrGetStatsForBalancing: - command_name: _shardsvrGetStatsForBalancing - cpp_name: ShardsvrGetStatsForBalancing - description: 'Internal command used by the balancer to retrieve stats for balancing.' - namespace: ignored - api_version: '' - strict: false - reply_type: ShardsvrGetStatsForBalancingReply - fields: - collections: - description: 'List of namespaces to retrieve statistic for' - type: array<NamespaceWithOptionalUUID> - scaleFactor: - description: 'Scale factor for data size units. If omitted 1048576 (MiB) will be used' - type: exactInt64 - optional: true diff --git a/src/mongo/s/request_types/merge_chunks_request_test.cpp b/src/mongo/s/request_types/merge_chunks_request_test.cpp index f7e77f1adaf..b631ca1dffa 100644 --- a/src/mongo/s/request_types/merge_chunks_request_test.cpp +++ b/src/mongo/s/request_types/merge_chunks_request_test.cpp @@ -60,12 +60,12 @@ TEST(ConfigSvrMergeChunks, BasicValidConfigCommand) { TEST(ConfigSvrMergeChunks, ConfigCommandtoBSON) { auto collUUID = UUID::gen(); - BSONObj serializedRequest = - BSON("_configsvrCommitChunksMerge" - << "TestDB.TestColl" - << "shard" - << "shard0000" - << "collUUID" << collUUID.toBSON() << "chunkRange" << chunkRange.toBSON()); + BSONObj serializedRequest = BSON("_configsvrCommitChunksMerge" + << "TestDB.TestColl" + << "shard" + << "shard0000" + << "collUUID" << collUUID.toBSON() << "chunkRange" + << chunkRange.toBSON() << "validAfter" << Timestamp{100}); BSONObj writeConcernObj = BSON("w" << "majority"); diff --git a/src/mongo/s/request_types/move_primary.idl b/src/mongo/s/request_types/move_primary.idl index 4691542c06b..51d4a095b8a 100644 --- a/src/mongo/s/request_types/move_primary.idl +++ b/src/mongo/s/request_types/move_primary.idl @@ -33,7 +33,6 @@ global: imports: - "mongo/idl/basic_types.idl" - - "mongo/s/sharding_types.idl" structs: movePrimary: @@ -63,20 +62,3 @@ structs: to: type: string description: "The shard serving as the destination for un-sharded collections." - -commands: - _configsvrCommitMovePrimary: - command_name: _configsvrCommitMovePrimary - description: Reassign a new primary shard for the given database on the config server - cpp_name: ConfigsvrCommitMovePrimary - namespace: type - type: string - api_version: - strict: false - fields: - expectedDatabaseVersion: - type: database_version - description: Database version known by the current primary shard - to: - type: shard_id - description: Shard serving as the destination for un-sharded collections diff --git a/src/mongo/s/request_types/sharded_ddl_commands.idl b/src/mongo/s/request_types/sharded_ddl_commands.idl index 2791c5a1b65..2c86a3d199a 100644 --- a/src/mongo/s/request_types/sharded_ddl_commands.idl +++ b/src/mongo/s/request_types/sharded_ddl_commands.idl @@ -315,11 +315,6 @@ commands: api_version: "" cpp_name: ShardsvrDropCollectionParticipant strict: false - fields: - fromMigrate: - type: bool - description: "Whether the drop comes as a result of an interrupted migration process." - optional: true _shardsvrRenameCollection: command_name: _shardsvrRenameCollection @@ -330,12 +325,6 @@ commands: api_version: "" chained_structs: RenameCollectionRequest: RenameCollectionRequest - fields: - allowEncryptedCollectionRename: - description: "Encrypted Collection renames are usually disallowed to minimize user error. - C2C needs to do the renames to replicate create collection." - type: bool - optional: true _shardsvrSetAllowMigrations: command_name: _shardsvrSetAllowMigrations @@ -381,7 +370,7 @@ commands: namespace: concatenate_with_db api_version: "" strict: false - chained_structs: + chained_structs: RefineCollectionShardKeyRequest: RefineCollectionShardKeyRequest _configsvrRefineCollectionShardKey: @@ -414,7 +403,7 @@ commands: namespace: concatenate_with_db api_version: "" strict: false - chained_structs: + chained_structs: DropIndexesRequest: DropIndexesRequest _configsvrCreateDatabase: diff --git a/src/mongo/s/resharding/common_types.idl b/src/mongo/s/resharding/common_types.idl index 456f96bf811..fb0547b7091 100644 --- a/src/mongo/s/resharding/common_types.idl +++ b/src/mongo/s/resharding/common_types.idl @@ -271,13 +271,10 @@ structs: description: "A struct representing the information needed for a resharding pipeline to determine which documents belong to a particular shard." strict: true - query_shape_component: true fields: recipientShardId: type: shard_id description: "The id of the recipient shard." - query_shape: anonymize reshardingKey: type: KeyPattern description: "The index specification document to use as the new shard key." - query_shape: custom diff --git a/src/mongo/s/router_role.cpp b/src/mongo/s/router.cpp index 6fa31d6a447..dba40ad1137 100644 --- a/src/mongo/s/router_role.cpp +++ b/src/mongo/s/router.cpp @@ -29,7 +29,7 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding -#include "mongo/s/router_role.h" +#include "mongo/s/router.h" #include "mongo/logv2/log.h" #include "mongo/s/grid.h" @@ -69,34 +69,32 @@ CachedDatabaseInfo DBPrimaryRouter::_getRoutingInfo(OperationContext* opCtx) con } void DBPrimaryRouter::_onException(RouteContext* context, Status s) { - auto catalogCache = Grid::get(_service)->catalogCache(); - - if (s == ErrorCodes::StaleDbVersion) { - auto si = s.extraInfo<StaleDbRoutingVersion>(); - tassert(6375900, "StaleDbVersion must have extraInfo", si); - tassert(6375901, - str::stream() << "StaleDbVersion on unexpected database. Expected " << _db - << ", received " << si->getDb(), - si->getDb() == _db); - - catalogCache->onStaleDatabaseVersion(si->getDb(), si->getVersionWanted()); - } else { - uassertStatusOK(s); - } - if (++context->numAttempts > kMaxNumStaleVersionRetries) { uassertStatusOKWithContext( s, str::stream() << "Exceeded maximum number of " << kMaxNumStaleVersionRetries << " retries attempting \'" << context->comment << "\'"); } else { - LOGV2_DEBUG(6375902, + LOGV2_DEBUG(637590, 3, - "Retrying database primary routing operation", - "attempt"_attr = context->numAttempts, - "comment"_attr = context->comment, + "Retrying {description}. Got error: {status}", + "description"_attr = context->comment, "status"_attr = s); } + + auto catalogCache = Grid::get(_service)->catalogCache(); + + if (s == ErrorCodes::StaleDbVersion) { + auto si = s.extraInfo<StaleDbRoutingVersion>(); + invariant(si); + invariant(si->getDb() == _db, + str::stream() << "StaleDbVersion on unexpected database. Expected " << _db + << ", received " << si->getDb()); + + catalogCache->onStaleDatabaseVersion(si->getDb(), si->getVersionWanted()); + } else { + uassertStatusOK(s); + } } CollectionRouter::CollectionRouter(ServiceContext* service, NamespaceString nss) @@ -124,39 +122,42 @@ ChunkManager CollectionRouter::_getRoutingInfo(OperationContext* opCtx) const { } void CollectionRouter::_onException(RouteContext* context, Status s) { - auto catalogCache = Grid::get(_service)->catalogCache(); - - if (s == ErrorCodes::StaleDbVersion) { - auto si = s.extraInfo<StaleDbRoutingVersion>(); - tassert(6375903, "StaleDbVersion must have extraInfo", si); - catalogCache->onStaleDatabaseVersion(si->getDb(), si->getVersionWanted()); - } else if (s == ErrorCodes::StaleConfig) { - auto si = s.extraInfo<StaleConfigInfo>(); - tassert(6375904, "StaleConfig must have extraInfo", si); - catalogCache->invalidateShardOrEntireCollectionEntryForShardedCollection( - si->getNss(), si->getVersionWanted(), si->getShardId()); - } else if (s == ErrorCodes::StaleEpoch) { - if (auto si = s.extraInfo<StaleEpochInfo>()) { - catalogCache->invalidateShardOrEntireCollectionEntryForShardedCollection( - si->getNss(), si->getVersionWanted(), ShardId()); - } - } else { - uassertStatusOK(s); - } - if (++context->numAttempts > kMaxNumStaleVersionRetries) { uassertStatusOKWithContext( s, str::stream() << "Exceeded maximum number of " << kMaxNumStaleVersionRetries << " retries attempting \'" << context->comment << "\'"); } else { - LOGV2_DEBUG(6375906, + LOGV2_DEBUG(637591, 3, - "Retrying collection routing operation", - "attempt"_attr = context->numAttempts, - "comment"_attr = context->comment, + "Retrying {description}. Got error: {status}", + "description"_attr = context->comment, "status"_attr = s); } + + auto catalogCache = Grid::get(_service)->catalogCache(); + + if (s.isA<ErrorCategory::StaleShardVersionError>()) { + if (auto si = s.extraInfo<StaleConfigInfo>()) { + invariant(si->getNss() == _nss, + str::stream() << "StaleConfig on unexpected namespace. Expected " << _nss + << ", received " << si->getNss()); + catalogCache->invalidateShardOrEntireCollectionEntryForShardedCollection( + _nss, si->getVersionWanted(), si->getShardId()); + } else { + catalogCache->invalidateCollectionEntry_LINEARIZABLE(_nss); + } + } else if (s == ErrorCodes::StaleDbVersion) { + auto si = s.extraInfo<StaleDbRoutingVersion>(); + invariant(si); + invariant(si->getDb() == _nss.db(), + str::stream() << "StaleDbVersion on unexpected database. Expected " << _nss.db() + << ", received " << si->getDb()); + + catalogCache->onStaleDatabaseVersion(si->getDb(), si->getVersionWanted()); + } else { + uassertStatusOK(s); + } } } // namespace router diff --git a/src/mongo/s/router_role.h b/src/mongo/s/router.h index 6cfd1d9e546..6cfd1d9e546 100644 --- a/src/mongo/s/router_role.h +++ b/src/mongo/s/router.h diff --git a/src/mongo/s/routing_table_history_test.cpp b/src/mongo/s/routing_table_history_test.cpp index 31fd50674ca..9651911ee64 100644 --- a/src/mongo/s/routing_table_history_test.cpp +++ b/src/mongo/s/routing_table_history_test.cpp @@ -27,39 +27,20 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - #include "mongo/platform/basic.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/namespace_string.h" #include "mongo/db/service_context.h" -#include "mongo/idl/server_parameter_test_util.h" -#include "mongo/logv2/log.h" #include "mongo/s/catalog/type_chunk.h" #include "mongo/s/chunk_manager.h" #include "mongo/s/chunk_writes_tracker.h" -#include "mongo/s/chunks_test_util.h" #include "mongo/unittest/death_test.h" #include "mongo/unittest/unittest.h" - namespace mongo { - -using chunks_test_util::assertEqualChunkInfo; -using chunks_test_util::calculateCollVersion; -using chunks_test_util::calculateIntermediateShardKey; -using chunks_test_util::calculateShardsMaxValidAfter; -using chunks_test_util::calculateShardVersions; -using chunks_test_util::genChunkVector; -using chunks_test_util::genRandomChunkVector; -using chunks_test_util::getShardId; -using chunks_test_util::performRandomChunkOperations; - namespace { -PseudoRandom _random{SecureRandom().nextInt64()}; - const ShardId kThisShard("thisShard"); const NamespaceString kNss("TestDB", "TestColl"); @@ -163,57 +144,64 @@ void assertCorrectBytesWritten(const RoutingTableHistory& rt, }); } +/** + * Test fixture for tests that need to start with a fresh routing table with + * only a single chunk in it, with bytes already written to that chunk object. + */ class RoutingTableHistoryTest : public unittest::Test { public: - const KeyPattern& getShardKeyPattern() const { - return _shardKeyPattern; + void setUp() override { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + ChunkVersion version{1, 0, epoch, timestamp}; + + auto initChunk = + ChunkType{uuid, + ChunkRange{_shardKeyPattern.globalMin(), _shardKeyPattern.globalMax()}, + version, + kThisShard}; + + _rt.emplace(RoutingTableHistory::makeNew(kNss, + uuid, + _shardKeyPattern, + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + {initChunk})); + ASSERT_EQ(_rt->numChunks(), 1ull); + + // Should only be one + _rt->forEachChunk([&](const auto& chunkInfo) { + auto writesTracker = chunkInfo->getWritesTracker(); + writesTracker->addBytesWritten(_bytesInOriginalChunk); + return true; + }); } - const OID& collEpoch() const { - return _epoch; + const KeyPattern& getShardKeyPattern() const { + return _shardKeyPattern; } - const Timestamp& collTimestamp() const { - return _collTimestamp; + uint64_t getBytesInOriginalChunk() const { + return _bytesInOriginalChunk; } - const UUID& collUUID() const { - return _collUUID; + const RoutingTableHistory& getInitialRoutingTable() const { + return *_rt; } - std::vector<ChunkType> genRandomChunkVector(size_t minNumChunks = 1, - size_t maxNumChunks = 30) const { - return chunks_test_util::genRandomChunkVector( - _collUUID, _epoch, _collTimestamp, maxNumChunks, minNumChunks); - } +private: + uint64_t _bytesInOriginalChunk{4ull}; - RoutingTableHistory makeNewRt(const std::vector<ChunkType>& chunks) const { - const auto chunkBucketSize = llround(_random.nextInt64(chunks.size() * 1.2)) + 1; - LOGV2(7162710, - "Creating new RoutingTable", - "chunkBucketSize"_attr = chunkBucketSize, - "numChunks"_attr = chunks.size()); - RAIIServerParameterControllerForTest chunkBucketSizeParameter( - "routingTableCacheChunkBucketSize", chunkBucketSize); - return RoutingTableHistory::makeNew(kNss, - _collUUID, - _shardKeyPattern, - nullptr, - false, - _epoch, - _collTimestamp, - boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - true, - chunks); - } + boost::optional<RoutingTableHistory> _rt; -protected: - KeyPattern _shardKeyPattern{chunks_test_util::kShardKeyPattern}; - const OID _epoch{OID::gen()}; - const Timestamp _collTimestamp{1, 1}; - const UUID _collUUID{UUID::gen()}; + KeyPattern _shardKeyPattern{BSON("a" << 1)}; }; /** @@ -224,28 +212,13 @@ class RoutingTableHistoryTestThreeInitialChunks : public RoutingTableHistoryTest public: void setUp() override { RoutingTableHistoryTest::setUp(); - _initialChunkBoundaryPoints = {getShardKeyPattern().globalMin(), BSON("a" << 10), BSON("a" << 20), getShardKeyPattern().globalMax()}; - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; - auto chunks = - genChunkVector(collUUID(), _initialChunkBoundaryPoints, version, 1 /* numShards */); - - _rt.emplace(makeNewRt(chunks)); - + _rt.emplace(splitChunk(RoutingTableHistoryTest::getInitialRoutingTable(), + _initialChunkBoundaryPoints)); ASSERT_EQ(_rt->numChunks(), 3ull); - - _rt->forEachChunk([&](const auto& chunkInfo) { - auto writesTracker = chunkInfo->getWritesTracker(); - writesTracker->addBytesWritten(_bytesInOriginalChunk); - return true; - }); - } - - uint64_t getBytesInOriginalChunk() const { - return _bytesInOriginalChunk; } const RoutingTableHistory& getInitialRoutingTable() const { @@ -257,407 +230,24 @@ public: } private: - uint64_t _bytesInOriginalChunk{4ull}; - boost::optional<RoutingTableHistory> _rt; std::vector<BSONObj> _initialChunkBoundaryPoints; }; -/* - * Test creation of a Routing Table with randomly generated chunks - */ -TEST_F(RoutingTableHistoryTest, RandomCreateBasic) { - const auto chunks = genRandomChunkVector(); - const auto expectedShardVersions = calculateShardVersions(chunks); - const auto expectedCollVersion = calculateCollVersion(expectedShardVersions); - - // Create a new routing table from the randomly generated chunks - auto rt = makeNewRt(chunks); - - // Checks basic getter of routing table return correct values - ASSERT_EQ(kNss, rt.nss()); - ASSERT_EQ(ShardKeyPattern(getShardKeyPattern()).toString(), rt.getShardKeyPattern().toString()); - ASSERT_EQ(chunks.size(), rt.numChunks()); - - // Check that chunks have correct info - size_t i = 0; - rt.forEachChunk([&](const auto& chunkInfo) { - assertEqualChunkInfo(ChunkInfo{chunks[i++]}, *chunkInfo); - return true; - }); - ASSERT_EQ(i, chunks.size()); - - // Checks collection version is correct - ASSERT_EQ(expectedCollVersion, rt.getVersion()); - - // Checks version for each chunk - for (const auto& [shardId, shardVersion] : expectedShardVersions) { - ASSERT_EQ(shardVersion, rt.getVersion(shardId)); - } - - ASSERT_EQ(expectedShardVersions.size(), rt.getNShardsOwningChunks()); - - std::set<ShardId> expectedShardIds; - for (const auto& [shardId, shardVersion] : expectedShardVersions) { - expectedShardIds.insert(shardId); - } - std::set<ShardId> shardIds; - rt.getAllShardIds(&shardIds); - ASSERT(expectedShardIds == shardIds); - - // Validate all shard maxValidAfter - const auto expectedShardsMaxValidAfter = calculateShardsMaxValidAfter(chunks); - const auto expectedMaxValidAfter = [&](const ShardId& shard) { - auto it = expectedShardsMaxValidAfter.find(shard); - return it != expectedShardsMaxValidAfter.end() ? it->second : Timestamp{0, 0}; - }; - for (const auto& [shardId, _] : expectedShardVersions) { - ASSERT_GTE(rt.getMaxValidAfter(shardId), expectedMaxValidAfter(shardId)); - } - ASSERT_EQ(rt.getMaxValidAfter(ShardId{"shard-without-chunks"}), (Timestamp{0, 0})); -} - -/* - * Test that creation of Routing Table with chunks that do not cover the entire shard key space - * fails. - * - * The gap is produced by removing a random chunks from the randomly generated chunk list. Thus it - * also cover the case for which min/max key is missing. - */ -TEST_F(RoutingTableHistoryTest, RandomCreateWithMissingChunkFail) { - auto chunks = genRandomChunkVector(2 /*minNumChunks*/); - - // Remove one random chunk to simulate a gap in the shardkey - chunks.erase(chunks.begin() + _random.nextInt64(chunks.size())); - - // Create a new routing table from the randomly generated chunks - ASSERT_THROWS_CODE(makeNewRt(chunks), DBException, ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Test that creation of Routing Table with chunks that do not cover the entire shard key space - * fails. - * - * The gap is produced by shrinking the range of a random chunk. - */ -TEST_F(RoutingTableHistoryTest, RandomCreateWithChunkGapFail) { - auto chunks = genRandomChunkVector(2 /*minNumChunks*/); - - auto& shrinkedChunk = chunks.at(_random.nextInt64(chunks.size())); - auto intermediateKey = - calculateIntermediateShardKey(shrinkedChunk.getMin(), shrinkedChunk.getMax()); - if (_random.nextInt64(2)) { - // Shrink right bound - shrinkedChunk.setMax(intermediateKey); - } else { - // Shrink left bound - shrinkedChunk.setMin(intermediateKey); - } - - // Create a new routing table from the randomly generated chunks - ASSERT_THROWS_CODE(makeNewRt(chunks), DBException, ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Updating ChunkMap with gaps must fail - */ -TEST_F(RoutingTableHistoryTest, RandomUpdateWithChunkGapFail) { - auto chunks = genRandomChunkVector(); - - // Create a new routing table from the randomly generated chunks - auto rt = makeNewRt(chunks); - auto collVersion = rt.getVersion(); - - auto shrinkedChunk = chunks.at(_random.nextInt64(chunks.size())); - auto intermediateKey = - calculateIntermediateShardKey(shrinkedChunk.getMin(), shrinkedChunk.getMax()); - if (_random.nextInt64(2)) { - // Shrink right bound - shrinkedChunk.setMax(intermediateKey); - } else { - // Shrink left bound - shrinkedChunk.setMin(intermediateKey); - } - - // Bump chunk version - collVersion.incMajor(); - shrinkedChunk.setVersion(collVersion); - - ASSERT_THROWS_CODE(rt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - true, - {std::move(shrinkedChunk)}), - AssertionException, - ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Creating a Routing Table with overlapping chunks must fail. - */ -TEST_F(RoutingTableHistoryTest, RandomCreateWithChunkOverlapFail) { - auto chunks = genRandomChunkVector(2 /* minNumChunks */); - - auto chunkToExtendIt = chunks.begin() + _random.nextInt64(chunks.size()); - - const auto canExtendLeft = chunkToExtendIt > chunks.begin(); - const auto extendRight = - !canExtendLeft || ((chunkToExtendIt < std::prev(chunks.end())) && _random.nextInt64(2)); - const auto extendLeft = !extendRight; - if (extendRight) { - // extend right bound - chunkToExtendIt->setMax(calculateIntermediateShardKey(chunkToExtendIt->getMax(), - std::next(chunkToExtendIt)->getMax(), - 0.0 /* minKeyProb */, - 0.1 /* maxKeyProb */)); - auto newVersion = chunkToExtendIt->getVersion(); - newVersion.incMajor(); - std::next(chunkToExtendIt)->setVersion(newVersion); - } - - if (extendLeft) { - invariant(canExtendLeft); - // extend left bound - chunkToExtendIt->setMin(calculateIntermediateShardKey(std::prev(chunkToExtendIt)->getMin(), - chunkToExtendIt->getMin(), - 0.1 /* minKeyProb */, - 0.0 /* maxKeyProb */)); - auto newVersion = chunkToExtendIt->getVersion(); - newVersion.incMajor(); - std::prev(chunkToExtendIt)->setVersion(newVersion); - } - - // Create a new routing table from the randomly generated chunks - ASSERT_THROWS_CODE(makeNewRt(chunks), DBException, ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Updating a ChunkMap with overlapping chunks must fail. - */ -TEST_F(RoutingTableHistoryTest, RandomUpdateWithChunkOverlapFail) { - auto chunks = genRandomChunkVector(2 /* minNumChunks */); - - // Create a new routing table from the randomly generated chunks - auto rt = makeNewRt(chunks); - auto collVersion = rt.getVersion(); - - auto chunkToExtendIt = chunks.begin() + _random.nextInt64(chunks.size()); - - const auto canExtendLeft = chunkToExtendIt > chunks.begin(); - const auto extendRight = - !canExtendLeft || (chunkToExtendIt < std::prev(chunks.end()) && _random.nextInt64(2)); - const auto extendLeft = !extendRight; - if (extendRight) { - // extend right bound - chunkToExtendIt->setMax(calculateIntermediateShardKey( - chunkToExtendIt->getMax(), std::next(chunkToExtendIt)->getMax())); - } - - if (extendLeft) { - invariant(canExtendLeft); - // extend left bound - chunkToExtendIt->setMin(calculateIntermediateShardKey(std::prev(chunkToExtendIt)->getMin(), - chunkToExtendIt->getMin())); - } - - // Bump chunk version - collVersion.incMajor(); - chunkToExtendIt->setVersion(collVersion); - - ASSERT_THROWS_CODE(rt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - true, - {*chunkToExtendIt}), - AssertionException, - ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Creating a Routing Table with wrong min key must fail. - */ -TEST_F(RoutingTableHistoryTest, RandomCreateWrongMinFail) { - auto chunks = genRandomChunkVector(); - - chunks.begin()->setMin(BSON("a" << std::numeric_limits<int64_t>::min())); - - // Create a new routing table from the randomly generated chunks - ASSERT_THROWS_CODE(makeNewRt(chunks), DBException, ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Creating a Routing Table with wrong max key must fail. - */ -TEST_F(RoutingTableHistoryTest, RandomCreateWrongMaxFail) { - auto chunks = genRandomChunkVector(); - - chunks.begin()->setMax(BSON("a" << std::numeric_limits<int64_t>::max())); - - // Create a new routing table from the randomly generated chunks - ASSERT_THROWS_CODE(makeNewRt(chunks), DBException, ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Creating a Routing Table with mismatching epoch must fail. - */ -TEST_F(RoutingTableHistoryTest, RandomCreateMismatchingTimestampFail) { - auto chunks = genRandomChunkVector(); - - // Change epoch on a random chunk - auto chunkIt = chunks.begin() + _random.nextInt64(chunks.size()); - const auto& oldVersion = chunkIt->getVersion(); - - const Timestamp wrongTimestamp{Date_t::now()}; - ChunkVersion newVersion{ - oldVersion.majorVersion(), oldVersion.minorVersion(), collEpoch(), wrongTimestamp}; - chunkIt->setVersion(newVersion); - - // Create a new routing table from the randomly generated chunks - ASSERT_THROWS_CODE(makeNewRt(chunks), DBException, ErrorCodes::ConflictingOperationInProgress); -} - -/* - * Updating a Routing Table with mismatching Timestamp must fail. - */ -TEST_F(RoutingTableHistoryTest, RandomUpdateMismatchingTimestampFail) { - auto chunks = genRandomChunkVector(); - - // Create a new routing table from the randomly generated chunks - auto rt = makeNewRt(chunks); - - // Change epoch on a random chunk - auto chunkIt = chunks.begin() + _random.nextInt64(chunks.size()); - const auto& oldVersion = chunkIt->getVersion(); - const Timestamp wrongTimestamp{Date_t::now()}; - ChunkVersion newVersion{ - oldVersion.majorVersion(), oldVersion.minorVersion(), collEpoch(), wrongTimestamp}; - chunkIt->setVersion(newVersion); - - ASSERT_THROWS_CODE(rt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - true, - {*chunkIt}), - AssertionException, - ErrorCodes::ConflictingOperationInProgress); -} - - -/* - * Test update of the Routing Table with randomly generated changed chunks. - */ -TEST_F(RoutingTableHistoryTest, RandomUpdate) { - auto initialChunks = genRandomChunkVector(); - - const auto initialShardVersions = calculateShardVersions(initialChunks); - const auto initialCollVersion = calculateCollVersion(initialShardVersions); - - // Create a new routing table from the randomly generated initialChunks - auto initialRt = makeNewRt(initialChunks); - - auto chunks = initialChunks; - const auto maxNumChunkOps = 2 * initialChunks.size(); - const auto numChunkOps = _random.nextInt32(maxNumChunkOps); - - performRandomChunkOperations(&chunks, numChunkOps); - - std::vector<ChunkType> updatedChunks; - for (const auto& chunk : chunks) { - if (!chunk.getVersion().isOlderOrEqualThan(initialCollVersion)) { - updatedChunks.push_back(chunk); - } - } - - const auto expectedShardVersions = calculateShardVersions(chunks); - const auto expectedCollVersion = calculateCollVersion(expectedShardVersions); - - auto rt = initialRt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - true, - updatedChunks); - - // Checks basic getter of routing table return correct values - ASSERT_EQ(kNss, rt.nss()); - ASSERT_EQ(ShardKeyPattern(getShardKeyPattern()).toString(), rt.getShardKeyPattern().toString()); - ASSERT_EQ(chunks.size(), rt.numChunks()); - - // Check that chunks have correct info - size_t i = 0; - rt.forEachChunk([&](const auto& chunkInfo) { - assertEqualChunkInfo(ChunkInfo{chunks[i++]}, *chunkInfo); - return true; - }); - ASSERT_EQ(i, chunks.size()); - - // Checks collection version is correct - ASSERT_EQ(expectedCollVersion, rt.getVersion()); - - // Checks version for each shard - for (const auto& [shardId, shardVersion] : expectedShardVersions) { - ASSERT_EQ(shardVersion, rt.getVersion(shardId)); - } - - ASSERT_EQ(expectedShardVersions.size(), rt.getNShardsOwningChunks()); - - std::set<ShardId> expectedShardIds; - for (const auto& [shardId, shardVersion] : expectedShardVersions) { - expectedShardIds.insert(shardId); - } - std::set<ShardId> shardIds; - rt.getAllShardIds(&shardIds); - ASSERT(expectedShardIds == shardIds); - - // Validate all shard maxValidAfter - const auto expectedShardsMaxValidAfter = calculateShardsMaxValidAfter(chunks); - const auto expectedMaxValidAfter = [&](const ShardId& shard) { - auto it = expectedShardsMaxValidAfter.find(shard); - return it != expectedShardsMaxValidAfter.end() ? it->second : Timestamp{0, 0}; - }; - for (const auto& [shardId, _] : expectedShardVersions) { - ASSERT_GTE(rt.getMaxValidAfter(shardId), expectedMaxValidAfter(shardId)) - << "For shardid " << shardId; - } - ASSERT_EQ(rt.getMaxValidAfter(ShardId{"shard-without-chunks"}), (Timestamp{0, 0})); -} - TEST_F(RoutingTableHistoryTest, SplittingOnlyChunkCopiesBytesWrittenToAllSubchunks) { - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; - - const ChunkType initialChunk{ - collUUID(), - ChunkRange{getShardKeyPattern().globalMin(), getShardKeyPattern().globalMax()}, - version, - kThisShard}; + auto minKey = BSON("a" << 10); + auto maxKey = BSON("a" << 20); + auto newChunkBoundaryPoints = { + getShardKeyPattern().globalMin(), minKey, maxKey, getShardKeyPattern().globalMax()}; - auto rt = makeNewRt({initialChunk}); - ASSERT_EQ(1, rt.numChunks()); - - // Set the 4 written bytes in each chunk - const size_t bytesInOriginalChunk{4}; - rt.forEachChunk([&](const auto& chunkInfo) { - chunkInfo->getWritesTracker()->addBytesWritten(bytesInOriginalChunk); - return true; - }); - - version.incMinor(); - auto newChunks = genChunkVector(collUUID(), - {getShardKeyPattern().globalMin(), - BSON("a" << 10), - BSON("a" << 20), - getShardKeyPattern().globalMax()}, - version, - 1 /*numShards*/); - auto newRt = rt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - true, - newChunks); - ASSERT_EQ(3, newRt.numChunks()); + auto rt = splitChunk(getInitialRoutingTable(), newChunkBoundaryPoints); + ASSERT_EQ(rt.numChunks(), 3ull); rt.forEachChunk([&](const auto& chunkInfo) { - ASSERT_EQ(bytesInOriginalChunk, chunkInfo->getWritesTracker()->getBytesWritten()); + auto writesTracker = chunkInfo->getWritesTracker(); + auto bytesWritten = writesTracker->getBytesWritten(); + ASSERT_EQ(bytesWritten, getBytesInOriginalChunk()); return true; }); } @@ -738,124 +328,104 @@ TEST_F(RoutingTableHistoryTestThreeInitialChunks, expectedBytesInChunksNotSplit); } -TEST_F(RoutingTableHistoryTest, AllowMigrationFlag) { - auto chunks = genRandomChunkVector(); - - auto makeUpdatedChunk = [&](const ChunkVersion& oldVersion) { - auto updatedChunk = chunks[_random.nextInt64(chunks.size())]; - updatedChunk.setVersion({oldVersion.majorVersion() + 1, - oldVersion.minorVersion(), - collEpoch(), - collTimestamp()}); - return updatedChunk; - }; - - for (auto initialAllowMigrationsValue : std::vector<bool>{false, true}) { - - auto allowMigrationsValue = initialAllowMigrationsValue; - auto rt = RoutingTableHistory::makeNew(kNss, - collUUID(), - getShardKeyPattern(), - nullptr, - false, - collEpoch(), - collTimestamp(), - boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - allowMigrationsValue, - chunks); - ASSERT_EQ(allowMigrationsValue, rt.allowMigrations()); - - // Create an updated routing table with flipped allowMigration flag - allowMigrationsValue = !allowMigrationsValue; - rt = rt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - allowMigrationsValue, - {makeUpdatedChunk(rt.getVersion())}); - ASSERT_EQ(allowMigrationsValue, rt.allowMigrations()); - - // Change back the allow migration flag to the original value - allowMigrationsValue = !allowMigrationsValue; - rt = rt.makeUpdated(boost::none /* timeseriesFields */, - boost::none /* reshardingFields */, - boost::none /* maxChunkSizeBytes */, - allowMigrationsValue, - {makeUpdatedChunk(rt.getVersion())}); - ASSERT_EQ(allowMigrationsValue, rt.allowMigrations()); - } -} - TEST_F(RoutingTableHistoryTest, TestSplits) { - ChunkVersion version{1, 0, collEpoch(), collTimestamp()}; + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + ChunkVersion version{1, 0, epoch, timestamp}; auto chunkAll = - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), getShardKeyPattern().globalMax()}, version, kThisShard}; - auto rt = makeNewRt({chunkAll}); + auto rt = RoutingTableHistory::makeNew(kNss, + uuid, + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + {chunkAll}); std::vector<ChunkType> chunks1 = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}}; auto rt1 = rt.makeUpdated(boost::none /* timeseriesFields */, boost::none, boost::none, true, chunks1); - auto v1 = ChunkVersion{2, 2, collEpoch(), collTimestamp()}; + auto v1 = ChunkVersion{2, 2, epoch, timestamp}; ASSERT_EQ(v1, rt1.getVersion(kThisShard)); std::vector<ChunkType> chunks2 = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << -1)}, - ChunkVersion{3, 1, collEpoch(), collTimestamp()}, + ChunkVersion{3, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << -1), BSON("a" << 0)}, - ChunkVersion{3, 2, collEpoch(), collTimestamp()}, + ChunkVersion{3, 2, epoch, timestamp}, kThisShard}}; auto rt2 = rt1.makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, chunks2); - auto v2 = ChunkVersion{3, 2, collEpoch(), collTimestamp()}; + auto v2 = ChunkVersion{3, 2, epoch, timestamp}; ASSERT_EQ(v2, rt2.getVersion(kThisShard)); } TEST_F(RoutingTableHistoryTest, TestReplaceEmptyChunk) { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + std::vector<ChunkType> initialChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), getShardKeyPattern().globalMax()}, - ChunkVersion{1, 0, collEpoch(), collTimestamp()}, + ChunkVersion{1, 0, epoch, timestamp}, kThisShard}}; - auto rt = makeNewRt(initialChunks); + auto rt = RoutingTableHistory::makeNew(kNss, + uuid, + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + initialChunks); ASSERT_EQ(rt.numChunks(), 1); std::vector<ChunkType> changedChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}}; auto rt1 = rt.makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, changedChunks); - auto v1 = ChunkVersion{2, 2, collEpoch(), collTimestamp()}; + auto v1 = ChunkVersion{2, 2, epoch, timestamp}; ASSERT_EQ(v1, rt1.getVersion(kThisShard)); ASSERT_EQ(rt1.numChunks(), 2); @@ -874,184 +444,258 @@ TEST_F(RoutingTableHistoryTest, TestReplaceEmptyChunk) { } TEST_F(RoutingTableHistoryTest, TestUseLatestVersions) { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + std::vector<ChunkType> initialChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), getShardKeyPattern().globalMax()}, - ChunkVersion{1, 0, collEpoch(), collTimestamp()}, + ChunkVersion{1, 0, epoch, timestamp}, kThisShard}}; - auto rt = makeNewRt(initialChunks); + auto rt = RoutingTableHistory::makeNew(kNss, + uuid, + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + initialChunks); ASSERT_EQ(rt.numChunks(), 1); std::vector<ChunkType> changedChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), getShardKeyPattern().globalMax()}, - ChunkVersion{1, 0, collEpoch(), collTimestamp()}, + ChunkVersion{1, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}}; auto rt1 = rt.makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, changedChunks); - auto v1 = ChunkVersion{2, 2, collEpoch(), collTimestamp()}; + auto v1 = ChunkVersion{2, 2, epoch, timestamp}; ASSERT_EQ(v1, rt1.getVersion(kThisShard)); ASSERT_EQ(rt1.numChunks(), 2); } TEST_F(RoutingTableHistoryTest, TestOutOfOrderVersion) { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + std::vector<ChunkType> initialChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}}; - auto rt = makeNewRt(initialChunks); + auto rt = RoutingTableHistory::makeNew(kNss, + uuid, + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + initialChunks); ASSERT_EQ(rt.numChunks(), 2); std::vector<ChunkType> changedChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), getShardKeyPattern().globalMax()}, - ChunkVersion{3, 0, collEpoch(), collTimestamp()}, + ChunkVersion{3, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, - ChunkVersion{3, 1, collEpoch(), collTimestamp()}, + ChunkVersion{3, 1, epoch, timestamp}, kThisShard}}; auto rt1 = rt.makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, changedChunks); - auto v1 = ChunkVersion{3, 1, collEpoch(), collTimestamp()}; + auto v1 = ChunkVersion{3, 1, epoch, timestamp}; ASSERT_EQ(v1, rt1.getVersion(kThisShard)); ASSERT_EQ(rt1.numChunks(), 2); auto chunk1 = rt1.findIntersectingChunk(BSON("a" << 0)); - ASSERT_EQ(chunk1->getLastmod(), ChunkVersion(3, 0, collEpoch(), collTimestamp())); + ASSERT_EQ(chunk1->getLastmod(), ChunkVersion(3, 0, epoch, timestamp)); ASSERT_EQ(chunk1->getMin().woCompare(BSON("a" << 0)), 0); ASSERT_EQ(chunk1->getMax().woCompare(getShardKeyPattern().globalMax()), 0); } TEST_F(RoutingTableHistoryTest, TestMergeChunks) { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + std::vector<ChunkType> initialChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 0), BSON("a" << 10)}, - ChunkVersion{2, 0, collEpoch(), collTimestamp()}, + ChunkVersion{2, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 0)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 10), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}}; - auto rt = makeNewRt(initialChunks); - + auto rt = RoutingTableHistory::makeNew(kNss, + uuid, + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none, + boost::none /* timeseriesFields */, + boost::none /* chunkSizeBytes */, + true, + initialChunks); ASSERT_EQ(rt.numChunks(), 3); - ASSERT_EQ(rt.getVersion(), ChunkVersion(2, 2, collEpoch(), collTimestamp())); + ASSERT_EQ(rt.getVersion(), ChunkVersion(2, 2, epoch, timestamp)); std::vector<ChunkType> changedChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 10), getShardKeyPattern().globalMax()}, - ChunkVersion{3, 0, collEpoch(), collTimestamp()}, + ChunkVersion{3, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 10)}, - ChunkVersion{3, 1, collEpoch(), collTimestamp()}, + ChunkVersion{3, 1, epoch, timestamp}, kThisShard}}; auto rt1 = rt.makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, changedChunks); - auto v1 = ChunkVersion{3, 1, collEpoch(), collTimestamp()}; + auto v1 = ChunkVersion{3, 1, epoch, timestamp}; ASSERT_EQ(v1, rt1.getVersion(kThisShard)); ASSERT_EQ(rt1.numChunks(), 2); } TEST_F(RoutingTableHistoryTest, TestMergeChunksOrdering) { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + std::vector<ChunkType> initialChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << -10), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 0, collEpoch(), collTimestamp()}, + ChunkVersion{2, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << -500)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << -500), BSON("a" << -10)}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}}; - auto rt = makeNewRt(initialChunks); + auto rt = RoutingTableHistory::makeNew(kNss, + uuid, + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + initialChunks); ASSERT_EQ(rt.numChunks(), 3); - ASSERT_EQ(rt.getVersion(), ChunkVersion(2, 2, collEpoch(), collTimestamp())); + ASSERT_EQ(rt.getVersion(), ChunkVersion(2, 2, epoch, timestamp)); std::vector<ChunkType> changedChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << -500), BSON("a" << -10)}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << -10)}, - ChunkVersion{3, 1, collEpoch(), collTimestamp()}, + ChunkVersion{3, 1, epoch, timestamp}, kThisShard}}; auto rt1 = rt.makeUpdated( boost::none /* timeseriesFields */, boost::none, boost::none, true, changedChunks); - auto v1 = ChunkVersion{3, 1, collEpoch(), collTimestamp()}; + auto v1 = ChunkVersion{3, 1, epoch, timestamp}; ASSERT_EQ(v1, rt1.getVersion(kThisShard)); ASSERT_EQ(rt1.numChunks(), 2); auto chunk1 = rt1.findIntersectingChunk(BSON("a" << -500)); - ASSERT_EQ(chunk1->getLastmod(), ChunkVersion(3, 1, collEpoch(), collTimestamp())); + ASSERT_EQ(chunk1->getLastmod(), ChunkVersion(3, 1, epoch, timestamp)); ASSERT_EQ(chunk1->getMin().woCompare(getShardKeyPattern().globalMin()), 0); ASSERT_EQ(chunk1->getMax().woCompare(BSON("a" << -10)), 0); } TEST_F(RoutingTableHistoryTest, TestFlatten) { + const UUID uuid = UUID::gen(); + const OID epoch = OID::gen(); + const Timestamp timestamp(1); + std::vector<ChunkType> initialChunks = { - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 10)}, - ChunkVersion{2, 0, collEpoch(), collTimestamp()}, + ChunkVersion{2, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 10), BSON("a" << 20)}, - ChunkVersion{2, 1, collEpoch(), collTimestamp()}, + ChunkVersion{2, 1, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 20), getShardKeyPattern().globalMax()}, - ChunkVersion{2, 2, collEpoch(), collTimestamp()}, + ChunkVersion{2, 2, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), getShardKeyPattern().globalMax()}, - ChunkVersion{3, 0, collEpoch(), collTimestamp()}, + ChunkVersion{3, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{getShardKeyPattern().globalMin(), BSON("a" << 10)}, - ChunkVersion{4, 0, collEpoch(), collTimestamp()}, + ChunkVersion{4, 0, epoch, timestamp}, kThisShard}, - ChunkType{collUUID(), + ChunkType{uuid, ChunkRange{BSON("a" << 10), getShardKeyPattern().globalMax()}, - ChunkVersion{4, 1, collEpoch(), collTimestamp()}, + ChunkVersion{4, 1, epoch, timestamp}, kThisShard}, }; - auto rt = makeNewRt(initialChunks); + auto rt = RoutingTableHistory::makeNew(kNss, + UUID::gen(), + getShardKeyPattern(), + nullptr, + false, + epoch, + timestamp, + boost::none /* timeseriesFields */, + boost::none, + boost::none /* chunkSizeBytes */, + true, + initialChunks); ASSERT_EQ(rt.numChunks(), 2); - ASSERT_EQ(rt.getVersion(), ChunkVersion(4, 1, collEpoch(), collTimestamp())); + ASSERT_EQ(rt.getVersion(), ChunkVersion(4, 1, epoch, timestamp)); auto chunk1 = rt.findIntersectingChunk(BSON("a" << 0)); - ASSERT_EQ(chunk1->getLastmod(), ChunkVersion(4, 0, collEpoch(), collTimestamp())); + ASSERT_EQ(chunk1->getLastmod(), ChunkVersion(4, 0, epoch, timestamp)); ASSERT_EQ(chunk1->getMin().woCompare(getShardKeyPattern().globalMin()), 0); ASSERT_EQ(chunk1->getMax().woCompare(BSON("a" << 10)), 0); } diff --git a/src/mongo/s/service_entry_point_mongos.cpp b/src/mongo/s/service_entry_point_mongos.cpp index d704c3c1354..12ebf1299bd 100644 --- a/src/mongo/s/service_entry_point_mongos.cpp +++ b/src/mongo/s/service_entry_point_mongos.cpp @@ -219,7 +219,7 @@ void ServiceEntryPointMongos::onClientConnect(Client* client) { } } -void ServiceEntryPointMongos::onClientDisconnect(Client* client) try { +void ServiceEntryPointMongos::onClientDisconnect(Client* client) { if (load_balancer_support::isFromLoadBalancer(client)) { _loadBalancedConnections.decrement(); @@ -259,11 +259,6 @@ void ServiceEntryPointMongos::onClientDisconnect(Client* client) try { "aborting in-progress transaction because load-balanced client disconnected"}); } } -} catch (const DBException& ex) { - LOGV2_DEBUG(8969800, - 2, - "Encountered error while performing client connection cleanup", - "error"_attr = ex.toStatus()); } void ServiceEntryPointMongos::appendStats(BSONObjBuilder* bob) const { diff --git a/src/mongo/s/sessions_collection_sharded_test.cpp b/src/mongo/s/sessions_collection_sharded_test.cpp index b8cd82617e5..d82a4edfbfa 100644 --- a/src/mongo/s/sessions_collection_sharded_test.cpp +++ b/src/mongo/s/sessions_collection_sharded_test.cpp @@ -34,7 +34,6 @@ #include "mongo/client/remote_command_targeter_mock.h" #include "mongo/db/commands.h" #include "mongo/db/logical_session_id.h" -#include "mongo/db/query/cursor_response.h" #include "mongo/s/catalog/type_shard.h" #include "mongo/s/catalog_cache_test_fixture.h" #include "mongo/s/client/shard_registry.h" diff --git a/src/mongo/s/shard_id_test.cpp b/src/mongo/s/shard_id_test.cpp index 1182d429e26..08765eeff91 100644 --- a/src/mongo/s/shard_id_test.cpp +++ b/src/mongo/s/shard_id_test.cpp @@ -88,8 +88,8 @@ TEST(ShardId, Compare) { ShardId sa1(a1); ShardId sb(b); ASSERT_EQUALS(sa.compare(sa1), a.compare(a1)); - ASSERT_EQUALS(sb.compare(sa1) > 0, b.compare(a1) > 0); - ASSERT_EQUALS(sa.compare(sb) < 0, a.compare(b) < 0); + ASSERT_EQUALS(sb.compare(sa1), b.compare(a1)); + ASSERT_EQUALS(sa.compare(sb), a.compare(b)); } TEST(ShardId, Equals) { diff --git a/src/mongo/s/shard_key_pattern.cpp b/src/mongo/s/shard_key_pattern.cpp index 9e6f3244dcd..c8fb1b7a84c 100644 --- a/src/mongo/s/shard_key_pattern.cpp +++ b/src/mongo/s/shard_key_pattern.cpp @@ -33,7 +33,6 @@ #include <vector> -#include "mongo/bson/simple_bsonelement_comparator.h" #include "mongo/db/field_ref.h" #include "mongo/db/field_ref_set.h" #include "mongo/db/hasher.h" @@ -86,20 +85,10 @@ std::vector<std::unique_ptr<FieldRef>> parseShardKeyPattern(const BSONObj& keyPa // Empty parts of the path, ".."? for (size_t i = 0; i < newFieldRef->numParts(); ++i) { - const StringData part = newFieldRef->getPart(i); - uassert(ErrorCodes::BadValue, str::stream() << "Field " << patternEl.fieldNameStringData() << " contains empty parts", - !part.empty()); - - // Reject a shard key that has a field name that starts with '$' or contains parts that - // start with '$' unless the part is a DBRef (i.e. is equal to '$id', '$db' or '$ref'). - uassert(ErrorCodes::BadValue, - str::stream() << "Field " << patternEl.fieldNameStringData() - << " contains parts that start with '$'", - !part.startsWith("$") || - (i != 0 && (part == "$db" || part == "$id" || part == "$ref"))); + !newFieldRef->getPart(i).empty()); } // Numeric and ascending (1.0), or "hashed" with exactly hashed field. @@ -299,7 +288,7 @@ bool ShardKeyPattern::isShardKey(const BSONObj& shardKey) const { } bool ShardKeyPattern::isExtendedBy(const ShardKeyPattern& newShardKeyPattern) const { - return toBSON().isPrefixOf(newShardKeyPattern.toBSON(), SimpleBSONElementComparator::kInstance); + return toBSON().isFieldNamePrefixOf(newShardKeyPattern.toBSON()); } BSONObj ShardKeyPattern::normalizeShardKey(const BSONObj& shardKey) const { @@ -555,12 +544,12 @@ BSONObj ShardKeyPattern::extractShardKeyFromQuery(const CanonicalQuery& query) c return keyBuilder.obj(); } -bool ShardKeyPattern::isIndexUniquenessCompatible(const BSONObj& indexPattern) const { - if (!indexPattern.isEmpty() && indexPattern.firstElementFieldName() == kIdField) { +bool ShardKeyPattern::isUniqueIndexCompatible(const BSONObj& uniqueIndexPattern) const { + if (!uniqueIndexPattern.isEmpty() && uniqueIndexPattern.firstElementFieldName() == kIdField) { return true; } - return _keyPattern.toBSON().isFieldNamePrefixOf(indexPattern); + return _keyPattern.toBSON().isFieldNamePrefixOf(uniqueIndexPattern); } BoundList ShardKeyPattern::flattenBounds(const IndexBounds& indexBounds) const { diff --git a/src/mongo/s/shard_key_pattern.h b/src/mongo/s/shard_key_pattern.h index 70f5aff336d..545d1906e31 100644 --- a/src/mongo/s/shard_key_pattern.h +++ b/src/mongo/s/shard_key_pattern.h @@ -274,37 +274,36 @@ public: BSONObj extractShardKeyFromQuery(const CanonicalQuery& query) const; /** - * Returns true if the shard key pattern can ensure that the index uniqueness is respected - * across all shards. + * Returns true if the shard key pattern can ensure that the unique index pattern is + * respected across all shards. * * Primarily this just checks whether the shard key pattern field names are equal to or a - * prefix of the 'unique' or 'prepareUnique' index pattern field names. Since documents with the - * same fields in the shard key pattern are guaranteed to go to the same shard, and all - * documents must contain the full shard key, an index with {unique: true} or {prepareUnique: - * true} and a shard key pattern prefix can be sure when resolving duplicates that documents on - * other shards will have different shard keys, and so are not duplicates. + * prefix of the unique index pattern field names. Since documents with the same fields in + * the shard key pattern are guaranteed to go to the same shard, and all documents must + * contain the full shard key, a unique index with a shard key pattern prefix can be sure + * when resolving duplicates that documents on other shards will have different shard keys, + * and so are not duplicates. * * Hashed shard key patterns are similar to ordinary patterns in that they guarantee similar * shard keys go to the same shard. * * Examples: - * shard key {a : 1} is compatible with a unique/prepareUnique index on {_id : 1} - * shard key {a : 1} is compatible with a unique/prepareUnique index on {a : 1, b : 1} - * shard key {a : 1} is compatible with a unique/prepareUnique index on {a : -1, b : 1} - * shard key {a : "hashed"} is compatible with a unique/prepareUnique index on {a : 1} - * shard key {a : 1} is not compatible with a unique/prepareUnique index on {b : 1} - * shard key {a : "hashed", b : 1} is not compatible with unique/prepareUnique index on - * {b : 1} + * shard key {a : 1} is compatible with a unique index on {_id : 1} + * shard key {a : 1} is compatible with a unique index on {a : 1 , b : 1} + * shard key {a : 1} is compatible with a unique index on {a : -1 , b : 1 } + * shard key {a : "hashed"} is compatible with a unique index on {a : 1} + * shard key {a : 1} is not compatible with a unique index on {b : 1} + * shard key {a : "hashed" , b : 1 } is not compatible with unique index on { b : 1 } * * All unique index patterns starting with _id are assumed to be enforceable by the fact * that _ids must be unique, and so all unique _id prefixed indexes are compatible with * any shard key pattern. * - * NOTE: We assume 'indexPattern' is a valid unique/prepareUnique index pattern - a pattern like - * { k : "hashed" } is not capable of being a unique/prepareUnique index and is an invalid - * argument to this method. + * NOTE: We assume 'uniqueIndexPattern' is a valid unique index pattern - a pattern like + * { k : "hashed" } is not capable of being a unique index and is an invalid argument to + * this method. */ - bool isIndexUniquenessCompatible(const BSONObj& indexPattern) const; + bool isUniqueIndexCompatible(const BSONObj& uniqueIndexPattern) const; /** * Return an ordered list of bounds generated using this KeyPattern and the diff --git a/src/mongo/s/shard_key_pattern_test.cpp b/src/mongo/s/shard_key_pattern_test.cpp index e001e5cb55e..2965b5d6222 100644 --- a/src/mongo/s/shard_key_pattern_test.cpp +++ b/src/mongo/s/shard_key_pattern_test.cpp @@ -78,7 +78,6 @@ repl::OplogEntry makeOplogEntry(repl::OpTime opTime, nss, // namespace boost::none, // uuid boost::none, // fromMigrate - boost::none, // checkExistenceForDiffInsert repl::OplogEntry::kOplogVersion, // version oField, // o o2Field, // o2 @@ -100,7 +99,6 @@ TEST_F(ShardKeyPatternTest, SingleFieldShardKeyPatternsValidityCheck) { ShardKeyPattern s3(BSON("a" << (long long)1L)); ShardKeyPattern s4(BSON("a" << "hashed")); - ShardKeyPattern s5(BSON("a$" << 1)); ASSERT_THROWS(ShardKeyPattern(BSONObj()), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << -1)), DBException); @@ -113,21 +111,12 @@ TEST_F(ShardKeyPatternTest, SingleFieldShardKeyPatternsValidityCheck) { DBException); ASSERT_THROWS(ShardKeyPattern(BSON("" << 1)), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("." << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("$" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("$a" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("$**" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("$id" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("$db" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("$ref" << 1)), DBException); } TEST_F(ShardKeyPatternTest, CompositeShardKeyPatternsValidityCheck) { ShardKeyPattern s1(BSON("a" << 1 << "b" << 1)); ShardKeyPattern s2(BSON("a" << 1.0f << "b" << 1.0)); ShardKeyPattern s3(BSON("a" << 1 << "b" << 1.0 << "c" << 1.0f)); - ShardKeyPattern s4(BSON("a.$id" << 1)); - ShardKeyPattern s5(BSON("a.$db" << 1)); - ShardKeyPattern s6(BSON("a.$ref" << 1)); ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "b" << -1)), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "b" @@ -135,9 +124,6 @@ TEST_F(ShardKeyPatternTest, CompositeShardKeyPatternsValidityCheck) { DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "b." << 1.0)), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "" << 1.0)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "$" << 1.0)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "$b" << 1.0)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "$**" << 1.0)), DBException); } TEST_F(ShardKeyPatternTest, NestedShardKeyPatternsValidtyCheck) { @@ -145,7 +131,6 @@ TEST_F(ShardKeyPatternTest, NestedShardKeyPatternsValidtyCheck) { ShardKeyPattern s2(BSON("a.b.c.d" << 1.0)); ShardKeyPattern s3(BSON("a" << 1 << "c.d" << 1.0 << "e.f.g" << 1.0f)); ShardKeyPattern s4(BSON("a" << 1 << "a.b" << 1.0 << "a.b.c" << 1.0f)); - ShardKeyPattern s6(BSON("a.b$" << 1)); ASSERT_THROWS(ShardKeyPattern(BSON("a.b" << -1)), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << BSON("b" << 1))), DBException); @@ -154,9 +139,6 @@ TEST_F(ShardKeyPatternTest, NestedShardKeyPatternsValidtyCheck) { ASSERT_THROWS(ShardKeyPattern(BSON("a..b" << 1)), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << 1 << "a.b." << 1.0)), DBException); ASSERT_THROWS(ShardKeyPattern(BSON("a" << BSON("b" << 1) << "c.d" << 1.0)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("a.$" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("a.$b" << 1)), DBException); - ASSERT_THROWS(ShardKeyPattern(BSON("a.$**" << 1)), DBException); } TEST_F(ShardKeyPatternTest, IsShardKey) { @@ -626,7 +608,7 @@ TEST_F(ShardKeyPatternTest, ExtractQueryShardKeyHashed) { } static bool indexComp(const ShardKeyPattern& pattern, const BSONObj& indexPattern) { - return pattern.isIndexUniquenessCompatible(indexPattern); + return pattern.isUniqueIndexCompatible(indexPattern); } TEST_F(ShardKeyPatternTest, UniqueIndexCompatibleSingle) { @@ -1034,83 +1016,5 @@ TEST_F(ShardKeyPatternTest, ExtractShardKeyFromDocumentKey_Hashed) { BSONObj()); } -TEST_F(ShardKeyPatternTest, IsExtendedBy) { - - // NumberOfFields - ShardKeyPattern shardKeyPattern1(BSON("a" << 1)); - ShardKeyPattern shardKeyPattern2(BSON("a" << 1 << "b" << 1)); - ShardKeyPattern shardKeyPattern3(BSON("a" << 1 << "b" << 1 << "c" << 1)); - - // NumberOfFields_PositionOfHash - ShardKeyPattern shardKeyPatternHashed1_0(BSON("a" - << "hashed")); - ShardKeyPattern shardKeyPatternHashed2_0(BSON("a" - << "hashed" - << "b" << 1)); - ShardKeyPattern shardKeyPatternHashed2_1(BSON("a" << 1 << "b" - << "hashed")); - ShardKeyPattern shardKeyPatternHashed3_0(BSON("a" - << "hashed" - << "b" << 1 << "c" << 1)); - ShardKeyPattern shardKeyPatternHashed3_1(BSON("a" << 1 << "b" - << "hashed" - << "c" << 1)); - ShardKeyPattern shardKeyPatternHashed3_2(BSON("a" << 1 << "b" << 1 << "c" - << "hashed")); - - // same pattern, always true - ASSERT_TRUE(shardKeyPattern1.isExtendedBy(shardKeyPattern1)); - ASSERT_TRUE(shardKeyPattern2.isExtendedBy(shardKeyPattern2)); - ASSERT_TRUE(shardKeyPattern3.isExtendedBy(shardKeyPattern3)); - ASSERT_TRUE(shardKeyPatternHashed1_0.isExtendedBy(shardKeyPatternHashed1_0)); - ASSERT_TRUE(shardKeyPatternHashed2_0.isExtendedBy(shardKeyPatternHashed2_0)); - ASSERT_TRUE(shardKeyPatternHashed2_1.isExtendedBy(shardKeyPatternHashed2_1)); - ASSERT_TRUE(shardKeyPatternHashed3_0.isExtendedBy(shardKeyPatternHashed3_0)); - ASSERT_TRUE(shardKeyPatternHashed3_1.isExtendedBy(shardKeyPatternHashed3_1)); - ASSERT_TRUE(shardKeyPatternHashed3_2.isExtendedBy(shardKeyPatternHashed3_2)); - - // different number of fields, same values - ASSERT_TRUE(shardKeyPattern1.isExtendedBy(shardKeyPattern2)); - ASSERT_TRUE(shardKeyPattern2.isExtendedBy(shardKeyPattern3)); - ASSERT_TRUE(shardKeyPattern1.isExtendedBy(shardKeyPattern3)); - - ASSERT_FALSE(shardKeyPattern2.isExtendedBy(shardKeyPattern1)); - ASSERT_FALSE(shardKeyPattern3.isExtendedBy(shardKeyPattern2)); - ASSERT_FALSE(shardKeyPattern3.isExtendedBy(shardKeyPattern1)); - - // different number of fields, different values - // { a : 1 } is not extended by { a : "hashed" } and viceversa - ASSERT_FALSE(shardKeyPattern1.isExtendedBy(shardKeyPatternHashed1_0)); - ASSERT_FALSE(shardKeyPatternHashed1_0.isExtendedBy(shardKeyPattern1)); - - // { a : 1, b : 1 } is not extended by { a : 1, b : "hashed" } and viceversa - ASSERT_FALSE(shardKeyPattern2.isExtendedBy(shardKeyPatternHashed2_1)); - ASSERT_FALSE(shardKeyPatternHashed2_1.isExtendedBy(shardKeyPattern2)); - - // { a : 1 } is extended by { a : 1, b : "hashed" } but not viceversa - ASSERT_TRUE(shardKeyPattern1.isExtendedBy(shardKeyPatternHashed2_1)); - ASSERT_FALSE(shardKeyPatternHashed2_1.isExtendedBy(shardKeyPattern1)); - - // { a : 1, b : 1 } is extended by { a : 1, b : 1, c : "hashed" } but not viceversa - ASSERT_TRUE(shardKeyPattern2.isExtendedBy(shardKeyPatternHashed3_2)); - ASSERT_FALSE(shardKeyPatternHashed3_2.isExtendedBy(shardKeyPattern2)); - - // { a : 1, b : 1, c : 1 } is not extended by { a : 1, b : 1, c : "hashed" } and viceversa - ASSERT_FALSE(shardKeyPattern3.isExtendedBy(shardKeyPatternHashed3_2)); - ASSERT_FALSE(shardKeyPatternHashed3_2.isExtendedBy(shardKeyPattern3)); - - // { a: "hashed", b : 1 } is not extended by { a : 1, b : 1, c : "hashed" } and viceversa - ASSERT_FALSE(shardKeyPatternHashed2_1.isExtendedBy(shardKeyPatternHashed3_2)); - ASSERT_FALSE(shardKeyPatternHashed3_2.isExtendedBy(shardKeyPatternHashed2_1)); - - // { a : "hashed", b : 1 } is extended by { a : "hashed", b : 1, c : "1" } but not viceversa - ASSERT_TRUE(shardKeyPatternHashed2_0.isExtendedBy(shardKeyPatternHashed3_0)); - ASSERT_FALSE(shardKeyPatternHashed3_0.isExtendedBy(shardKeyPatternHashed2_0)); - - // { a : 1, b : "hashed " } is extended by { a : 1, b : "hashed", c : "1" } but not viceversa - ASSERT_TRUE(shardKeyPatternHashed2_1.isExtendedBy(shardKeyPatternHashed3_1)); - ASSERT_FALSE(shardKeyPatternHashed3_1.isExtendedBy(shardKeyPatternHashed2_1)); -} - } // namespace } // namespace mongo diff --git a/src/mongo/s/shard_util.cpp b/src/mongo/s/shard_util.cpp index 62ee76524ea..ae5e1aa9a1b 100644 --- a/src/mongo/s/shard_util.cpp +++ b/src/mongo/s/shard_util.cpp @@ -132,8 +132,7 @@ StatusWith<std::vector<BSONObj>> selectChunkSplitPoints(OperationContext* opCtx, const NamespaceString& nss, const ShardKeyPattern& shardKeyPattern, const ChunkRange& chunkRange, - long long chunkSizeBytes, - boost::optional<int> limit) { + long long chunkSizeBytes) { auto shardStatus = Grid::get(opCtx)->shardRegistry()->getShard(opCtx, shardId); if (!shardStatus.isOK()) { return shardStatus.getStatus(); @@ -148,9 +147,8 @@ StatusWith<std::vector<BSONObj>> selectChunkSplitPoints(OperationContext* opCtx, Shard::RetryPolicy::kIdempotent); }; - AutoSplitVectorRequest req( + const AutoSplitVectorRequest req( nss, shardKeyPattern.toBSON(), chunkRange.getMin(), chunkRange.getMax(), chunkSizeBytes); - req.setLimit(limit); auto cmdStatus = invokeSplitCommand(req.toBSON({}), nss.db()); diff --git a/src/mongo/s/shard_util.h b/src/mongo/s/shard_util.h index 18eb780eb1e..997dcc1e4c9 100644 --- a/src/mongo/s/shard_util.h +++ b/src/mongo/s/shard_util.h @@ -79,20 +79,20 @@ StatusWith<long long> retrieveCollectionShardSize(OperationContext* opCtx, /** * Ask the specified shard to figure out the split points for a given chunk. * - * - shardId: the shard id to query. - * - nss: namespace, which owns the chunk. - * - shardKeyPattern: the shard key which corresponds to this sharded namespace. - * - chunkRange: bounds of the chunk to search for split points on. - * - chunkSizeBytes: chunk size to target in bytes. - * - limit: limits the number of split points to search. Unspecified means no limit + * shardId The shard id to query. + * nss Namespace, which owns the chunk. + * shardKeyPattern The shard key which corresponds to this sharded namespace. + * chunkRange Bounds of the chunk to be split. + * chunkSize Chunk size to target in bytes. + * maxObjs Limits the number of objects in each chunk. Zero means max, unspecified means use the + * server default. */ StatusWith<std::vector<BSONObj>> selectChunkSplitPoints(OperationContext* opCtx, const ShardId& shardId, const NamespaceString& nss, const ShardKeyPattern& shardKeyPattern, const ChunkRange& chunkRange, - long long chunkSizeBytes, - boost::optional<int> limit = boost::none); + long long chunkSizeBytes); /** * Asks the specified shard to split the chunk described by min/maxKey into the respective split diff --git a/src/mongo/s/sharding_feature_flags.idl b/src/mongo/s/sharding_feature_flags.idl index aaeb4365106..2eea856a286 100644 --- a/src/mongo/s/sharding_feature_flags.idl +++ b/src/mongo/s/sharding_feature_flags.idl @@ -35,13 +35,11 @@ feature_flags: featureFlagNoMoreAutoSplitter: description: "Guarding code for the no more auto-splitter project" cpp_varname: feature_flags::gNoMoreAutoSplitter - default: true - version: 6.0 + default: false featureFlagBalanceAccordingToDataSize: description: Balancer taking decisions based on the data size if enabled, based on number of chunks if disabled cpp_varname: feature_flags::gBalanceAccordingToDataSize - default: true - version: 6.0 + default: false featureFlagShardingDataTransformMetrics: description: Feature flag for enabling the new metrics for global indexes and resharding. cpp_varname: feature_flags::gFeatureFlagShardingDataTransformMetrics @@ -57,8 +55,3 @@ feature_flags: cpp_varname: feature_flags::gOrphanTracking default: true version: 6.0 - featureFlagConcurrencyInChunkMigration: - description: "Feature flag for enabling concurrency within a chunk migration" - cpp_varname: feature_flags::gConcurrencyInChunkMigration - default: true - version: 6.0 diff --git a/src/mongo/s/sharding_initialization.cpp b/src/mongo/s/sharding_initialization.cpp index aa183dd6177..98129648e05 100644 --- a/src/mongo/s/sharding_initialization.cpp +++ b/src/mongo/s/sharding_initialization.cpp @@ -196,7 +196,6 @@ Status initializeGlobalShardingState(OperationContext* opCtx, // The shard registry must be started once the grid is initialized grid->shardRegistry()->startupPeriodicReloader(opCtx); - // Start up the cluster time keys manager with a sharded keys client. auto keysCollectionClient = std::make_unique<KeysCollectionClientSharded>(grid->catalogClient()); auto keyManager = diff --git a/src/mongo/s/sharding_router_test_fixture.cpp b/src/mongo/s/sharding_router_test_fixture.cpp index 2a5b2bfb8a4..4e9779773ac 100644 --- a/src/mongo/s/sharding_router_test_fixture.cpp +++ b/src/mongo/s/sharding_router_test_fixture.cpp @@ -70,7 +70,6 @@ #include "mongo/s/write_ops/batched_command_response.h" #include "mongo/transport/mock_session.h" #include "mongo/transport/transport_layer_mock.h" -#include "mongo/unittest/assert_that.h" #include "mongo/util/clock_source_mock.h" #include "mongo/util/tick_source_mock.h" @@ -393,9 +392,7 @@ void ShardingTestFixture::checkReadConcern(const BSONObj& cmdObj, ASSERT_EQ(Object, readConcernElem.type()); auto readConcernObj = readConcernElem.Obj(); - using namespace unittest::match; - ASSERT_THAT(readConcernObj[repl::ReadConcernArgs::kLevelFieldName].str(), - AnyOf(Eq("majority"), Eq("snapshot"))); + ASSERT_EQ("majority", readConcernObj[repl::ReadConcernArgs::kLevelFieldName].str()); auto afterOpTimeElem = readConcernObj[repl::ReadConcernArgs::kAfterOpTimeFieldName]; auto afterClusterTimeElem = readConcernObj[repl::ReadConcernArgs::kAfterClusterTimeFieldName]; diff --git a/src/mongo/s/sharding_task_executor_pool_controller.cpp b/src/mongo/s/sharding_task_executor_pool_controller.cpp index c73967e6c50..d95a76a168f 100644 --- a/src/mongo/s/sharding_task_executor_pool_controller.cpp +++ b/src/mongo/s/sharding_task_executor_pool_controller.cpp @@ -253,7 +253,7 @@ auto ShardingTaskExecutorPoolController::updateHost(PoolId id, const HostState& "maxConns"_attr = maxConns); // Update the target for just the pool first - poolData.target = stats.requests + stats.active + stats.leased; + poolData.target = stats.requests + stats.active; if (poolData.target < minConns) { poolData.target = minConns; diff --git a/src/mongo/s/stale_exception.cpp b/src/mongo/s/stale_exception.cpp index a04c6c27937..ac026a45ea2 100644 --- a/src/mongo/s/stale_exception.cpp +++ b/src/mongo/s/stale_exception.cpp @@ -39,18 +39,6 @@ MONGO_INIT_REGISTER_ERROR_EXTRA_INFO(StaleConfigInfo); MONGO_INIT_REGISTER_ERROR_EXTRA_INFO(StaleEpochInfo); MONGO_INIT_REGISTER_ERROR_EXTRA_INFO(StaleDbRoutingVersion); -boost::optional<ChunkVersion> extractOptionalChunkVersion(const BSONObj& obj, StringData field) { - try { - return ChunkVersion::fromBSONLegacyOrNewerFormat(obj, field); - } catch (const DBException& ex) { - auto status = ex.toStatus(); - if (status != ErrorCodes::NoSuchKey) { - throw; - } - } - return boost::none; -} - } // namespace void StaleConfigInfo::serialize(BSONObjBuilder* bob) const { @@ -68,35 +56,31 @@ std::shared_ptr<const ErrorExtraInfo> StaleConfigInfo::parse(const BSONObj& obj) const auto shardId = obj["shardId"].String(); uassert(ErrorCodes::NoSuchKey, "The shardId field is missing", !shardId.empty()); + auto extractOptionalChunkVersion = [&obj](StringData field) -> boost::optional<ChunkVersion> { + try { + return ChunkVersion::fromBSONLegacyOrNewerFormat(obj, field); + } catch (const DBException& ex) { + auto status = ex.toStatus(); + if (status != ErrorCodes::NoSuchKey) { + throw; + } + } + return boost::none; + }; + return std::make_shared<StaleConfigInfo>( NamespaceString(obj["ns"].String()), ChunkVersion::fromBSONLegacyOrNewerFormat(obj, "vReceived"), - extractOptionalChunkVersion(obj, "vWanted"), + extractOptionalChunkVersion("vWanted"), ShardId(shardId)); } void StaleEpochInfo::serialize(BSONObjBuilder* bob) const { bob->append("ns", _nss.ns()); - if (_received) - _received->appendLegacyWithField(bob, "vReceived"); - if (_wanted) - _wanted->appendLegacyWithField(bob, "vWanted"); } std::shared_ptr<const ErrorExtraInfo> StaleEpochInfo::parse(const BSONObj& obj) { - auto received = extractOptionalChunkVersion(obj, "vReceived"); - auto wanted = extractOptionalChunkVersion(obj, "vWanted"); - - uassert(6375907, - str::stream() << "Either both vReceived (" << received << ")" - << " and vWanted (" << wanted << ") must be present or none", - received.is_initialized() == wanted.is_initialized()); - - if (received) - return std::make_shared<StaleEpochInfo>( - NamespaceString(obj["ns"].String()), *received, *wanted); - else - return std::make_shared<StaleEpochInfo>(NamespaceString(obj["ns"].String())); + return std::make_shared<StaleEpochInfo>(NamespaceString(obj["ns"].String())); } void StaleDbRoutingVersion::serialize(BSONObjBuilder* bob) const { diff --git a/src/mongo/s/stale_exception.h b/src/mongo/s/stale_exception.h index 1778fe689a4..d82b18bab52 100644 --- a/src/mongo/s/stale_exception.h +++ b/src/mongo/s/stale_exception.h @@ -75,7 +75,7 @@ public: void serialize(BSONObjBuilder* bob) const; static std::shared_ptr<const ErrorExtraInfo> parse(const BSONObj& obj); -private: +protected: NamespaceString _nss; ChunkVersion _received; boost::optional<ChunkVersion> _wanted; @@ -85,42 +85,25 @@ private: boost::optional<SharedSemiFuture<void>> _criticalSectionSignal; }; -// TODO (SERVER-74380): Rename the StaleEpoch code to StaleDownstreamRouter and the info to -// StaleDownstreamRouterInfo class StaleEpochInfo final : public ErrorExtraInfo { public: static constexpr auto code = ErrorCodes::StaleEpoch; - StaleEpochInfo(NamespaceString nss, ChunkVersion received, ChunkVersion wanted) - : _nss(std::move(nss)), _received(received), _wanted(wanted) {} - - // TODO (SERVER-74380): Remove this constructor StaleEpochInfo(NamespaceString nss) : _nss(std::move(nss)) {} const auto& getNss() const { return _nss; } - const auto& getVersionReceived() const { - return _received; - } - - const auto& getVersionWanted() const { - return _wanted; - } - void serialize(BSONObjBuilder* bob) const; static std::shared_ptr<const ErrorExtraInfo> parse(const BSONObj& obj); private: NamespaceString _nss; - - // TODO (SERVER-74380): These two fields are boost::optional for backwards compatibility. Either - // both of them are boost::none or both are set. - boost::optional<ChunkVersion> _received; - boost::optional<ChunkVersion> _wanted; }; +using StaleConfigException = ExceptionFor<ErrorCodes::StaleConfig>; + class StaleDbRoutingVersion final : public ErrorExtraInfo { public: static constexpr auto code = ErrorCodes::StaleDbVersion; diff --git a/src/mongo/s/stale_exception_test.cpp b/src/mongo/s/stale_exception_test.cpp index 98a2b897af5..57cb2b89062 100644 --- a/src/mongo/s/stale_exception_test.cpp +++ b/src/mongo/s/stale_exception_test.cpp @@ -55,7 +55,7 @@ TEST(StaleExceptionTest, StaleConfigInfoSerializationTest) { ASSERT_EQUALS(deserializedInfo->getShardId(), kShardId); } -TEST(StaleExceptionTest, StaleEpochInfoLegacySerializationTest) { +TEST(StaleExceptionTest, StaleEpochInfoSerializationTest) { StaleEpochInfo info(kNss); // Serialize @@ -67,24 +67,6 @@ TEST(StaleExceptionTest, StaleEpochInfoLegacySerializationTest) { std::static_pointer_cast<const StaleEpochInfo>(StaleEpochInfo::parse(bob.obj())); ASSERT_EQUALS(deserializedInfo->getNss(), kNss); - ASSERT(!deserializedInfo->getVersionReceived()); - ASSERT(!deserializedInfo->getVersionWanted()); -} - -TEST(StaleExceptionTest, StaleEpochInfoSerializationTest) { - StaleEpochInfo info(kNss, ChunkVersion::UNSHARDED(), ChunkVersion::UNSHARDED()); - - // Serialize - BSONObjBuilder bob; - info.serialize(&bob); - - // Deserialize - auto deserializedInfo = - std::static_pointer_cast<const StaleEpochInfo>(StaleEpochInfo::parse(bob.obj())); - - ASSERT_EQ(deserializedInfo->getNss(), kNss); - ASSERT_EQ(*deserializedInfo->getVersionReceived(), ChunkVersion::UNSHARDED()); - ASSERT_EQ(*deserializedInfo->getVersionWanted(), ChunkVersion::UNSHARDED()); } } // namespace diff --git a/src/mongo/s/stale_shard_version_helpers.cpp b/src/mongo/s/stale_shard_version_helpers.cpp index 07f5711cf6c..50e63c1667e 100644 --- a/src/mongo/s/stale_shard_version_helpers.cpp +++ b/src/mongo/s/stale_shard_version_helpers.cpp @@ -62,6 +62,10 @@ void checkErrorStatusAndMaxRetries(const Status& status, if (status == ErrorCodes::StaleDbVersion) { auto staleInfo = status.extraInfo< error_details::ErrorExtraInfoForImpl<ErrorCodes::StaleDbVersion>::type>(); + invariant(staleInfo->getDb() == nss.db(), + str::stream() << "StaleDbVersion error on unexpected database. Expected " + << nss.db() << ", received " << staleInfo->getDb()); + // If the database version is stale, refresh its entry in the catalog cache. catalogCache->onStaleDatabaseVersion(staleInfo->getDb(), staleInfo->getVersionWanted()); @@ -74,6 +78,9 @@ void checkErrorStatusAndMaxRetries(const Status& status, // If the cache currently considers the collection to be unsharded, this will trigger an // epoch refresh. If no shard is provided, then the epoch is stale and we must refresh. if (auto staleInfo = status.extraInfo<StaleConfigInfo>()) { + invariant(staleInfo->getNss() == nss, + str::stream() << "StaleConfig error on unexpected namespace. Expected " << nss + << ", received " << staleInfo->getNss()); catalogCache->invalidateShardOrEntireCollectionEntryForShardedCollection( nss, staleInfo->getVersionWanted(), staleInfo->getShardId()); } else { diff --git a/src/mongo/s/transaction_router.cpp b/src/mongo/s/transaction_router.cpp index 01e9abefb86..db0cc68ca61 100644 --- a/src/mongo/s/transaction_router.cpp +++ b/src/mongo/s/transaction_router.cpp @@ -43,7 +43,6 @@ #include "mongo/db/internal_transactions_feature_flag_gen.h" #include "mongo/db/jsobj.h" #include "mongo/db/logical_session_id.h" -#include "mongo/db/namespace_string.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/transaction_validation.h" #include "mongo/db/txn_retry_counter_too_old_info.h" @@ -71,11 +70,6 @@ using namespace fmt::literals; // TODO SERVER-39704: Remove this fail point once the router can safely retry within a transaction // on stale version and snapshot errors. MONGO_FAIL_POINT_DEFINE(enableStaleVersionAndSnapshotRetriesWithinTransactions); -// This failpoint is used to skip the conflictPlacementTimestamp check for unittests in -// transaction_router_test.cpp. The check involves fetching of the catalog cache, which the existing -// unittests are not set up to do. -MONGO_FAIL_POINT_DEFINE(skipConflictPlacementTimestampCheck); - const char kCoordinatorField[] = "coordinator"; const char kReadConcernLevelSnapshotName[] = "snapshot"; @@ -265,34 +259,6 @@ std::string actionTypeToString(TransactionRouter::TransactionActions action) { MONGO_UNREACHABLE; } -/** - * Sets the given logical time as the atClusterTime for the transaction to be the greater of - * the given time and the user's afterClusterTime, if one was provided. - */ -void setAtClusterTime(const LogicalSessionId& lsid, - const TxnNumberAndRetryCounter& txnNumberAndRetryCounter, - StmtId latestStmtId, - TransactionRouter::AtClusterTime* atClusterTime, - const boost::optional<LogicalTime>& afterClusterTime, - const LogicalTime& candidateTime) { - // If the user passed afterClusterTime, the chosen time must be greater than or equal to it. - if (afterClusterTime && *afterClusterTime > candidateTime) { - atClusterTime->setTime(*afterClusterTime, latestStmtId); - return; - } - - LOGV2_DEBUG(22888, - 2, - "Setting global snapshot timestamp for transaction", - "sessionId"_attr = lsid, - "txnNumber"_attr = txnNumberAndRetryCounter.getTxnNumber(), - "txnRetryCounter"_attr = txnNumberAndRetryCounter.getTxnRetryCounter(), - "globalSnapshotTimestamp"_attr = candidateTime, - "latestStmtId"_attr = latestStmtId); - - atClusterTime->setTime(candidateTime, latestStmtId); -} - } // unnamed namespace TransactionRouter::TransactionRouter() = default; @@ -384,8 +350,7 @@ void TransactionRouter::Observer::_reportTransactionState(OperationContext* opCt } if (_atClusterTimeHasBeenSet()) { - builder->append("globalReadTimestamp", - o().atClusterTimeForSnapshotReadConcern->getTime().asTimestamp()); + builder->append("globalReadTimestamp", o().atClusterTime->getTime().asTimestamp()); } const auto& timingStats = o().metricsTracker->getTimingStats(); @@ -443,8 +408,7 @@ void TransactionRouter::Observer::_reportTransactionState(OperationContext* opCt } bool TransactionRouter::Observer::_atClusterTimeHasBeenSet() const { - return o().atClusterTimeForSnapshotReadConcern && - o().atClusterTimeForSnapshotReadConcern->timeHasBeenSet(); + return o().atClusterTime.is_initialized() && o().atClusterTime->timeHasBeenSet(); } const LogicalSessionId& TransactionRouter::Observer::_sessionId() const { @@ -491,7 +455,7 @@ BSONObj TransactionRouter::Participant::attachTxnFieldsIfNeeded( BSONObjBuilder newCmd = mustStartTransaction ? appendFieldsForStartTransaction(std::move(cmd), sharedOptions.readConcernArgs, - sharedOptions.atClusterTimeForSnapshotReadConcern, + sharedOptions.atClusterTime, !hasStartTxn) : BSONObjBuilder(std::move(cmd)); @@ -625,17 +589,12 @@ bool TransactionRouter::AtClusterTime::canChange(StmtId currentStmtId) const { } bool TransactionRouter::Router::mustUseAtClusterTime() const { - return o().atClusterTimeForSnapshotReadConcern.is_initialized(); + return o().atClusterTime.is_initialized(); } LogicalTime TransactionRouter::Router::getSelectedAtClusterTime() const { - invariant(o().atClusterTimeForSnapshotReadConcern); - return o().atClusterTimeForSnapshotReadConcern->getTime(); -} - -LogicalTime TransactionRouter::Router::getPlacementConflictTime() const { - invariant(o().placementConflictTimeForNonSnapshotReadConcern); - return o().placementConflictTimeForNonSnapshotReadConcern->getTime(); + invariant(o().atClusterTime); + return o().atClusterTime->getTime(); } const boost::optional<ShardId>& TransactionRouter::Router::getCoordinatorId() const { @@ -646,76 +605,21 @@ const boost::optional<ShardId>& TransactionRouter::Router::getRecoveryShardId() return p().recoveryShardId; } -void TransactionRouter::Router::_checkForPlacementConflict(OperationContext* opCtx, - const ShardId& shardId, - const NamespaceString& nss) { - // Check if the current routing table is aware of a data placement change that - // is more recent than the timestamp the current transaction started with. If - // so, we throw a MigrationConflict error to force the client to retry so the - // storage engine uses an up to date snapshot. - // No need to check it when using snapshot readConcern. - const auto cm = - uassertStatusOK(Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfo(opCtx, nss)); - if (!_atClusterTimeHasBeenSet() && cm.isSharded() && - (getPlacementConflictTime().asTimestamp() < cm.getMaxValidAfter(shardId))) { - uasserted(ErrorCodes::MigrationConflict, - str::stream() << "Collection " << nss - << " has undergone a catalog change operation at time " - << cm.getMaxValidAfter(shardId) - << " and no longer satisfies the " - "requirements for the current transaction which requires " - << getPlacementConflictTime().asTimestamp() - << ". Transaction will be aborted."); - } - - // For dbVersion, the router needs to check when using both snapshot and non-snapshot read - // concerns. - if (!cm.isSharded()) { - const auto txnConflictTimestamp = _atClusterTimeHasBeenSet() - ? getSelectedAtClusterTime().asTimestamp() - : getPlacementConflictTime().asTimestamp(); - - bool dbWasCreatedByThisTransaction = - !p().createdDatabases.empty() && p().createdDatabases.count(nss.db().toString()) > 0; - if (txnConflictTimestamp < cm.dbVersion().getTimestamp() && - !dbWasCreatedByThisTransaction) { - uasserted(ErrorCodes::MigrationConflict, - str::stream() << "Database " << nss.db() - << " has undergone a catalog change operation at time " - << cm.dbVersion().getTimestamp() - << " and no longer satisfies the " - "requirements for the current transaction which requires " - << txnConflictTimestamp << ". Transaction will be aborted."); - } - } -} - BSONObj TransactionRouter::Router::attachTxnFieldsIfNeeded(OperationContext* opCtx, const ShardId& shardId, - const BSONObj& cmdObj, - const StringData& dbName) { - // Skip the placement check if we are not running a transaction. - if (!(opCtx->isRetryableWrite() || - MONGO_unlikely(skipConflictPlacementTimestampCheck.shouldFail()))) { - // For commands only against a db and not a collection, skip the placementConflict check. - if (auto nss = NamespaceString(CommandHelpers::parseNsFromCommand(dbName, cmdObj)); - nsIsFull(nss.toString())) { - _checkForPlacementConflict(opCtx, shardId, nss); - } - } - + const BSONObj& cmdObj) { RouterTransactionsMetrics::get(opCtx)->incrementTotalRequestsTargeted(); if (auto txnPart = getParticipant(shardId)) { - LOGV2_DEBUG(22883, - 4, - "{sessionId}:{txnNumber} Sending transaction fields to existing " - "participant: {shardId}", - "Attaching transaction fields to request for existing participant shard", - "sessionId"_attr = _sessionId(), - "txnNumber"_attr = o().txnNumberAndRetryCounter.getTxnNumber(), - "txnRetryCounter"_attr = o().txnNumberAndRetryCounter.getTxnRetryCounter(), - "shardId"_attr = shardId, - "request"_attr = redact(cmdObj)); + LOGV2_DEBUG( + 22883, + 4, + "{sessionId}:{txnNumber} Sending transaction fields to existing participant: {shardId}", + "Attaching transaction fields to request for existing participant shard", + "sessionId"_attr = _sessionId(), + "txnNumber"_attr = o().txnNumberAndRetryCounter.getTxnNumber(), + "txnRetryCounter"_attr = o().txnNumberAndRetryCounter.getTxnRetryCounter(), + "shardId"_attr = shardId, + "request"_attr = redact(cmdObj)); return txnPart->attachTxnFieldsIfNeeded(cmdObj, false); } @@ -730,8 +634,7 @@ BSONObj TransactionRouter::Router::attachTxnFieldsIfNeeded(OperationContext* opC "shardId"_attr = shardId, "request"_attr = redact(cmdObj)); if (!p().isRecoveringCommit) { - // Don't update participant stats during recovery since the participant list isn't - // known. + // Don't update participant stats during recovery since the participant list isn't known. RouterTransactionsMetrics::get(opCtx)->incrementTotalContactedParticipants(); } @@ -739,10 +642,9 @@ BSONObj TransactionRouter::Router::attachTxnFieldsIfNeeded(OperationContext* opC } void TransactionRouter::Router::_verifyParticipantAtClusterTime(const Participant& participant) { - const auto& participantAtClusterTime = - participant.sharedOptions.atClusterTimeForSnapshotReadConcern; + const auto& participantAtClusterTime = participant.sharedOptions.atClusterTime; invariant(participantAtClusterTime); - invariant(*participantAtClusterTime == o().atClusterTimeForSnapshotReadConcern->getTime()); + invariant(*participantAtClusterTime == o().atClusterTime->getTime()); } const TransactionRouter::Participant* TransactionRouter::Router::getParticipant( @@ -751,7 +653,7 @@ const TransactionRouter::Participant* TransactionRouter::Router::getParticipant( if (iter == o().participants.end()) return nullptr; - if (o().atClusterTimeForSnapshotReadConcern) { + if (o().atClusterTime) { _verifyParticipantAtClusterTime(iter->second); } @@ -773,10 +675,8 @@ TransactionRouter::Participant& TransactionRouter::Router::_createParticipant( o().txnNumberAndRetryCounter, o().apiParameters, o().readConcernArgs, - o().atClusterTimeForSnapshotReadConcern - ? boost::optional<LogicalTime>(o().atClusterTimeForSnapshotReadConcern->getTime()) - : boost::none, - boost::none, + o().atClusterTime ? boost::optional<LogicalTime>(o().atClusterTime->getTime()) + : boost::none, isInternalSessionForRetryableWrite(_sessionId())}; stdx::lock_guard<Client> lk(*opCtx->getClient()); @@ -963,8 +863,7 @@ void TransactionRouter::Router::onViewResolutionError(OperationContext* opCtx, bool TransactionRouter::Router::canContinueOnSnapshotError() const { if (MONGO_unlikely(enableStaleVersionAndSnapshotRetriesWithinTransactions.shouldFail())) { - return o().atClusterTimeForSnapshotReadConcern && - o().atClusterTimeForSnapshotReadConcern->canChange(p().latestStmtId); + return o().atClusterTime && o().atClusterTime->canChange(p().latestStmtId); } return false; @@ -985,8 +884,7 @@ void TransactionRouter::Router::onSnapshotError(OperationContext* opCtx, const S "txnNumber"_attr = o().txnNumberAndRetryCounter.getTxnNumber(), "txnRetryCounter"_attr = o().txnNumberAndRetryCounter.getTxnRetryCounter(), "error"_attr = redact(status), - "previousGlobalSnapshotTimestamp"_attr = - o().atClusterTimeForSnapshotReadConcern->getTime()); + "previousGlobalSnapshotTimestamp"_attr = o().atClusterTime->getTime()); // The transaction must be restarted on all participants because a new read timestamp will be // selected, so clear all pending participants. Snapshot errors are only retryable on the first @@ -998,37 +896,45 @@ void TransactionRouter::Router::onSnapshotError(OperationContext* opCtx, const S stdx::lock_guard<Client> lk(*opCtx->getClient()); // Reset the global snapshot timestamp so the retry will select a new one. - o(lk).atClusterTimeForSnapshotReadConcern.reset(); - o(lk).atClusterTimeForSnapshotReadConcern.emplace(); + o(lk).atClusterTime.reset(); + o(lk).atClusterTime.emplace(); } void TransactionRouter::Router::setDefaultAtClusterTime(OperationContext* opCtx) { + if (!o().atClusterTime || !o().atClusterTime->canChange(p().latestStmtId)) { + return; + } + const auto defaultTime = VectorClock::get(opCtx)->getTime(); + _setAtClusterTime(opCtx, + repl::ReadConcernArgs::get(opCtx).getArgsAfterClusterTime(), + defaultTime.clusterTime()); +} - if (o().atClusterTimeForSnapshotReadConcern) { - if (o().atClusterTimeForSnapshotReadConcern->canChange(p().latestStmtId)) { - stdx::lock_guard<Client> lk(*opCtx->getClient()); - setAtClusterTime(_sessionId(), - o(lk).txnNumberAndRetryCounter, - p().latestStmtId, - o(lk).atClusterTimeForSnapshotReadConcern.get_ptr(), - repl::ReadConcernArgs::get(opCtx).getArgsAfterClusterTime(), - defaultTime.clusterTime()); - } - } else if (o().placementConflictTimeForNonSnapshotReadConcern) { - // The placementConflictTimestamp is chosen to be the latest VectorClock time known, which - // should be regularly gossiped. This will ensure that we are not in a state where a mongos - // repeatedly chooses a stale timestamp and throw MigrationConflict errors. - if (o().placementConflictTimeForNonSnapshotReadConcern->canChange(p().latestStmtId)) { - stdx::lock_guard<Client> lk(*opCtx->getClient()); - setAtClusterTime(_sessionId(), - o(lk).txnNumberAndRetryCounter, - p().latestStmtId, - o(lk).placementConflictTimeForNonSnapshotReadConcern.get_ptr(), - repl::ReadConcernArgs::get(opCtx).getArgsAfterClusterTime(), - defaultTime.clusterTime()); - } +void TransactionRouter::Router::_setAtClusterTime( + OperationContext* opCtx, + const boost::optional<LogicalTime>& afterClusterTime, + LogicalTime candidateTime) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + + // If the user passed afterClusterTime, the chosen time must be greater than or equal to it. + if (afterClusterTime && *afterClusterTime > candidateTime) { + o(lk).atClusterTime->setTime(*afterClusterTime, p().latestStmtId); + return; } + + LOGV2_DEBUG(22888, + 2, + "{sessionId}:{txnNumber} Setting global snapshot timestamp to " + "{globalSnapshotTimestamp} on statement {latestStmtId}", + "Setting global snapshot timestamp for transaction", + "sessionId"_attr = _sessionId(), + "txnNumber"_attr = o().txnNumberAndRetryCounter.getTxnNumber(), + "txnRetryCounter"_attr = o().txnNumberAndRetryCounter.getTxnRetryCounter(), + "globalSnapshotTimestamp"_attr = candidateTime, + "latestStmtId"_attr = p().latestStmtId); + + o(lk).atClusterTime->setTime(candidateTime, p().latestStmtId); } void TransactionRouter::Router::_continueTxn(OperationContext* opCtx, @@ -1055,15 +961,6 @@ void TransactionRouter::Router::_continueTxn(OperationContext* opCtx, repl::ReadConcernArgs::get(opCtx) = o().readConcernArgs; ++p().latestStmtId; - - uassert( - 8027900, - str::stream() << "attempting to continue transaction that was not started lsid: " - << _sessionId() - << " txnNumber: " << o().txnNumberAndRetryCounter.getTxnNumber(), - o().atClusterTimeForSnapshotReadConcern || - o().placementConflictTimeForNonSnapshotReadConcern); - _onContinue(opCtx); break; } @@ -1554,12 +1451,10 @@ void TransactionRouter::Router::_resetRouterState( p().recoveryShardId.reset(); o(lk).apiParameters = {}; o(lk).readConcernArgs = {}; - o(lk).atClusterTimeForSnapshotReadConcern.reset(); - o(lk).placementConflictTimeForNonSnapshotReadConcern.reset(); + o(lk).atClusterTime.reset(); o(lk).abortCause = std::string(); o(lk).metricsTracker.emplace(opCtx->getServiceContext()); p().terminationInitiated = false; - p().createdDatabases.clear(); auto tickSource = opCtx->getServiceContext()->getTickSource(); o(lk).metricsTracker->trySetActive(tickSource, tickSource->getTicks()); @@ -1587,16 +1482,13 @@ void TransactionRouter::Router::_resetRouterStateForStartTransaction( { stdx::lock_guard<Client> lk(*opCtx->getClient()); - auto& osw = o(lk); - - osw.apiParameters = APIParameters::get(opCtx); - osw.readConcernArgs = readConcernArgs; + o(lk).apiParameters = APIParameters::get(opCtx); + o(lk).readConcernArgs = readConcernArgs; + } - if (osw.readConcernArgs.getLevel() == repl::ReadConcernLevel::kSnapshotReadConcern) { - osw.atClusterTimeForSnapshotReadConcern.emplace(); - } else { - osw.placementConflictTimeForNonSnapshotReadConcern.emplace(); - } + if (o().readConcernArgs.getLevel() == repl::ReadConcernLevel::kSnapshotReadConcern) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).atClusterTime.emplace(); } LOGV2_DEBUG(22889, @@ -1626,8 +1518,7 @@ BSONObj TransactionRouter::Router::_commitWithRecoveryToken(OperationContext* op auto rawCoordinateCommit = coordinateCommitCmd.toBSON( BSON(WriteConcernOptions::kWriteConcernField << opCtx->getWriteConcern().toBSON())); - return attachTxnFieldsIfNeeded( - opCtx, recoveryShardId, rawCoordinateCommit, coordinateCommitCmd.getDbName()); + return attachTxnFieldsIfNeeded(opCtx, recoveryShardId, rawCoordinateCommit); }(); auto recoveryShard = uassertStatusOK(shardRegistry->getShard(opCtx, recoveryShardId)); @@ -1664,7 +1555,7 @@ void TransactionRouter::Router::_logSlowTransaction(OperationContext* opCtx, std::string globalReadTimestampTemp; if (_atClusterTimeHasBeenSet()) { - globalReadTimestampTemp = o().atClusterTimeForSnapshotReadConcern->getTime().toString(); + globalReadTimestampTemp = o().atClusterTime->getTime().toString(); attrs.add("globalReadTimestamp", globalReadTimestampTemp); } diff --git a/src/mongo/s/transaction_router.h b/src/mongo/s/transaction_router.h index cf1be06dcca..a7b49c5eb12 100644 --- a/src/mongo/s/transaction_router.h +++ b/src/mongo/s/transaction_router.h @@ -90,9 +90,7 @@ public: repl::ReadConcernArgs readConcernArgs; // Only set for transactions with snapshot level read concern. - boost::optional<LogicalTime> atClusterTimeForSnapshotReadConcern; - - boost::optional<LogicalTime> placementConflictTimeForNonSnapshotReadConcern; + boost::optional<LogicalTime> atClusterTime; bool isInternalTransactionForRetryableWrite; }; @@ -408,12 +406,11 @@ public: */ BSONObj attachTxnFieldsIfNeeded(OperationContext* opCtx, const ShardId& shardId, - const BSONObj& cmdObj, - const StringData& dbName); + const BSONObj& cmdObj); /** - * Processes the transaction metadata in the response from the participant if the - * response indicates the operation succeeded. + * Processes the transaction metadata in the response from the participant if the response + * indicates the operation succeeded. */ void processParticipantResponse(OperationContext* opCtx, const ShardId& shardId, @@ -476,11 +473,6 @@ public: LogicalTime getSelectedAtClusterTime() const; /** - * Returns the placement conflict timestamp chosen for this transaction. - */ - LogicalTime getPlacementConflictTime() const; - - /** * Sets the atClusterTime for the current transaction to the latest time in the router's * logical clock. Does nothing if the transaction does not have snapshot read concern or an * atClusterTime has already been selected and cannot be changed. @@ -565,13 +557,6 @@ public: return true; } - /** - * Annotate that this transaction has attempted to create database 'dbName'. - */ - void annotateCreatedDatabase(const StringData dbName) { - p().createdDatabases.insert(dbName.toString()); - } - private: /** * Resets the router's state. Used when the router sees a new transaction for the first @@ -624,6 +609,14 @@ public: BSONObj _handOffCommitToCoordinator(OperationContext* opCtx); /** + * Sets the given logical time as the atClusterTime for the transaction to be the greater of + * the given time and the user's afterClusterTime, if one was provided. + */ + void _setAtClusterTime(OperationContext* opCtx, + const boost::optional<LogicalTime>& afterClusterTime, + LogicalTime candidateTime); + + /** * Throws NoSuchTransaction if the response from abortTransaction failed with a code other * than NoSuchTransaction. Does not check for write concern errors. */ @@ -723,14 +716,6 @@ public: */ bool _errorAllowsRetryOnStaleShardOrDb(const Status& status) const; - /** - * Check if the routing table has a higher timestamp than this transaction's - * placement conflict timestamp. - */ - void _checkForPlacementConflict(OperationContext* opCtx, - const ShardId& shardId, - const NamespaceString& nss); - TransactionRouter::PrivateState& p() { return _tr->_p; } @@ -789,9 +774,7 @@ private: // The cluster time of the timestamp all participant shards in the current transaction with // snapshot level read concern must read from. Only set for transactions running with // snapshot level read concern. - boost::optional<AtClusterTime> atClusterTimeForSnapshotReadConcern; - - boost::optional<AtClusterTime> placementConflictTimeForNonSnapshotReadConcern; + boost::optional<AtClusterTime> atClusterTime; // String representing the reason a transaction aborted. Either the string name of the error // code that led to an implicit abort or "abort" if the client sent abortTransaction. @@ -840,9 +823,6 @@ private: // Track whether commit or abort have been initiated. bool terminationInitiated{false}; - - // Tracks databases that this transaction has attempted to create. - std::set<std::string> createdDatabases; } _p; }; diff --git a/src/mongo/s/transaction_router_test.cpp b/src/mongo/s/transaction_router_test.cpp index d72d1aea6ff..1191e92d11e 100644 --- a/src/mongo/s/transaction_router_test.cpp +++ b/src/mongo/s/transaction_router_test.cpp @@ -137,8 +137,6 @@ protected: _staleVersionAndSnapshotRetriesBlock = std::make_unique<FailPointEnableBlock>( "enableStaleVersionAndSnapshotRetriesWithinTransactions"); - _skipConflictPlacementTimestampCheck = - std::make_unique<FailPointEnableBlock>("skipConflictPlacementTimestampCheck"); } void disableRouterRetriesFailPoint() { @@ -220,10 +218,6 @@ protected: }); } -protected: - std::unique_ptr<FailPointEnableBlock> _skipConflictPlacementTimestampCheck; - - private: // Enables the transaction router to retry within a transaction on stale version and snapshot // errors for the duration of each test. @@ -279,8 +273,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -288,8 +281,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("update" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(BSON("update" << "test" << "coordinator" << true << "autocommit" << false << "txnNumber" @@ -320,8 +312,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, BasicStartTxnWithAtClusterTime) auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -329,8 +320,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, BasicStartTxnWithAtClusterTime) auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("update" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(BSON("update" << "test" << "coordinator" << true << "autocommit" << false << "txnNumber" @@ -373,8 +363,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, NewParticipantMustAttachTxnAndRe auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -382,8 +371,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, NewParticipantMustAttachTxnAndRe auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("update" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(BSON("update" << "test" << "coordinator" << true << "autocommit" << false << "txnNumber" @@ -404,8 +392,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, NewParticipantMustAttachTxnAndRe auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -413,8 +400,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, NewParticipantMustAttachTxnAndRe auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, BSON("update" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(BSON("update" << "test" << "autocommit" << false << "txnNumber" << txnNum), @@ -435,8 +421,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, StartingNewTxnShouldClearState) auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("update" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(BSON("update" << "test" << "readConcern" @@ -467,8 +452,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, StartingNewTxnShouldClearState) auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } } @@ -482,15 +466,14 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.beginOrContinueTxn( operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" << "test" - << "txnNumber" << txnNum), - "db"); + << "txnNumber" << txnNum)); ASSERT_EQ(newCmd.hasField("txnRetryCounter"), false); } @@ -506,7 +489,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, FirstParticipantIsCoordinator) { ASSERT_FALSE(txnRouter.getCoordinatorId()); { - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); auto& participant = *txnRouter.getParticipant(shard1); ASSERT(participant.isCoordinator); ASSERT(txnRouter.getCoordinatorId()); @@ -514,7 +497,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, FirstParticipantIsCoordinator) { } { - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); auto& participant = *txnRouter.getParticipant(shard2); ASSERT(!participant.isCoordinator); ASSERT(txnRouter.getCoordinatorId()); @@ -530,7 +513,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, FirstParticipantIsCoordinator) { ASSERT_FALSE(txnRouter.getCoordinatorId()); { - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); auto& participant = *txnRouter.getParticipant(shard2); ASSERT(participant.isCoordinator); ASSERT(txnRouter.getCoordinatorId()); @@ -551,7 +534,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, RecoveryShardDoesNotGetSetForRea ASSERT_FALSE(txnRouter.getRecoveryShardId()); // The recovery shard is not set on scheduling requests. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_FALSE(txnRouter.getRecoveryShardId()); // The recovery shard is not set if a participant responds with ok but that it is read-only. @@ -559,7 +542,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, RecoveryShardDoesNotGetSetForRea ASSERT_FALSE(txnRouter.getRecoveryShardId()); // The recovery shard is not set even if more read-only participants respond. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyTrueResponse); ASSERT_FALSE(txnRouter.getRecoveryShardId()); @@ -590,7 +573,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT(txnRouter.getRecoveryShardId()); ASSERT_EQ(*txnRouter.getRecoveryShardId(), shard1); @@ -606,7 +589,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); // Response to first statement says read-only. txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); @@ -629,12 +612,12 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.setDefaultAtClusterTime(operationContext()); // Shard1's response says read-only. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); ASSERT_FALSE(txnRouter.getRecoveryShardId()); // Shard2's response says not read-only. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); ASSERT(txnRouter.getRecoveryShardId()); ASSERT_EQ(*txnRouter.getRecoveryShardId(), shard2); @@ -651,7 +634,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.setDefaultAtClusterTime(operationContext()); // Shard1's response says not read-only. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT(txnRouter.getRecoveryShardId()); ASSERT_EQ(*txnRouter.getRecoveryShardId(), shard1); @@ -676,7 +659,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.setDefaultAtClusterTime(operationContext()); // Shard1's response says not read-only. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT(txnRouter.getRecoveryShardId()); ASSERT_EQ(*txnRouter.getRecoveryShardId(), shard1); @@ -688,7 +671,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, // Shard2 responds, it doesn't matter whether it's read-only, just that it's a pending // participant. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); ASSERT(txnRouter.getRecoveryShardId()); ASSERT_EQ(*txnRouter.getRecoveryShardId(), shard1); @@ -713,7 +696,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, RecoveryShardIsResetOnStartingNe txnRouter.setDefaultAtClusterTime(operationContext()); // Shard1's response says not read-only. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT(txnRouter.getRecoveryShardId()); ASSERT_EQ(*txnRouter.getRecoveryShardId(), shard1); @@ -748,8 +731,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, DoesNotAttachTxnNumIfAlreadyTher shard1, BSON("insert" << "test" - << "txnNumber" << txnNum), - "db"); + << "txnNumber" << txnNum)); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -768,8 +750,7 @@ DEATH_TEST_F(TransactionRouterTestWithDefaultSession, shard1, BSON("insert" << "test" - << "txnNumber" << TxnNumber(10)), - "db"); + << "txnNumber" << TxnNumber(10))); } DEATH_TEST_F(TransactionRouterTestWithDefaultSession, @@ -831,8 +812,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, AttachTxnValidatesReadConcernIfA << "test" << "readConcern" << BSON("level" - << "snapshot")), - "db"); + << "snapshot"))); ASSERT_BSONOBJ_EQ(BSON("insert" << "test" << "readConcern" @@ -943,8 +923,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, PassesThroughEmptyReadConcernToP auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -972,8 +951,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedNewObj, newCmd); } @@ -1098,7 +1076,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); TxnRecoveryToken recoveryToken; @@ -1135,7 +1113,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); TxnRecoveryToken recoveryToken; @@ -1172,8 +1150,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyTrueResponse); @@ -1223,8 +1201,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -1272,8 +1250,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -1429,8 +1407,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -1489,8 +1467,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -1632,8 +1610,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, SnapshotErrorsResetAtClusterTime auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } @@ -1659,8 +1636,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, SnapshotErrorsResetAtClusterTime auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } } @@ -1683,8 +1659,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } @@ -1704,8 +1679,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } @@ -1725,8 +1699,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } } @@ -1742,8 +1715,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, SnapshotErrorsClearsAllParticipa // Successfully start a transaction on two shards, selecting one as the coordinator. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT(txnRouter.getCoordinatorId()); ASSERT_EQ(*txnRouter.getCoordinatorId(), shard1); @@ -1761,10 +1734,10 @@ TEST_F(TransactionRouterTestWithDefaultSession, SnapshotErrorsClearsAllParticipa ASSERT_FALSE(txnRouter.getCoordinatorId()); { - auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_TRUE(newCmd["startTransaction"].trueValue()); - newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_FALSE(newCmd["startTransaction"].trueValue()); } @@ -1774,10 +1747,10 @@ TEST_F(TransactionRouterTestWithDefaultSession, SnapshotErrorsClearsAllParticipa { // Shard1 should also attach startTransaction field again. - auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_TRUE(newCmd["startTransaction"].trueValue()); - newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_FALSE(newCmd["startTransaction"].trueValue()); } } @@ -1819,8 +1792,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, ParticipantsRememberStmtIdCreate // command. int initialStmtId = 0; - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_EQ(txnRouter.getParticipant(shard1)->stmtIdCreatedAt, initialStmtId); ASSERT_EQ(txnRouter.getParticipant(shard2)->stmtIdCreatedAt, initialStmtId); @@ -1830,7 +1803,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, ParticipantsRememberStmtIdCreate operationContext(), txnNum, TransactionRouter::TransactionActions::kContinue); ShardId shard3("shard3"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}); ASSERT_EQ(txnRouter.getParticipant(shard3)->stmtIdCreatedAt, initialStmtId + 1); ASSERT_EQ(txnRouter.getParticipant(shard1)->stmtIdCreatedAt, initialStmtId); @@ -1847,8 +1820,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, ParticipantsRememberStmtIdCreate operationContext(), txnNum2, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_EQ(txnRouter.getParticipant(shard3)->stmtIdCreatedAt, initialStmtId); ASSERT_EQ(txnRouter.getParticipant(shard2)->stmtIdCreatedAt, initialStmtId); @@ -1857,7 +1830,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, ParticipantsRememberStmtIdCreate txnRouter.beginOrContinueTxn( operationContext(), txnNum2, TransactionRouter::TransactionActions::kContinue); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_EQ(txnRouter.getParticipant(shard1)->stmtIdCreatedAt, initialStmtId + 1); } @@ -1874,8 +1847,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, // Start a transaction on two shards, selecting one as the coordinator, but simulate a // re-targeting error from at least one of them. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT(txnRouter.getCoordinatorId()); ASSERT_EQ(*txnRouter.getCoordinatorId(), shard1); @@ -1892,7 +1865,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, { ASSERT_FALSE(txnRouter.getParticipant(shard2)); - auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_TRUE(newCmd["startTransaction"].trueValue()); } @@ -1903,7 +1876,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, { // Shard1 has not started a transaction. ASSERT_FALSE(txnRouter.getParticipant(shard1)); - auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_TRUE(newCmd["startTransaction"].trueValue()); } } @@ -1919,7 +1892,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnlyNewlyCreatedParticipantsClea // First statement successfully targets one shard, selecing it as the coordinator. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT(txnRouter.getCoordinatorId()); ASSERT_EQ(*txnRouter.getCoordinatorId(), shard1); @@ -1931,8 +1904,8 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnlyNewlyCreatedParticipantsClea txnRouter.beginOrContinueTxn( operationContext(), txnNum, TransactionRouter::TransactionActions::kContinue); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}); ASSERT(txnRouter.canContinueOnStaleShardOrDbError("find", kDummyStatus)); auto future = launchAsync( @@ -1942,13 +1915,13 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnlyNewlyCreatedParticipantsClea // Shards 2 and 3 must start a transaction, but shard 1 must not. ASSERT_FALSE( - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db")["startTransaction"] + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {})["startTransaction"] .trueValue()); ASSERT_TRUE( - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db")["startTransaction"] + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {})["startTransaction"] .trueValue()); ASSERT_TRUE( - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}, "db")["startTransaction"] + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {})["startTransaction"] .trueValue()); } @@ -1987,8 +1960,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("find" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); auto future = @@ -2001,8 +1973,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("find" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } @@ -2071,7 +2042,7 @@ TEST_F(TransactionRouterTest, AbortForSingleParticipant) { txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); auto future = launchAsync([&] { return txnRouter.abortTransaction(operationContext()); }); @@ -2104,8 +2075,8 @@ TEST_F(TransactionRouterTest, AbortForMultipleParticipantsAllReturnSuccess) { txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -2147,9 +2118,9 @@ TEST_F(TransactionRouterTest, AbortForMultipleParticipantsSomeReturnNoSuchTransa txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard3, kOkReadOnlyFalseResponse); @@ -2196,9 +2167,9 @@ TEST_F(TransactionRouterTest, AbortForMultipleParticipantsSomeReturnNetworkError txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard3, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); txnRouter.processParticipantResponse(operationContext(), shard3, kOkReadOnlyFalseResponse); @@ -2246,7 +2217,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnViewResolutionErrorClearsAllNe txnRouter.setDefaultAtClusterTime(operationContext()); // One shard is targeted by the first statement. - auto firstShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + auto firstShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_TRUE(firstShardCmd["startTransaction"].trueValue()); ASSERT(txnRouter.getCoordinatorId()); @@ -2264,7 +2235,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnViewResolutionErrorClearsAllNe ASSERT_FALSE(txnRouter.getCoordinatorId()); // The first shard is targeted by the retry and should have to start a transaction again. - firstShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + firstShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_TRUE(firstShardCmd["startTransaction"].trueValue()); // Advance to a later client statement that targets a new shard. @@ -2273,7 +2244,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnViewResolutionErrorClearsAllNe txnRouter.beginOrContinueTxn( operationContext(), txnNum, TransactionRouter::TransactionActions::kContinue); - auto secondShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + auto secondShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_TRUE(secondShardCmd["startTransaction"].trueValue()); // Simulate a view resolution error. @@ -2283,9 +2254,9 @@ TEST_F(TransactionRouterTestWithDefaultSession, OnViewResolutionErrorClearsAllNe future.default_timed_get(); // Only the new participant shard was reset. - firstShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + firstShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_FALSE(firstShardCmd["startTransaction"].trueValue()); - secondShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + secondShardCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_TRUE(secondShardCmd["startTransaction"].trueValue()); } @@ -2319,7 +2290,7 @@ TEST_F(TransactionRouterTest, ImplicitAbortForSingleParticipant) { txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); auto future = launchAsync( [&] { return txnRouter.implicitlyAbortTransaction(operationContext(), kDummyStatus); }); @@ -2352,8 +2323,8 @@ TEST_F(TransactionRouterTest, ImplicitAbortForMultipleParticipants) { txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); auto future = launchAsync( [&] { return txnRouter.implicitlyAbortTransaction(operationContext(), kDummyStatus); }); @@ -2393,7 +2364,7 @@ TEST_F(TransactionRouterTest, ImplicitAbortIgnoresErrors) { txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); auto future = launchAsync( [&] { return txnRouter.implicitlyAbortTransaction(operationContext(), kDummyStatus); }); @@ -2414,26 +2385,6 @@ TEST_F(TransactionRouterTest, ImplicitAbortIgnoresErrors) { future.default_timed_get(); } -TEST_F(TransactionRouterTest, CannotContinueAfterCommit) { - LogicalSessionId lsid(makeLogicalSessionIdForTest()); - TxnNumber txnNum{3}; - - auto opCtx = operationContext(); - opCtx->setLogicalSessionId(lsid); - opCtx->setTxnNumber(txnNum); - - RouterOperationContextSession scopedSession(opCtx); - auto txnRouter = TransactionRouter::get(opCtx); - - txnRouter.beginOrContinueTxn( - operationContext(), txnNum, TransactionRouter::TransactionActions::kCommit); - txnRouter.setDefaultAtClusterTime(operationContext()); - - ASSERT_THROWS(txnRouter.beginOrContinueTxn( - opCtx, txnNum, TransactionRouter::TransactionActions::kContinue), - AssertionException); -} - TEST_F(TransactionRouterTestWithDefaultSession, AbortPropagatesWriteConcern) { TxnNumber txnNum{3}; operationContext()->setTxnNumber(txnNum); @@ -2447,7 +2398,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, AbortPropagatesWriteConcern) { txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(opCtx); - txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}); auto future = launchAsync([&] { return txnRouter.abortTransaction(operationContext()); }); @@ -2471,7 +2422,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, ContinueOnlyOnStaleVersionOnFirs txnRouter.beginOrContinueTxn( operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); disableRouterRetriesFailPoint(); @@ -2486,10 +2437,10 @@ TEST_F(TransactionRouterTestWithDefaultSession, ContinueOnlyOnStaleVersionOnFirs txnRouter.onStaleShardOrDbError(operationContext(), "find", kStaleConfigStatus); // Readd the initial participant removed on onStaleShardOrDbError - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); // Add another participant - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); // Check that the transaction cannot continue on stale config with more than one participant ASSERT_FALSE(txnRouter.canContinueOnStaleShardOrDbError("update", kStaleConfigStatus)); @@ -2511,7 +2462,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, ContinueOnlyOnStaleVersionOnFirs operationContext(), txnNum, TransactionRouter::TransactionActions::kContinue); // Cannot retry on a stale config error with one participant after the first statement. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_FALSE(txnRouter.canContinueOnStaleShardOrDbError("update", kStaleConfigStatus)); } @@ -2558,8 +2509,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); // The next statement cannot change the atClusterTime. @@ -2577,8 +2527,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedReadConcern, newCmd["readConcern"].Obj()); } @@ -2618,24 +2567,21 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedRC, newCmd["readConcern"].Obj()); // Only attached on first command to a participant. newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT(newCmd["readConcern"].eoo()); // Attached for new participants after the first one. newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(expectedRC, newCmd["readConcern"].Obj()); } } @@ -2656,8 +2602,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ( BSON("level" << rcIt.first << "afterClusterTime" << clusterTime.asTimestamp()), newCmd["readConcern"].Obj()); @@ -2680,8 +2625,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, NonSnapshotReadConcernLevelsPres auto newCmd = txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, BSON("insert" - << "test"), - "db"); + << "test")); ASSERT_BSONOBJ_EQ(BSON("level" << rcIt.first << "afterOpTime" << opTime), newCmd["readConcern"].Obj()); } @@ -2701,7 +2645,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, // txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT(txnRouter.canContinueOnSnapshotError()); auto future = launchAsync([&] { txnRouter.onSnapshotError(operationContext(), kDummyStatus); }); @@ -2732,7 +2676,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, // txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT(txnRouter.canContinueOnSnapshotError()); auto future = launchAsync([&] { txnRouter.onSnapshotError(operationContext(), kDummyStatus); }); @@ -2765,7 +2709,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, // txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT(txnRouter.canContinueOnSnapshotError()); auto future = launchAsync([&] { @@ -2795,8 +2739,8 @@ DEATH_TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.setDefaultAtClusterTime(operationContext()); // Add some participants to the list. - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard2, {}); // Simulate response from some participant not in the list. txnRouter.processParticipantResponse(operationContext(), shard3, kOkReadOnlyTrueResponse); @@ -2812,7 +2756,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, BSON("ok" << 0)); ASSERT(TransactionRouter::Participant::ReadOnly::kUnset == txnRouter.getParticipant(shard1)->readOnly); @@ -2828,7 +2772,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); const auto participant = txnRouter.getParticipant(shard1); @@ -2854,7 +2798,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); const auto participant = txnRouter.getParticipant(shard1); @@ -2880,7 +2824,7 @@ TEST_F( operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); // First response says readOnly: true. txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); @@ -2907,7 +2851,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); // First response says readOnly: false. txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); @@ -2933,7 +2877,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(operationContext()); - txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(operationContext(), shard1, {}); // First response is an error. txnRouter.processParticipantResponse(operationContext(), shard1, BSON("ok" << 0)); @@ -2971,7 +2915,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(opCtx); - txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}); // Continue causes the _latestStmtId to be bumped. repl::ReadConcernArgs::get(opCtx) = repl::ReadConcernArgs(); @@ -2998,7 +2942,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(opCtx); - txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}); // Aborting will set the termination initiation state. auto future = launchAsync([&] { txnRouter.implicitlyAbortTransaction(opCtx, kDummyStatus); }); @@ -3021,7 +2965,7 @@ TEST_F(TransactionRouterTestWithDefaultSession, txnRouter.beginOrContinueTxn(opCtx, txnNum, TransactionRouter::TransactionActions::kStart); txnRouter.setDefaultAtClusterTime(opCtx); - txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}, "db"); + txnRouter.attachTxnFieldsIfNeeded(opCtx, shard1, {}); // Process !readonly response to set participant state. txnRouter.processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); @@ -3145,92 +3089,6 @@ TEST_F(TransactionRouterTestWithDefaultSession, CannotBeReapedWithActiveYielders ASSERT(txnRouter.canBeReaped()); } -TEST_F(TransactionRouterTestWithDefaultSession, DetectsConflictsDueToMovePrimary) { - _skipConflictPlacementTimestampCheck.reset(); - - const auto kDbName = "testDB"; - const auto kNss = NamespaceString(kDbName, "collA"); - - TxnNumber txnNum{3}; - - const auto runTest = [&]() { - const auto setCatalogCache = [&](Timestamp databaseTimestamp) { - auto refreshFuture = launchAsync([this, kNss] { - auto client = getServiceContext()->makeClient("Test"); - auto const catalogCache = Grid::get(getServiceContext())->catalogCache(); - - uassertStatusOK( - catalogCache->getDatabaseWithRefresh(operationContext(), kNss.db())); - - return boost::make_optional(uassertStatusOK( - catalogCache->getCollectionRoutingInfoWithRefresh(operationContext(), kNss))); - }); - - // Set kDbName with the requested database timestamp. - expectFindSendBSONObjVector(kTestConfigShardHost, [&]() { - DatabaseType db(kDbName, shard1, DatabaseVersion(UUID::gen(), databaseTimestamp)); - return std::vector<BSONObj>{db.toBSON()}; - }()); - - // Set kNss as an unsharded collection. - expectFindSendBSONObjVector(kTestConfigShardHost, - []() { return std::vector<BSONObj>{}; }()); - - refreshFuture.default_timed_get(); - }; - - operationContext()->setTxnNumber(txnNum); - operationContext()->setInMultiDocumentTransaction(); - - auto txnRouter = TransactionRouter::get(operationContext()); - txnRouter.beginOrContinueTxn( - operationContext(), txnNum++, TransactionRouter::TransactionActions::kStart); - txnRouter.setDefaultAtClusterTime(operationContext()); - - // Note the fixture sets VectorClock::clusterTime to Timestamp(3, 1). This will get used by - // the transaction as 'atClusterTime'/'placementConflictTime'. - - // Database timestamp is valid for the transaction. - setCatalogCache(Timestamp(2, 0)); - - ASSERT_DOES_NOT_THROW(txnRouter.attachTxnFieldsIfNeeded(operationContext(), - shard1, - BSON("insert" - << "collA"), - kDbName)); - - // Database timestamp is not valid for the transaction. - setCatalogCache(Timestamp(4, 0)); - - ASSERT_THROWS_CODE(txnRouter.attachTxnFieldsIfNeeded(operationContext(), - shard1, - BSON("insert" - << "collA"), - kDbName), - AssertionException, - ErrorCodes::MigrationConflict); - - // But no error if this transaction created that database. - txnRouter.annotateCreatedDatabase(kDbName); - ASSERT_DOES_NOT_THROW(txnRouter.attachTxnFieldsIfNeeded(operationContext(), - shard1, - BSON("insert" - << "collA"), - kDbName)); - }; - - // Test with non-snapshot read concern. - repl::ReadConcernArgs::get(operationContext()) = - repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern); - runTest(); - - // Test with snapshot read concern. - repl::ReadConcernArgs::get(operationContext()) = - repl::ReadConcernArgs(repl::ReadConcernLevel::kSnapshotReadConcern); - runTest(); -} - - // Begins a transaction with snapshot level read concern and sets a default cluster time. class TransactionRouterTestWithDefaultSessionAndStartedSnapshot : public TransactionRouterTestWithDefaultSession { @@ -3259,8 +3117,7 @@ TEST_F(TransactionRouterTestWithDefaultSessionAndStartedSnapshot, AddAtClusterTi << "testColl" << "readConcern" << BSON("level" - << "snapshot")), - "db"); + << "snapshot"))); ASSERT_BSONOBJ_EQ(rcLatestInMemoryAtClusterTime, newCmd["readConcern"].Obj()); } @@ -3278,8 +3135,7 @@ TEST_F(TransactionRouterTestWithDefaultSessionAndStartedSnapshot, << BSON("level" << "snapshot" << "afterClusterTime" - << existingAfterClusterTime)), - "db"); + << existingAfterClusterTime))); ASSERT_BSONOBJ_EQ(rcLatestInMemoryAtClusterTime, newCmd["readConcern"].Obj()); } @@ -3378,7 +3234,7 @@ protected: // void explicitAbortInProgress() { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse( operationContext(), shard1, kOkReadOnlyFalseResponse); @@ -3390,7 +3246,7 @@ protected: } void implicitAbortInProgress() { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse( operationContext(), shard1, kOkReadOnlyFalseResponse); @@ -3403,7 +3259,7 @@ protected: } void runCommit(StatusWith<BSONObj> swRes, bool expectRetries = false) { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse( operationContext(), shard1, kOkReadOnlyFalseResponse); @@ -3459,7 +3315,7 @@ protected: } void runSingleShardCommit() { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); startCapturingLogMessages(); @@ -3471,9 +3327,9 @@ protected: } void runReadOnlyCommit() { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter().processParticipantResponse(operationContext(), shard2, kOkReadOnlyTrueResponse); startCapturingLogMessages(); @@ -3486,9 +3342,9 @@ protected: } void runSingleWriteShardCommit() { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse(operationContext(), shard1, kOkReadOnlyTrueResponse); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter().processParticipantResponse( operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -3501,10 +3357,10 @@ protected: } void runTwoPhaseCommit() { - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse( operationContext(), shard1, kOkReadOnlyFalseResponse); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter().processParticipantResponse( operationContext(), shard2, kOkReadOnlyFalseResponse); @@ -3543,7 +3399,7 @@ protected: auto beginAndPauseCommit() { // Commit after targeting one shard so the commit has to do work and can be paused. - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse( operationContext(), shard1, kOkReadOnlyFalseResponse); auto future = launchAsync( @@ -4655,6 +4511,17 @@ TEST_F(TransactionRouterMetricsTest, RouterMetricsCurrent_Stash) { ASSERT_EQUALS(1L, routerTxnMetrics()->getCurrentInactive()); } +TEST_F(TransactionRouterMetricsTest, RouterMetricsCurrent_BeginAfterStash) { + beginRecoverCommitWithDefaultTxnNumber(); + txnRouter().stash(operationContext(), TransactionRouter::StashReason::kDone); + txnRouter().beginOrContinueTxn( + operationContext(), kTxnNumber, TransactionRouter::TransactionActions::kContinue); + + ASSERT_EQUALS(1L, routerTxnMetrics()->getCurrentOpen()); + ASSERT_EQUALS(1L, routerTxnMetrics()->getCurrentActive()); + ASSERT_EQUALS(0L, routerTxnMetrics()->getCurrentInactive()); +} + TEST_F(TransactionRouterMetricsTest, RouterMetricsCurrent_AreNotCumulative) { // Test active. beginTxnWithDefaultTxnNumber(); @@ -5072,14 +4939,14 @@ TEST_F(TransactionRouterMetricsTest, RouterMetricsTotalContactedParticipants) { beginTxnWithDefaultTxnNumber(); ASSERT_EQUALS(0L, routerTxnMetrics()->getTotalContactedParticipants()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_EQUALS(1L, routerTxnMetrics()->getTotalContactedParticipants()); // Only increases for new participants. - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_EQUALS(1L, routerTxnMetrics()->getTotalContactedParticipants()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); ASSERT_EQUALS(2L, routerTxnMetrics()->getTotalContactedParticipants()); // Is cumulative across transactions. @@ -5087,7 +4954,7 @@ TEST_F(TransactionRouterMetricsTest, RouterMetricsTotalContactedParticipants) { operationContext(), kTxnNumber + 1, TransactionRouter::TransactionActions::kStart); ASSERT_EQUALS(2L, routerTxnMetrics()->getTotalContactedParticipants()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); ASSERT_EQUALS(3L, routerTxnMetrics()->getTotalContactedParticipants()); } @@ -5100,15 +4967,15 @@ TEST_F(TransactionRouterMetricsTest, RouterMetricsTotalRequestsTargeted) { ASSERT_EQUALS(0L, routerTxnMetrics()->getTotalRequestsTargeted()); // Increases each time transaction fields are attached. - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT_EQUALS(1L, routerTxnMetrics()->getTotalRequestsTargeted()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT_EQUALS(2L, routerTxnMetrics()->getTotalRequestsTargeted()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter().processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); ASSERT_EQUALS(3L, routerTxnMetrics()->getTotalRequestsTargeted()); } @@ -5148,11 +5015,11 @@ TEST_F(TransactionRouterMetricsTest, RouterMetricsTotalParticipantsAtCommit) { beginTxnWithDefaultTxnNumber(); ASSERT_EQUALS(0L, routerTxnMetrics()->getTotalParticipantsAtCommit()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); txnRouter().processParticipantResponse(operationContext(), shard1, kOkReadOnlyFalseResponse); ASSERT_EQUALS(0L, routerTxnMetrics()->getTotalParticipantsAtCommit()); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); txnRouter().processParticipantResponse(operationContext(), shard2, kOkReadOnlyFalseResponse); ASSERT_EQUALS(0L, routerTxnMetrics()->getTotalParticipantsAtCommit()); @@ -5169,7 +5036,7 @@ TEST_F(TransactionRouterMetricsTest, RouterMetricsTotalParticipantsAtCommit) { operationContext()->setTxnNumber(kTxnNumber + 1); txnRouter().beginOrContinueTxn( operationContext(), kTxnNumber + 1, TransactionRouter::TransactionActions::kStart); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); runCommit(kDummyOkRes); ASSERT_EQUALS(3L, routerTxnMetrics()->getTotalParticipantsAtCommit()); } @@ -5333,8 +5200,8 @@ TEST_F(TransactionRouterMetricsTest, ReportResourcesWithParticipantList) { clockSource->reset(startTime); beginTxnWithDefaultTxnNumber(); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}, "db"); - txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}, "db"); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard1, {}); + txnRouter().attachTxnFieldsIfNeeded(operationContext(), shard2, {}); auto state = txnRouter().reportState(operationContext(), true /* sessionIsActive */); auto transactionDocument = state.getObjectField("transaction"); diff --git a/src/mongo/s/write_ops/batch_write_exec.cpp b/src/mongo/s/write_ops/batch_write_exec.cpp index 980d376b607..45b89579dbb 100644 --- a/src/mongo/s/write_ops/batch_write_exec.cpp +++ b/src/mongo/s/write_ops/batch_write_exec.cpp @@ -104,25 +104,6 @@ bool hasTransientTransactionError(const BatchedCommandResponse& response) { // applies when no writes are occurring and metadata is not changing on reload. const int kMaxRoundsWithoutProgress(5); -/** - * Provides the write concern with which child batches have to be internally submitted. - */ -boost::optional<WriteConcernOptions> getWriteConcernForChildBatch(OperationContext* opCtx) { - // Per-operation write concern is not supported in transactions. - if (TransactionRouter::get(opCtx)) { - return boost::none; - } - - // Retrieve the WC specified by the remote client; in case of "fire and forget" request, the WC - // needs to be upgraded to "w: 1" for the sharding protocol to correctly handle internal - // writeErrors. - auto wc = opCtx->getWriteConcern(); - if (!wc.requiresWriteAcknowledgement()) { - wc.w = 1; - } - - return wc; -} } // namespace void BatchWriteExec::executeBatch(OperationContext* opCtx, @@ -214,7 +195,6 @@ void BatchWriteExec::executeBatch(OperationContext* opCtx, // std::vector<AsyncRequestsSender::Request> requests; - const auto wcSettingForChildBatch = getWriteConcernForChildBatch(opCtx); // Get as many batches as we can at once for (auto&& childBatch : childBatches) { @@ -237,10 +217,6 @@ void BatchWriteExec::executeBatch(OperationContext* opCtx, BSONObjBuilder requestBuilder; shardBatchRequest.serialize(&requestBuilder); logical_session_id_helpers::serializeLsidAndTxnNumber(opCtx, &requestBuilder); - if (wcSettingForChildBatch) { - requestBuilder.append(WriteConcernOptions::kWriteConcernField, - wcSettingForChildBatch->toBSON()); - } return requestBuilder.obj(); }(); diff --git a/src/mongo/s/write_ops/batch_write_exec_test.cpp b/src/mongo/s/write_ops/batch_write_exec_test.cpp index 3e90e5b65d3..3b4079b4c84 100644 --- a/src/mongo/s/write_ops/batch_write_exec_test.cpp +++ b/src/mongo/s/write_ops/batch_write_exec_test.cpp @@ -33,14 +33,12 @@ #include "mongo/client/remote_command_targeter_factory_mock.h" #include "mongo/client/remote_command_targeter_mock.h" #include "mongo/db/commands.h" -#include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/logical_session_id.h" #include "mongo/db/vector_clock.h" #include "mongo/s/catalog/type_shard.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/mock_ns_targeter.h" #include "mongo/s/session_catalog_router.h" -#include "mongo/s/shard_cannot_refresh_due_to_locks_held_exception.h" #include "mongo/s/sharding_router_test_fixture.h" #include "mongo/s/stale_exception.h" #include "mongo/s/transaction_router.h" @@ -202,40 +200,6 @@ BSONObj expectInsertsReturnTenantMigrationAbortedErrorsBase( return tenantMigrationAbortedResponse.obj(); } -BSONObj expectInsertsReturnCannotRefreshErrorsBase(const NamespaceString& nss, - const std::vector<BSONObj>& expected, - const executor::RemoteCommandRequest& request) { - ASSERT_EQUALS(nss.db(), request.dbname); - - const auto opMsgRequest(OpMsgRequest::fromDBAndBody(request.dbname, request.cmdObj)); - const auto actualBatchedInsert(BatchedCommandRequest::parseInsert(opMsgRequest)); - ASSERT_EQUALS(nss.toString(), actualBatchedInsert.getNS().ns()); - - const auto& inserted = actualBatchedInsert.getInsertRequest().getDocuments(); - ASSERT_EQUALS(expected.size(), inserted.size()); - - auto itInserted = inserted.begin(); - auto itExpected = expected.begin(); - - for (; itInserted != inserted.end(); itInserted++, itExpected++) { - ASSERT_BSONOBJ_EQ(*itExpected, *itInserted); - } - - BatchedCommandResponse cannotRefreshResponse; - cannotRefreshResponse.setStatus(Status::OK()); - cannotRefreshResponse.setN(0); - - // Report a ShardCannotRefreshDueToLocksHeld error for each write in the batch. - int i = 0; - for (itInserted = inserted.begin(); itInserted != inserted.end(); ++itInserted) { - cannotRefreshResponse.addToErrDetails(write_ops::WriteError( - i, Status(ShardCannotRefreshDueToLocksHeldInfo(nss), "Catalog cache busy in refresh"))); - ++i; - } - - return cannotRefreshResponse.toBSON(); -} - /** * Mimics a single shard backend for a particular collection which can be initialized with a * set of write command results to return. @@ -335,12 +299,6 @@ public: }); } - void expectInsertsReturnCannotRefreshErrors(const std::vector<BSONObj>& expected) { - onCommandForPoolExecutor([&](const executor::RemoteCommandRequest& request) { - return expectInsertsReturnCannotRefreshErrorsBase(nss, expected, request); - }); - } - void expectInsertsReturnError(const std::vector<BSONObj>& expected, const BatchedCommandResponse& errResponse) { onCommandForPoolExecutor([&](const executor::RemoteCommandRequest& request) { @@ -397,6 +355,7 @@ TEST_F(BatchWriteExecTest, SingleOpUnordered) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Do single-target, single doc batch write op auto future = launchAsync([&] { @@ -435,6 +394,7 @@ TEST_F(BatchWriteExecTest, SingleUpdateTargetsShardWithLet) { << "100")))}); return updateOp; }()); + updateRequest.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -520,6 +480,7 @@ TEST_F(BatchWriteExecTest, SingleDeleteTargetsShardWithLet) { deleteOp.setDeletes(std::vector{write_ops::DeleteOpEntry(q, false)}); return deleteOp; }()); + deleteRequest.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); @@ -605,6 +566,7 @@ TEST_F(BatchWriteExecTest, MultiOpLargeOrdered) { insertOp.setDocuments(docsToInsert); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -637,6 +599,7 @@ TEST_F(BatchWriteExecTest, SingleOpUnorderedError) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -677,6 +640,7 @@ TEST_F(BatchWriteExecTest, MultiOpLargeUnorderedWithStaleShardVersionError) { insertOp.setDocuments(docsToInsert); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -708,6 +672,7 @@ TEST_F(BatchWriteExecTest, StaleShardVersionReturnedFromBatchWithSingleMultiWrit write_ops::UpdateModification::parseFromClassicUpdate(BSON("Key" << 100)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -785,43 +750,6 @@ TEST_F(BatchWriteExecTest, StaleShardVersionReturnedFromBatchWithSingleMultiWrit ASSERT_EQ(3, response.getNModified()); } -TEST_F(BatchWriteExecTest, MultiOpLargeUnorderedWithCannotRefreshError) { - const int kNumDocsToInsert = 100'000; - - std::vector<BSONObj> docsToInsert; - docsToInsert.reserve(kNumDocsToInsert); - for (int i = 0; i < kNumDocsToInsert; i++) { - docsToInsert.push_back(BSON("_id" << i)); - } - - BatchedCommandRequest request([&] { - write_ops::InsertCommandRequest insertOp(nss); - insertOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(false); - return writeCommandBase; - }()); - insertOp.setDocuments(docsToInsert); - return insertOp; - }()); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - BatchWriteExec::executeBatch( - operationContext(), singleShardNSTargeter, request, &response, &stats); - - ASSERT(response.getOk()); - ASSERT_EQ(kNumDocsToInsert, response.getN()); - }); - - expectInsertsReturnCannotRefreshErrors({docsToInsert.begin(), docsToInsert.begin() + 60133}); - expectInsertsReturnSuccess({docsToInsert.begin(), docsToInsert.begin() + 60133}); - expectInsertsReturnSuccess({docsToInsert.begin() + 60133, docsToInsert.end()}); - - future.default_timed_get(); -} - TEST_F(BatchWriteExecTest, RetryableErrorReturnedFromMultiWriteWithShard1AllOKShard2AllStaleShardVersion) { BatchedCommandRequest request([&] { @@ -840,6 +768,7 @@ TEST_F(BatchWriteExecTest, write_ops::UpdateModification::parseFromClassicUpdate(BSON("y" << 2)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -943,6 +872,7 @@ TEST_F(BatchWriteExecTest, RetryableErrorReturnedFromMultiWriteWithShard1Firs) { write_ops::UpdateModification::parseFromClassicUpdate(BSON("y" << 2)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -1056,6 +986,7 @@ TEST_F(BatchWriteExecTest, RetryableErrorReturnedFromMultiWriteWithShard1FirstOK write_ops::UpdateModification::parseFromClassicUpdate(BSON("y" << 2)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -1165,6 +1096,7 @@ TEST_F(BatchWriteExecTest, RetryableErrorReturnedFromWriteWithShard1SSVShard2OK) write_ops::UpdateModification::parseFromClassicUpdate(BSON("x" << 1)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -1264,6 +1196,7 @@ TEST_F(BatchWriteExecTest, StaleShardOp) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Execute request auto future = launchAsync([&] { @@ -1295,6 +1228,7 @@ TEST_F(BatchWriteExecTest, MultiStaleShardOp) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -1332,6 +1266,7 @@ TEST_F(BatchWriteExecTest, TooManyStaleShardOp) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -1366,6 +1301,7 @@ TEST_F(BatchWriteExecTest, StaleDbOp) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Execute request auto future = launchAsync([&] { @@ -1397,6 +1333,7 @@ TEST_F(BatchWriteExecTest, MultiStaleDbOp) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -1434,6 +1371,7 @@ TEST_F(BatchWriteExecTest, TooManyStaleDbOp) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -1457,73 +1395,6 @@ TEST_F(BatchWriteExecTest, TooManyStaleDbOp) { future.default_timed_get(); } -TEST_F(BatchWriteExecTest, MultiCannotRefreshShardOp) { - BatchedCommandRequest request([&] { - write_ops::InsertCommandRequest insertOp(nss); - insertOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(false); - return writeCommandBase; - }()); - insertOp.setDocuments({BSON("x" << 1)}); - return insertOp; - }()); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - BatchWriteExec::executeBatch( - operationContext(), singleShardNSTargeter, request, &response, &stats); - ASSERT(response.getOk()); - }); - - const std::vector<BSONObj> expected{BSON("x" << 1)}; - - // Return multiple ShardCannotRefreshDueToLocksHeld errors, but less than the give-up number - for (int i = 0; i < 3; i++) { - expectInsertsReturnCannotRefreshErrors(expected); - } - - expectInsertsReturnSuccess(expected); - - future.default_timed_get(); -} - -TEST_F(BatchWriteExecTest, TooManyCannotRefreshShardOp) { - // Retry op in exec too many times b/c of busy catalog cache (the error is not expected to - // trigger a refresh on any implementation of NSTargeter). We should report a no progress error - // for everything in the batch. - BatchedCommandRequest request([&] { - write_ops::InsertCommandRequest insertOp(nss); - insertOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(false); - return writeCommandBase; - }()); - insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); - return insertOp; - }()); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - BatchWriteExec::executeBatch( - operationContext(), singleShardNSTargeter, request, &response, &stats); - ASSERT(response.getOk()); - ASSERT_EQ(0, response.getN()); - ASSERT(response.isErrDetailsSet()); - ASSERT_EQUALS(response.getErrDetailsAt(0).getStatus().code(), ErrorCodes::NoProgressMade); - ASSERT_EQUALS(response.getErrDetailsAt(1).getStatus().code(), ErrorCodes::NoProgressMade); - }); - - // Return multiple StaleShardVersion errors - for (int i = 0; i < (1 + kMaxRoundsWithoutProgress); i++) { - expectInsertsReturnCannotRefreshErrors({BSON("x" << 1), BSON("x" << 2)}); - } - - future.default_timed_get(); -} - TEST_F(BatchWriteExecTest, RetryableWritesLargeBatch) { // A retryable error without a txnNumber is not retried. @@ -1546,6 +1417,7 @@ TEST_F(BatchWriteExecTest, RetryableWritesLargeBatch) { insertOp.setDocuments(docsToInsert); return insertOp; }()); + request.setWriteConcern(BSONObj()); operationContext()->setLogicalSessionId(makeLogicalSessionIdForTest()); operationContext()->setTxnNumber(5); @@ -1580,6 +1452,7 @@ TEST_F(BatchWriteExecTest, RetryableErrorNoTxnNumber) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); BatchedCommandResponse retryableErrResponse; retryableErrResponse.setStatus({ErrorCodes::NotWritablePrimary, "mock retryable error"}); @@ -1618,6 +1491,7 @@ TEST_F(BatchWriteExecTest, RetryableErrorTxnNumber) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); operationContext()->setLogicalSessionId(makeLogicalSessionIdForTest()); operationContext()->setTxnNumber(5); @@ -1655,6 +1529,7 @@ TEST_F(BatchWriteExecTest, NonRetryableErrorTxnNumber) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); operationContext()->setLogicalSessionId(makeLogicalSessionIdForTest()); operationContext()->setTxnNumber(5); @@ -1696,6 +1571,7 @@ TEST_F(BatchWriteExecTest, StaleEpochIsNotRetryable) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); operationContext()->setLogicalSessionId(makeLogicalSessionIdForTest()); operationContext()->setTxnNumber(5); @@ -1723,249 +1599,6 @@ TEST_F(BatchWriteExecTest, StaleEpochIsNotRetryable) { future.default_timed_get(); } -TEST_F(BatchWriteExecTest, FireAndForgetBatchInsertGetsReplyWithOnlyOkStatus) { - const int kNumDocsToInsert = 5; - const std::string kDocValue("sample"); - - std::vector<BSONObj> docsToInsert; - docsToInsert.reserve(kNumDocsToInsert); - for (int i = 0; i < kNumDocsToInsert; i++) { - docsToInsert.push_back(BSON("_id" << i << "otherField" << kDocValue)); - } - - BatchedCommandRequest request([&] { - write_ops::InsertCommandRequest insertOp(nss); - insertOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(true); - return writeCommandBase; - }()); - insertOp.setDocuments(docsToInsert); - return insertOp; - }()); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - // Set Unacknowledged WC for a "fire & forget" request - auto opCtx = operationContext(); - opCtx->setWriteConcern( - WriteConcernOptions::parse(WriteConcernOptions::Unacknowledged).getValue()); - BatchWriteExec::executeBatch(opCtx, singleShardNSTargeter, request, &response, &stats); - - // The reply should only contain an OK status, without any further detail on - // the ops actually executed across the cluster. - BatchedCommandResponse expectedReplyToFireAndForgetRequest; - expectedReplyToFireAndForgetRequest.setStatus(Status::OK()); - ASSERT_EQUALS(response.toBSON().woCompare(expectedReplyToFireAndForgetRequest.toBSON()), 0); - }); - - expectInsertsReturnSuccess(docsToInsert.begin(), docsToInsert.end()); - - future.default_timed_get(); -} - -TEST_F(BatchWriteExecTest, FireAndForgetBatchUpdateGetsReplyWithOnlyOkStatus) { - BatchedCommandRequest request([&] { - write_ops::UpdateCommandRequest updateOp(nss); - updateOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(false); - return writeCommandBase; - }()); - updateOp.setUpdates(std::vector{write_ops::UpdateOpEntry( - BSON("_id" << 100), - write_ops::UpdateModification::parseFromClassicUpdate(BSON("Key" << 100)))}); - return updateOp; - }()); - - const static auto epoch = OID::gen(); - const static Timestamp timestamp(2); - - // This allows the batch to target each write operation to perform this test - class MultiShardTargeter : public MockNSTargeter { - public: - using MockNSTargeter::MockNSTargeter; - - std::vector<ShardEndpoint> targetUpdate(OperationContext* opCtx, - const BatchItemRef& itemRef) const override { - if (targetAll) { - return std::vector{ - ShardEndpoint( - kShardName1, ChunkVersion(100, 200, epoch, timestamp), boost::none), - ShardEndpoint( - kShardName2, ChunkVersion(101, 200, epoch, timestamp), boost::none)}; - } else { - return std::vector{ShardEndpoint( - kShardName2, ChunkVersion(101, 200, epoch, timestamp), boost::none)}; - } - } - - bool targetAll = true; - }; - - MultiShardTargeter multiShardNSTargeter( - nss, - {MockRange( - ShardEndpoint(kShardName1, ChunkVersion(100, 200, epoch, timestamp), boost::none), - BSON("sk" << MINKEY), - BSON("sk" << 10)), - MockRange( - ShardEndpoint(kShardName2, ChunkVersion(101, 200, epoch, timestamp), boost::none), - BSON("sk" << 10), - BSON("sk" << MAXKEY))}); - auto future = launchAsync([&] { - // Set Unacknowledged WC for a "fire & forget" request - auto opCtx = operationContext(); - opCtx->setWriteConcern( - WriteConcernOptions::parse(WriteConcernOptions::Unacknowledged).getValue()); - - BatchedCommandResponse response; - BatchWriteExecStats stats; - BatchWriteExec::executeBatch(opCtx, multiShardNSTargeter, request, &response, &stats); - return response; - }); - - onCommandForPoolExecutor([&](const RemoteCommandRequest& request) { - ASSERT_EQ(kTestShardHost1, request.target); - - BatchedCommandResponse response; - response.setStatus(Status::OK()); - response.setNModified(1); - - return response.toBSON(); - }); - - onCommandForPoolExecutor([&](const RemoteCommandRequest& request) { - ASSERT_EQ(kTestShardHost2, request.target); - - BatchedCommandResponse response; - response.setStatus(Status::OK()); - response.setNModified(0); - response.addToErrDetails( - write_ops::WriteError(0, - Status(StaleConfigInfo(nss, - ChunkVersion(101, 200, epoch, timestamp), - ChunkVersion(105, 200, epoch, timestamp), - ShardId(kShardName2)), - "Stale error"))); - return response.toBSON(); - }); - - onCommandForPoolExecutor([&](const RemoteCommandRequest& request) { - ASSERT_EQ(kTestShardHost2, request.target); - - BatchedCommandResponse response; - response.setStatus(Status::OK()); - response.setNModified(2); - - return response.toBSON(); - }); - - // The reply should only contain an OK status, without any further detail on - // the ops actually executed across the cluster. - // (Despite of a "fire and forget" request, child batches still need to be internally processed - // before returning a reply). - auto response = future.default_timed_get(); - BatchedCommandResponse expectedReplyToFireAndForgetRequest; - expectedReplyToFireAndForgetRequest.setStatus(Status::OK()); - ASSERT_EQUALS(response.toBSON().woCompare(expectedReplyToFireAndForgetRequest.toBSON()), 0); -} - -TEST_F(BatchWriteExecTest, FireAndForgetBatchDeleteGetsReplyWithOnlyOkStatus) { - // Try to update the single doc where a let param is used in the shard key. - const auto let = BSON("y" << 100); - const auto rtc = LegacyRuntimeConstants{Date_t::now(), Timestamp(1, 1)}; - const auto q = BSON("x" - << "$$y"); - BatchedCommandRequest deleteRequest([&] { - write_ops::DeleteCommandRequest deleteOp(nss); - deleteOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(false); - return writeCommandBase; - }()); - deleteOp.setLet(let); - deleteOp.setLegacyRuntimeConstants(rtc); - deleteOp.setDeletes(std::vector{write_ops::DeleteOpEntry(q, false)}); - return deleteOp; - }()); - - const static auto epoch = OID::gen(); - - class MultiShardTargeter : public MockNSTargeter { - public: - using MockNSTargeter::MockNSTargeter; - - protected: - std::vector<ShardEndpoint> targetDelete(OperationContext* opCtx, - const BatchItemRef& itemRef) const override { - return std::vector{ShardEndpoint( - kShardName2, ChunkVersion(101, 200, epoch, Timestamp(1, 1)), boost::none)}; - } - }; - - MultiShardTargeter multiShardNSTargeter( - nss, - {MockRange(ShardEndpoint( - kShardName1, ChunkVersion(100, 200, epoch, Timestamp(1, 1)), boost::none), - BSON("x" << MINKEY), - BSON("x" << 0)), - MockRange(ShardEndpoint( - kShardName2, ChunkVersion(101, 200, epoch, Timestamp(1, 1)), boost::none), - BSON("x" << 0), - BSON("x" << MAXKEY))}); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - - // Set Unacknowledged WC for a "fire & forget" request - auto opCtx = operationContext(); - opCtx->setWriteConcern( - WriteConcernOptions::parse(WriteConcernOptions::Unacknowledged).getValue()); - - BatchWriteExec::executeBatch(opCtx, multiShardNSTargeter, deleteRequest, &response, &stats); - - return response; - }); - - // The update will hit the first shard. - onCommandForPoolExecutor( - [&](const RemoteCommandRequest& request) { - ASSERT_EQ(kTestShardHost2, request.target); - - BatchedCommandResponse response; - response.setStatus(Status::OK()); - - // Check that let params are propagated to shards. - const auto opMsgRequest(OpMsgRequest::fromDBAndBody(request.dbname, request.cmdObj)); - const auto actualBatchedUpdate(BatchedCommandRequest::parseDelete(opMsgRequest)); - ASSERT_BSONOBJ_EQ(let, actualBatchedUpdate.getLet().value_or(BSONObj())); - ASSERT_EQUALS(actualBatchedUpdate.getLegacyRuntimeConstants()->getLocalNow(), - rtc.getLocalNow()); - ASSERT_EQUALS(actualBatchedUpdate.getLegacyRuntimeConstants()->getClusterTime(), - rtc.getClusterTime()); - - // Check that let params are only forwarded and not evaluated. - auto expectedQ = BSON("x" - << "$$y"); - for (auto&& u : actualBatchedUpdate.getDeleteRequest().getDeletes()) - ASSERT_BSONOBJ_EQ(expectedQ, u.getQ()); - - return response.toBSON(); - }); - - // The reply should only contain an OK status, without any further detail on - // the ops actually executed across the cluster. - // (Despite of a "fire and forget" request, child batches still need to be internally processed - // before returning a reply). - auto response = future.default_timed_get(); - BatchedCommandResponse expectedReplyToFireAndForgetRequest; - expectedReplyToFireAndForgetRequest.setStatus(Status::OK()); - ASSERT_EQUALS(response.toBSON().woCompare(expectedReplyToFireAndForgetRequest.toBSON()), 0); -} - TEST_F(BatchWriteExecTest, TenantMigrationAbortedErrorOrderedOp) { const std::vector<BSONObj> expected{BSON("x" << 1), BSON("x" << 2), BSON("x" << 3)}; BatchedCommandRequest request([&] { @@ -1978,6 +1611,7 @@ TEST_F(BatchWriteExecTest, TenantMigrationAbortedErrorOrderedOp) { insertOp.setDocuments(expected); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Execute request auto future = launchAsync([&] { @@ -2008,6 +1642,7 @@ TEST_F(BatchWriteExecTest, TenantMigrationAbortedErrorUnorderedOp) { insertOp.setDocuments(expected); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Execute request auto future = launchAsync([&] { @@ -2038,6 +1673,7 @@ TEST_F(BatchWriteExecTest, MultipleTenantMigrationAbortedErrorUnorderedOp) { insertOp.setDocuments(expected); return insertOp; }()); + request.setWriteConcern(BSONObj()); const int numTenantMigrationAbortedErrors = 3; @@ -2072,6 +1708,7 @@ TEST_F(BatchWriteExecTest, MultipleTenantMigrationAbortedErrorOrderedOp) { insertOp.setDocuments(expected); return insertOp; }()); + request.setWriteConcern(BSONObj()); const int numTenantMigrationAbortedErrors = 3; @@ -2106,6 +1743,7 @@ TEST_F(BatchWriteExecTest, PartialTenantMigrationAbortedErrorOrderedOp) { insertOp.setDocuments(expected); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Execute request auto future = launchAsync([&] { @@ -2138,6 +1776,7 @@ TEST_F(BatchWriteExecTest, PartialTenantMigrationErrorUnorderedOp) { insertOp.setDocuments(expected); return insertOp; }()); + request.setWriteConcern(BSONObj()); // Execute request auto future = launchAsync([&] { @@ -2222,6 +1861,7 @@ TEST_F(BatchWriteExecTargeterErrorTest, TargetedFailedAndErrorResponse) { write_ops::UpdateModification::parseFromClassicUpdate(BSON("Key" << 100)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -2357,6 +1997,7 @@ TEST_F(BatchWriteExecTransactionTargeterErrorTest, TargetedFailedAndErrorRespons write_ops::UpdateModification::parseFromClassicUpdate(BSON("Key" << 100)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -2500,6 +2141,7 @@ TEST_F(BatchWriteExecTransactionMultiShardTest, TargetedSucceededAndErrorRespons write_ops::UpdateModification::parseFromClassicUpdate(BSON("Key" << 100)))}); return updateOp; }()); + request.setWriteConcern(BSONObj()); const static auto epoch = OID::gen(); const static Timestamp timestamp(2); @@ -2625,22 +2267,6 @@ public: }); } - void expectInsertsReturnCannotRefreshErrors(const std::vector<BSONObj>& expected) { - onCommandForPoolExecutor([&](const executor::RemoteCommandRequest& request) { - BSONObjBuilder bob; - - bob.appendElementsUnique( - expectInsertsReturnCannotRefreshErrorsBase(nss, expected, request)); - - // Because this is the transaction-specific fixture, return transaction metadata in - // the response. - TxnResponseMetadata txnResponseMetadata(false /* readOnly */); - txnResponseMetadata.serialize(&bob); - - return bob.obj(); - }); - } - void expectInsertsReturnTransientTxnErrors(const std::vector<BSONObj>& expected) { onCommandForPoolExecutor([&](const executor::RemoteCommandRequest& request) { ASSERT_EQUALS(nss.db(), request.dbname); @@ -2690,6 +2316,7 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchThrows_CommandError) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -2721,6 +2348,7 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_WriteError) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -2750,6 +2378,7 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_WriteErrorOrdered) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -2768,64 +2397,6 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_WriteErrorOrdered) { future.default_timed_get(); } -TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_WriteErrorFromBusyCache) { - BatchedCommandRequest request([&] { - write_ops::InsertCommandRequest insertOp(nss); - insertOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(false); - return writeCommandBase; - }()); - insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); - return insertOp; - }()); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - BatchWriteExec::executeBatch( - operationContext(), singleShardNSTargeter, request, &response, &stats); - - ASSERT(response.isErrDetailsSet()); - ASSERT_GT(response.sizeErrDetails(), 0u); - ASSERT_EQ(ErrorCodes::ShardCannotRefreshDueToLocksHeld, - response.getErrDetailsAt(0).getStatus().code()); - }); - - expectInsertsReturnCannotRefreshErrors({BSON("x" << 1), BSON("x" << 2)}); - - future.default_timed_get(); -} - -TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_WriteErrorOrderedFromBusyCache) { - BatchedCommandRequest request([&] { - write_ops::InsertCommandRequest insertOp(nss); - insertOp.setWriteCommandRequestBase([] { - write_ops::WriteCommandRequestBase writeCommandBase; - writeCommandBase.setOrdered(true); - return writeCommandBase; - }()); - insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); - return insertOp; - }()); - - auto future = launchAsync([&] { - BatchedCommandResponse response; - BatchWriteExecStats stats; - BatchWriteExec::executeBatch( - operationContext(), singleShardNSTargeter, request, &response, &stats); - - ASSERT(response.isErrDetailsSet()); - ASSERT_GT(response.sizeErrDetails(), 0u); - ASSERT_EQ(ErrorCodes::ShardCannotRefreshDueToLocksHeld, - response.getErrDetailsAt(0).getStatus().code()); - }); - - expectInsertsReturnCannotRefreshErrors({BSON("x" << 1), BSON("x" << 2)}); - - future.default_timed_get(); -} - TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_TransientTxnError) { BatchedCommandRequest request([&] { write_ops::InsertCommandRequest insertOp(nss); @@ -2837,13 +2408,16 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_TransientTxnError) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; BatchWriteExecStats stats; - ASSERT_THROWS(BatchWriteExec::executeBatch( - operationContext(), singleShardNSTargeter, request, &response, &stats), - WriteConflictException); + ASSERT_THROWS_CODE( + BatchWriteExec::executeBatch( + operationContext(), singleShardNSTargeter, request, &response, &stats), + AssertionException, + ErrorCodes::WriteConflict); }); expectInsertsReturnTransientTxnErrors({BSON("x" << 1), BSON("x" << 2)}); @@ -2862,6 +2436,7 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_DispatchError) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; @@ -2893,6 +2468,7 @@ TEST_F(BatchWriteExecTransactionTest, ErrorInBatchSets_TransientDispatchError) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSONObj()); auto future = launchAsync([&] { BatchedCommandResponse response; diff --git a/src/mongo/s/write_ops/batch_write_op.cpp b/src/mongo/s/write_ops/batch_write_op.cpp index ed67f6c0088..a61ee3dd4bf 100644 --- a/src/mongo/s/write_ops/batch_write_op.cpp +++ b/src/mongo/s/write_ops/batch_write_op.cpp @@ -59,6 +59,7 @@ struct WriteErrorComp { // batches before serializing. // // TODO: Revisit when we revisit command limits in general +const int kEstUpdateOverheadBytes = (BSONObjMaxInternalSize - BSONObjMaxUserSize) / 100; const int kEstDeleteOverheadBytes = (BSONObjMaxInternalSize - BSONObjMaxUserSize) / 100; /** @@ -158,17 +159,51 @@ int getWriteSizeBytes(const WriteOp& writeOp) { return item.getDocument().objsize(); } else if (batchType == BatchedCommandRequest::BatchType_Update) { // Note: Be conservative here - it's okay if we send slightly too many batches. - const auto& update = item.getUpdate(); - auto estSize = write_ops::getUpdateSizeEstimate(update.getQ(), - update.getU(), - update.getC(), - update.getUpsertSupplied().has_value(), - update.getCollation(), - update.getArrayFilters(), - update.getHint()); + auto estSize = static_cast<int>(BSONObj::kMinBSONLength); + static const auto boolSize = 1; + + // Add the size of the 'collation' field, if present. + estSize += !item.getUpdate().getCollation() ? 0 + : (UpdateOpEntry::kCollationFieldName.size() + + item.getUpdate().getCollation()->objsize()); + + // Add the size of the 'arrayFilters' field, if present. + estSize += !item.getUpdate().getArrayFilters() ? 0 : ([&item]() { + auto size = BSONObj::kMinBSONLength + UpdateOpEntry::kArrayFiltersFieldName.size(); + for (auto&& filter : *item.getUpdate().getArrayFilters()) { + size += filter.objsize(); + } + return size; + })(); + + // Add the sizes of the 'multi' and 'upsert' fields. + estSize += UpdateOpEntry::kUpsertFieldName.size() + boolSize; + estSize += UpdateOpEntry::kMultiFieldName.size() + boolSize; + + // Add the size of 'upsertSupplied' field if present. + if (auto upsertSupplied = item.getUpdate().getUpsertSupplied()) { + estSize += UpdateOpEntry::kUpsertSuppliedFieldName.size() + boolSize; + } + + // Add the sizes of the 'q' and 'u' fields. + estSize += (UpdateOpEntry::kQFieldName.size() + item.getUpdate().getQ().objsize() + + UpdateOpEntry::kUFieldName.size() + item.getUpdate().getU().objsize()); + + // Add the size of the 'c' field if present. + if (auto constants = item.getUpdate().getC()) { + estSize += UpdateOpEntry::kCFieldName.size() + item.getUpdate().getC()->objsize(); + } + + // Add the size of 'hint' field if present. + if (auto hint = item.getUpdate().getHint(); !hint.isEmpty()) { + estSize += UpdateOpEntry::kHintFieldName.size() + hint.objsize(); + } + + // Finally, add the constant updateOp overhead size. + estSize += kEstUpdateOverheadBytes; // When running a debug build, verify that estSize is at least the BSON serialization size. - dassert(estSize >= update.toBSON().objsize()); + dassert(estSize >= item.getUpdate().toBSON().objsize()); return estSize; } else if (batchType == BatchedCommandRequest::BatchType_Delete) { // Note: Be conservative here - it's okay if we send slightly too many batches. @@ -568,9 +603,6 @@ BatchedCommandRequest BatchWriteOp::buildBatchRequest(const TargetedWriteBatch& wcb.setStmtIds(std::move(stmtIdsForOp)); } - wcb.setBypassEmptyTsReplacement( - _clientRequest.getWriteCommandRequestBase().getBypassEmptyTsReplacement()); - return wcb; }()); @@ -583,6 +615,19 @@ BatchedCommandRequest BatchWriteOp::buildBatchRequest(const TargetedWriteBatch& if (dbVersion) request.setDbVersion(*dbVersion); + if (_clientRequest.hasWriteConcern()) { + if (_clientRequest.isVerboseWC()) { + request.setWriteConcern(_clientRequest.getWriteConcern()); + } else { + // Mongos needs to send to the shard with w > 0 so it will be able to see the + // writeErrors + request.setWriteConcern(upgradeWriteConcern(_clientRequest.getWriteConcern())); + } + } else if (!TransactionRouter::get(_opCtx)) { + // Apply the WC from the opCtx (except if in a transaction). + request.setWriteConcern(_opCtx->getWriteConcern().toBSON()); + } + return request; } @@ -765,7 +810,7 @@ void BatchWriteOp::buildClientResponse(BatchedCommandResponse* batchResp) { batchResp->setStatus(Status::OK()); // For non-verbose, it's all we need. - if (!_opCtx->getWriteConcern().requiresWriteAcknowledgement()) { + if (!_clientRequest.isVerboseWC()) { return; } @@ -816,7 +861,12 @@ void BatchWriteOp::buildClientResponse(BatchedCommandResponse* batchResp) { } } - if (!_wcErrors.empty()) { + // Only return a write concern error if everything succeeded (unordered or ordered) + // OR if something succeeded and we're unordered + const bool orderedOps = _clientRequest.getWriteCommandRequestBase().getOrdered(); + const bool reportWCError = + errOps.empty() || (!orderedOps && errOps.size() < _clientRequest.sizeWriteOps()); + if (!_wcErrors.empty() && reportWCError) { WriteConcernErrorDetail* error = new WriteConcernErrorDetail; // Generate the multi-error message below diff --git a/src/mongo/s/write_ops/batch_write_op_test.cpp b/src/mongo/s/write_ops/batch_write_op_test.cpp index b21e64c6d53..bfda09f0814 100644 --- a/src/mongo/s/write_ops/batch_write_op_test.cpp +++ b/src/mongo/s/write_ops/batch_write_op_test.cpp @@ -32,7 +32,6 @@ #include "mongo/s/concurrency/locker_mongos_client_observer.h" #include "mongo/s/mock_ns_targeter.h" #include "mongo/s/session_catalog_router.h" -#include "mongo/s/shard_cannot_refresh_due_to_locks_held_exception.h" #include "mongo/s/sharding_router_test_fixture.h" #include "mongo/s/transaction_router.h" #include "mongo/s/write_ops/batch_write_op.h" @@ -235,6 +234,7 @@ TEST_F(BatchWriteOpTest, SingleWriteConcernErrorOrdered) { insertOp.setDocuments({BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSON("w" << 3)); BatchWriteOp batchOp(_opCtx, request); @@ -246,6 +246,7 @@ TEST_F(BatchWriteOpTest, SingleWriteConcernErrorOrdered) { BatchedCommandRequest targetBatch = batchOp.buildBatchRequest(*targeted.begin()->second, targeter); + ASSERT(targetBatch.getWriteConcern().woCompare(request.getWriteConcern()) == 0); BatchedCommandResponse response; buildResponse(1, &response); @@ -304,22 +305,12 @@ TEST_F(BatchWriteOpTest, SingleStaleError) { batchOp.noteBatchResponse(*targeted.begin()->second, response, nullptr); ASSERT(!batchOp.isFinished()); - // Respond with a ShardCannotRefreshDueToLocksHeld error; the batch should still be retriable. - targeted.clear(); - ASSERT_OK(batchOp.targetBatch(targeter, false, &targeted)); - buildResponse(0, &response); - response.addToErrDetails(write_ops::WriteError( - 0, Status{ShardCannotRefreshDueToLocksHeldInfo(nss), "mock cache busy error"})); - - batchOp.noteBatchResponse(*targeted.begin()->second, response, nullptr); - ASSERT(!batchOp.isFinished()); - - // Respond with an 'ok' response targeted.clear(); ASSERT_OK(batchOp.targetBatch(targeter, false, &targeted)); buildResponse(1, &response); + // Respond with an 'ok' response batchOp.noteBatchResponse(*targeted.begin()->second, response, nullptr); ASSERT(batchOp.isFinished()); @@ -1010,7 +1001,9 @@ TEST_F(BatchWriteOpTest, MultiOpPartialSingleShardErrorOrdered) { // Tests of edge-case functionality, lifecycle is assumed to be behaving normally // -// Multi-op (unordered) error and write concern error test. +// Multi-op (unordered) error and write concern error test. We never report the write concern error +// for single-doc batches, since the error means there's no write concern applied. Don't suppress +// the error if ordered : false. TEST_F(BatchWriteOpTest, MultiOpErrorAndWriteConcernErrorUnordered) { NamespaceString nss("foo.bar"); ShardEndpoint endpoint(ShardId("shard"), ChunkVersion::IGNORED(), boost::none); @@ -1027,6 +1020,7 @@ TEST_F(BatchWriteOpTest, MultiOpErrorAndWriteConcernErrorUnordered) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 1)}); return insertOp; }()); + request.setWriteConcern(BSON("w" << 3)); BatchWriteOp batchOp(_opCtx, request); @@ -1051,7 +1045,8 @@ TEST_F(BatchWriteOpTest, MultiOpErrorAndWriteConcernErrorUnordered) { ASSERT(clientResponse.isWriteConcernErrorSet()); } -// Single-op (ordered) error and write concern error test. +// Single-op (ordered) error and write concern error test. Suppress the write concern error if +// ordered and we also have an error TEST_F(BatchWriteOpTest, SingleOpErrorAndWriteConcernErrorOrdered) { NamespaceString nss("foo.bar"); ShardEndpoint endpointA(ShardId("shardA"), ChunkVersion::IGNORED(), boost::none); @@ -1069,6 +1064,7 @@ TEST_F(BatchWriteOpTest, SingleOpErrorAndWriteConcernErrorOrdered) { updateOp.setUpdates({buildUpdate(BSON("x" << GTE << -1 << LT << 2), true)}); return updateOp; }()); + request.setWriteConcern(BSON("w" << 3)); BatchWriteOp batchOp(_opCtx, request); @@ -1095,14 +1091,14 @@ TEST_F(BatchWriteOpTest, SingleOpErrorAndWriteConcernErrorOrdered) { ASSERT(batchOp.isFinished()); ASSERT(++targetedIt == targeted.end()); - // Ordered reports write concern error. + // Ordered doesn't report write concern error BatchedCommandResponse clientResponse; batchOp.buildClientResponse(&clientResponse); ASSERT(clientResponse.getOk()); ASSERT_EQUALS(clientResponse.getN(), 1); ASSERT(clientResponse.isErrDetailsSet()); ASSERT_EQUALS(clientResponse.sizeErrDetails(), 1u); - ASSERT(clientResponse.isWriteConcernErrorSet()); + ASSERT(!clientResponse.isWriteConcernErrorSet()); } // Targeting failure on second op in batch op (ordered) @@ -1411,6 +1407,7 @@ TEST_F(BatchWriteOpTest, MultiOpTwoWCErrors) { insertOp.setDocuments({BSON("x" << -1), BSON("x" << 2)}); return insertOp; }()); + request.setWriteConcern(BSON("w" << 3)); BatchWriteOp batchOp(_opCtx, request); diff --git a/src/mongo/s/write_ops/batched_command_request.cpp b/src/mongo/s/write_ops/batched_command_request.cpp index 185857d6acc..107f1a49204 100644 --- a/src/mongo/s/write_ops/batched_command_request.cpp +++ b/src/mongo/s/write_ops/batched_command_request.cpp @@ -38,6 +38,8 @@ namespace mongo { namespace { +const auto kWriteConcern = "writeConcern"_sd; + template <class T> BatchedCommandRequest constructBatchedCommandRequest(const OpMsgRequest& request) { auto batchRequest = BatchedCommandRequest{T::parse(request)}; @@ -51,6 +53,11 @@ BatchedCommandRequest constructBatchedCommandRequest(const OpMsgRequest& request batchRequest.setShardVersion(shardVersion); } + auto writeConcernField = request.body[kWriteConcern]; + if (!writeConcernField.eoo()) { + batchRequest.setWriteConcern(writeConcernField.Obj()); + } + // The 'isTimeseriesNamespace' is an internal parameter used for communication between mongos // and mongod. auto isTimeseriesNamespace = @@ -166,9 +173,19 @@ const boost::optional<BSONObj>& BatchedCommandRequest::getLet() const { return _visit(Visitor{}); }; -const OptionalBool& BatchedCommandRequest::getBypassEmptyTsReplacement() const { - return _visit([](auto&& op) -> decltype(auto) { return op.getBypassEmptyTsReplacement(); }); -}; +bool BatchedCommandRequest::isVerboseWC() const { + if (!hasWriteConcern()) { + return true; + } + + BSONObj writeConcern = getWriteConcern(); + BSONElement wElem = writeConcern["w"]; + if (!wElem.isNumber() || wElem.Number() != 0) { + return true; + } + + return false; +} const write_ops::WriteCommandRequestBase& BatchedCommandRequest::getWriteCommandRequestBase() const { @@ -189,6 +206,10 @@ void BatchedCommandRequest::serialize(BSONObjBuilder* builder) const { if (_dbVersion) { builder->append("databaseVersion", _dbVersion->toBSON()); } + + if (_writeConcern) { + builder->append(kWriteConcern, *_writeConcern); + } } BSONObj BatchedCommandRequest::toBSON() const { diff --git a/src/mongo/s/write_ops/batched_command_request.h b/src/mongo/s/write_ops/batched_command_request.h index 0bcb51a3556..eea7f7bbe11 100644 --- a/src/mongo/s/write_ops/batched_command_request.h +++ b/src/mongo/s/write_ops/batched_command_request.h @@ -52,25 +52,15 @@ public: : _batchType(BatchType_Insert), _insertReq(std::make_unique<write_ops::InsertCommandRequest>(std::move(insertOp))) {} - BatchedCommandRequest(std::unique_ptr<write_ops::InsertCommandRequest> insertOp) - : _batchType(BatchType_Insert), _insertReq(std::move(insertOp)) {} - BatchedCommandRequest(write_ops::UpdateCommandRequest updateOp) : _batchType(BatchType_Update), _updateReq(std::make_unique<write_ops::UpdateCommandRequest>(std::move(updateOp))) {} - BatchedCommandRequest(std::unique_ptr<write_ops::UpdateCommandRequest> updateOp) - : _batchType(BatchType_Update), _updateReq(std::move(updateOp)) {} - BatchedCommandRequest(write_ops::DeleteCommandRequest deleteOp) : _batchType(BatchType_Delete), _deleteReq(std::make_unique<write_ops::DeleteCommandRequest>(std::move(deleteOp))) {} - BatchedCommandRequest(std::unique_ptr<write_ops::DeleteCommandRequest> deleteOp) - : _batchType(BatchType_Delete), _deleteReq(std::move(deleteOp)) {} - BatchedCommandRequest(BatchedCommandRequest&&) = default; - BatchedCommandRequest& operator=(BatchedCommandRequest&&) = default; static BatchedCommandRequest parseInsert(const OpMsgRequest& request); static BatchedCommandRequest parseUpdate(const OpMsgRequest& request); @@ -101,19 +91,26 @@ public: return *_deleteReq; } - std::unique_ptr<write_ops::InsertCommandRequest> extractInsertRequest() { - return std::move(_insertReq); + std::size_t sizeWriteOps() const; + + void setWriteConcern(const BSONObj& writeConcern) { + _writeConcern = writeConcern.getOwned(); } - std::unique_ptr<write_ops::UpdateCommandRequest> extractUpdateRequest() { - return std::move(_updateReq); + void unsetWriteConcern() { + _writeConcern = boost::none; } - std::unique_ptr<write_ops::DeleteCommandRequest> extractDeleteRequest() { - return std::move(_deleteReq); + bool hasWriteConcern() const { + return _writeConcern.is_initialized(); } - std::size_t sizeWriteOps() const; + const BSONObj& getWriteConcern() const { + invariant(_writeConcern); + return *_writeConcern; + } + + bool isVerboseWC() const; void setShardVersion(ChunkVersion shardVersion) { _shardVersion = std::move(shardVersion); @@ -149,7 +146,6 @@ public: const boost::optional<LegacyRuntimeConstants>& getLegacyRuntimeConstants() const; const boost::optional<BSONObj>& getLet() const; - const OptionalBool& getBypassEmptyTsReplacement() const; const write_ops::WriteCommandRequestBase& getWriteCommandRequestBase() const; void setWriteCommandRequestBase(write_ops::WriteCommandRequestBase writeCommandBase); @@ -232,6 +228,8 @@ private: boost::optional<ChunkVersion> _shardVersion; boost::optional<DatabaseVersion> _dbVersion; + + boost::optional<BSONObj> _writeConcern; }; /** diff --git a/src/mongo/s/write_ops/batched_command_request_test.cpp b/src/mongo/s/write_ops/batched_command_request_test.cpp index be0728b1533..9a5e968f10d 100644 --- a/src/mongo/s/write_ops/batched_command_request_test.cpp +++ b/src/mongo/s/write_ops/batched_command_request_test.cpp @@ -92,12 +92,14 @@ TEST(BatchedCommandRequest, InsertCloneWithIds) { insertOp.setDocuments({BSON("x" << 1), BSON("x" << 2)}); return insertOp; }()); + batchedRequest.setWriteConcern(BSON("w" << 2)); const auto clonedRequest(BatchedCommandRequest::cloneInsertWithIds(std::move(batchedRequest))); ASSERT_EQ("xyz.abc", clonedRequest.getNS().ns()); ASSERT(clonedRequest.getWriteCommandRequestBase().getOrdered()); ASSERT(clonedRequest.getWriteCommandRequestBase().getBypassDocumentValidation()); + ASSERT_BSONOBJ_EQ(BSON("w" << 2), clonedRequest.getWriteConcern()); const auto& insertDocs = clonedRequest.getInsertRequest().getDocuments(); ASSERT_EQ(2u, insertDocs.size()); diff --git a/src/mongo/s/write_ops/write_op.cpp b/src/mongo/s/write_ops/write_op.cpp index 6b33d3b9312..236c7efbc94 100644 --- a/src/mongo/s/write_ops/write_op.cpp +++ b/src/mongo/s/write_ops/write_op.cpp @@ -29,24 +29,7 @@ #include "mongo/s/write_ops/write_op.h" - -#include <absl/container/flat_hash_set.h> -#include <algorithm> -#include <boost/move/utility_core.hpp> -#include <boost/none.hpp> -#include <boost/optional/optional.hpp> -#include <ostream> -#include <string> - -#include "mongo/base/error_codes.h" -#include "mongo/base/status.h" -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/db/catalog/collection_uuid_mismatch_info.h" -#include "mongo/db/stats/counters.h" -#include "mongo/s/sharding_feature_flags_gen.h" #include "mongo/s/transaction_router.h" -#include "mongo/s/write_ops/batch_write_op.h" -#include "mongo/s/write_ops/batched_command_request.h" #include "mongo/util/assert_util.h" namespace mongo { @@ -96,16 +79,6 @@ write_ops::WriteError combineOpErrors(const std::vector<ChildWriteOp const*>& er Status(MultipleErrorsOccurredInfo(errB.arr()), msg.str())); } -bool isSafeToIgnoreErrorInPartiallyAppliedOp(write_ops::WriteError& error) { - // UUID mismatch errors are safe to ignore if the actualCollection is null in conjuntion with - // other successful operations. This is true because it means we wrongly targeted a non-owning - // shard with the operation and we wouldn't have applied any modifications anyway. - // - // Note this is only safe if we're using ShardVersion::IGNORED since we're ignoring any - // placement concern and broadcasting to all shards. - return error.getStatus().code() == ErrorCodes::CollectionUUIDMismatch && - !error.getStatus().extraInfo<CollectionUUIDMismatchInfo>()->actualCollection(); -} } // namespace const BatchItemRef& WriteOp::getWriteItem() const { @@ -209,29 +182,7 @@ void WriteOp::_updateOpState() { _state = WriteOpState_Ready; } else if (!childErrors.empty()) { _error = combineOpErrors(childErrors); - bool isTargetingAllShardsWithSVIgnored = - childErrors.front() - ->endpoint->shardVersion - .map([&](const auto& cv) { return ChunkVersion::isIgnoredVersion(cv); }) - .get_value_or(false); - // There are errors that are safe to ignore if they were correctly applied to other shards - // and we're using ShardVersion::IGNORED. They are safe to ignore as they can be interpreted - // as no-ops if the shard response had been instead a successful result since they wouldn't - // have modified any data. As a result, we can swallow the errors and treat them as a - // successful operation. - if (isTargetingAllShardsWithSVIgnored && isSafeToIgnoreErrorInPartiallyAppliedOp(*_error) && - !_successfulShardSet.empty()) { - if (!hasPendingChild) { - _error.reset(); - _state = WriteOpState_Completed; - } else { - // As this error is acceptable we wait until all other operations finish to take a - // decision. - return; - } - } else { - _state = WriteOpState_Error; - } + _state = WriteOpState_Error; } else if (hasPendingChild && _inTxn) { // Return early here since this means that there were no errors while in txn // but there are still ops that have not yet finished. |
