summaryrefslogtreecommitdiff
path: root/src/mongo/s
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/s')
-rw-r--r--src/mongo/s/SConscript12
-rw-r--r--src/mongo/s/append_raw_responses_test.cpp4
-rw-r--r--src/mongo/s/async_requests_sender.cpp113
-rw-r--r--src/mongo/s/async_requests_sender.h10
-rw-r--r--src/mongo/s/catalog/sharding_catalog_client.h37
-rw-r--r--src/mongo/s/catalog/sharding_catalog_client_impl.cpp227
-rw-r--r--src/mongo/s/catalog/sharding_catalog_client_impl.h17
-rw-r--r--src/mongo/s/catalog/sharding_catalog_client_mock.cpp22
-rw-r--r--src/mongo/s/catalog/sharding_catalog_client_mock.h17
-rw-r--r--src/mongo/s/catalog/sharding_catalog_client_test.cpp50
-rw-r--r--src/mongo/s/catalog/type_chunk.cpp17
-rw-r--r--src/mongo/s/catalog/type_chunk.h3
-rw-r--r--src/mongo/s/catalog/type_chunk_test.cpp63
-rw-r--r--src/mongo/s/catalog/type_collection.cpp2
-rw-r--r--src/mongo/s/catalog_cache.cpp210
-rw-r--r--src/mongo/s/catalog_cache.h35
-rw-r--r--src/mongo/s/catalog_cache_loader.h5
-rw-r--r--src/mongo/s/catalog_cache_loader_mock.cpp4
-rw-r--r--src/mongo/s/catalog_cache_loader_mock.h1
-rw-r--r--src/mongo/s/catalog_cache_refresh_test.cpp1
-rw-r--r--src/mongo/s/catalog_cache_test.cpp66
-rw-r--r--src/mongo/s/catalog_cache_test_fixture.cpp2
-rw-r--r--src/mongo/s/chunk.cpp36
-rw-r--r--src/mongo/s/chunk.h12
-rw-r--r--src/mongo/s/chunk_manager.cpp695
-rw-r--r--src/mongo/s/chunk_manager.h257
-rw-r--r--src/mongo/s/chunk_manager_refresh_bm.cpp165
-rw-r--r--src/mongo/s/chunk_manager_targeter.cpp99
-rw-r--r--src/mongo/s/chunk_manager_targeter.h3
-rw-r--r--src/mongo/s/chunk_manager_targeter_test.cpp19
-rw-r--r--src/mongo/s/chunk_map_test.cpp425
-rw-r--r--src/mongo/s/chunks_test_util.cpp352
-rw-r--r--src/mongo/s/chunks_test_util.h117
-rw-r--r--src/mongo/s/client/shard.cpp61
-rw-r--r--src/mongo/s/client/shard.h25
-rw-r--r--src/mongo/s/client/shard_registry.cpp99
-rw-r--r--src/mongo/s/client/shard_registry.h8
-rw-r--r--src/mongo/s/client/shard_remote.cpp31
-rw-r--r--src/mongo/s/client/shard_remote.h6
-rw-r--r--src/mongo/s/client/sharding_connection_hook.cpp8
-rw-r--r--src/mongo/s/client/sharding_network_connection_hook.cpp14
-rw-r--r--src/mongo/s/client/sharding_network_connection_hook.h6
-rw-r--r--src/mongo/s/cluster_commands_helpers.cpp82
-rw-r--r--src/mongo/s/cluster_commands_helpers.h56
-rw-r--r--src/mongo/s/cluster_ddl.cpp5
-rw-r--r--src/mongo/s/commands/SConscript5
-rw-r--r--src/mongo/s/commands/cluster_coll_stats_cmd.cpp6
-rw-r--r--src/mongo/s/commands/cluster_collection_mod_cmd.cpp14
-rw-r--r--src/mongo/s/commands/cluster_convert_to_capped_cmd.cpp13
-rw-r--r--src/mongo/s/commands/cluster_count_cmd.cpp12
-rw-r--r--src/mongo/s/commands/cluster_create_indexes_cmd.cpp8
-rw-r--r--src/mongo/s/commands/cluster_data_size_cmd.cpp6
-rw-r--r--src/mongo/s/commands/cluster_distinct_cmd.cpp8
-rw-r--r--src/mongo/s/commands/cluster_drop_collection_cmd.cpp8
-rw-r--r--src/mongo/s/commands/cluster_filemd5_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_find_and_modify_cmd.cpp84
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.h43
-rw-r--r--src/mongo/s/commands/cluster_fle2_compact_cmd.cpp1
-rw-r--r--src/mongo/s/commands/cluster_fsync_cmd.cpp68
-rw-r--r--src/mongo/s/commands/cluster_fsync_unlock_cmd.cpp134
-rw-r--r--src/mongo/s/commands/cluster_fsync_unlock_cmd.idl42
-rw-r--r--src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp5
-rw-r--r--src/mongo/s/commands/cluster_hello_cmd.cpp10
-rw-r--r--src/mongo/s/commands/cluster_index_filter_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_map_reduce_agg.cpp6
-rw-r--r--src/mongo/s/commands/cluster_move_range_cmd.cpp2
-rw-r--r--src/mongo/s/commands/cluster_plan_cache_clear_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_profile_cmd.cpp4
-rw-r--r--src/mongo/s/commands/cluster_rename_collection_cmd.cpp31
-rw-r--r--src/mongo/s/commands/cluster_set_free_monitoring_cmd.cpp75
-rw-r--r--src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp6
-rw-r--r--src/mongo/s/commands/cluster_validate_cmd.cpp6
-rw-r--r--src/mongo/s/commands/cluster_write_cmd.cpp40
-rw-r--r--src/mongo/s/commands/strategy.cpp102
-rw-r--r--src/mongo/s/commands/strategy.h4
-rw-r--r--src/mongo/s/concurrency/locker_mongos.h13
-rw-r--r--src/mongo/s/config_server_catalog_cache_loader.cpp4
-rw-r--r--src/mongo/s/config_server_catalog_cache_loader.h1
-rw-r--r--src/mongo/s/database_version.idl3
-rw-r--r--src/mongo/s/mongod_and_mongos_server_parameters.idl10
-rw-r--r--src/mongo/s/mongos_main.cpp31
-rw-r--r--src/mongo/s/mongos_options.cpp7
-rw-r--r--src/mongo/s/multi_statement_transaction_requests_sender.cpp8
-rw-r--r--src/mongo/s/query/SConscript27
-rw-r--r--src/mongo/s/query/async_results_merger.cpp3
-rw-r--r--src/mongo/s/query/async_results_merger_params.idl24
-rw-r--r--src/mongo/s/query/blocking_results_merger.cpp5
-rw-r--r--src/mongo/s/query/blocking_results_merger.h1
-rw-r--r--src/mongo/s/query/cluster_aggregate.cpp172
-rw-r--r--src/mongo/s/query/cluster_aggregation_planner.cpp87
-rw-r--r--src/mongo/s/query/cluster_aggregation_planner.h4
-rw-r--r--src/mongo/s/query/cluster_client_cursor.h33
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.cpp46
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.h22
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.cpp20
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.h8
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.cpp58
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.h15
-rw-r--r--src/mongo/s/query/cluster_find.cpp78
-rw-r--r--src/mongo/s/query/document_source_merge_cursors.cpp7
-rw-r--r--src/mongo/s/query/document_source_merge_cursors.h2
-rw-r--r--src/mongo/s/query/establish_cursors.cpp205
-rw-r--r--src/mongo/s/query/establish_cursors.h20
-rw-r--r--src/mongo/s/query/router_stage_remove_metadata_fields.cpp9
-rw-r--r--src/mongo/s/query/router_stage_remove_metadata_fields_test.cpp54
-rw-r--r--src/mongo/s/query/store_possible_cursor.cpp14
-rw-r--r--src/mongo/s/request_types/auto_split_vector.idl4
-rw-r--r--src/mongo/s/request_types/balancer_collection_status.idl2
-rw-r--r--src/mongo/s/request_types/commit_chunk_migration_request_type.h5
-rw-r--r--src/mongo/s/request_types/get_stats_for_balancing.idl84
-rw-r--r--src/mongo/s/request_types/merge_chunks_request_test.cpp12
-rw-r--r--src/mongo/s/request_types/move_primary.idl18
-rw-r--r--src/mongo/s/request_types/sharded_ddl_commands.idl15
-rw-r--r--src/mongo/s/resharding/common_types.idl3
-rw-r--r--src/mongo/s/router.cpp (renamed from src/mongo/s/router_role.cpp)89
-rw-r--r--src/mongo/s/router.h (renamed from src/mongo/s/router_role.h)0
-rw-r--r--src/mongo/s/routing_table_history_test.cpp850
-rw-r--r--src/mongo/s/service_entry_point_mongos.cpp7
-rw-r--r--src/mongo/s/sessions_collection_sharded_test.cpp1
-rw-r--r--src/mongo/s/shard_id_test.cpp4
-rw-r--r--src/mongo/s/shard_key_pattern.cpp21
-rw-r--r--src/mongo/s/shard_key_pattern.h35
-rw-r--r--src/mongo/s/shard_key_pattern_test.cpp98
-rw-r--r--src/mongo/s/shard_util.cpp6
-rw-r--r--src/mongo/s/shard_util.h16
-rw-r--r--src/mongo/s/sharding_feature_flags.idl11
-rw-r--r--src/mongo/s/sharding_initialization.cpp1
-rw-r--r--src/mongo/s/sharding_router_test_fixture.cpp5
-rw-r--r--src/mongo/s/sharding_task_executor_pool_controller.cpp2
-rw-r--r--src/mongo/s/stale_exception.cpp44
-rw-r--r--src/mongo/s/stale_exception.h23
-rw-r--r--src/mongo/s/stale_exception_test.cpp20
-rw-r--r--src/mongo/s/stale_shard_version_helpers.cpp7
-rw-r--r--src/mongo/s/transaction_router.cpp243
-rw-r--r--src/mongo/s/transaction_router.h46
-rw-r--r--src/mongo/s/transaction_router_test.cpp435
-rw-r--r--src/mongo/s/write_ops/batch_write_exec.cpp24
-rw-r--r--src/mongo/s/write_ops/batch_write_exec_test.cpp508
-rw-r--r--src/mongo/s/write_ops/batch_write_op.cpp78
-rw-r--r--src/mongo/s/write_ops/batch_write_op_test.cpp29
-rw-r--r--src/mongo/s/write_ops/batched_command_request.cpp27
-rw-r--r--src/mongo/s/write_ops/batched_command_request.h34
-rw-r--r--src/mongo/s/write_ops/batched_command_request_test.cpp2
-rw-r--r--src/mongo/s/write_ops/write_op.cpp51
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(),
+ [&currentRangeShardId, &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.