diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/s/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/s/auto_split_vector.cpp | 358 | ||||
-rw-r--r-- | src/mongo/db/s/auto_split_vector.h | 134 | ||||
-rw-r--r-- | src/mongo/db/s/auto_split_vector_command.cpp | 98 | ||||
-rw-r--r-- | src/mongo/db/s/auto_split_vector_test.cpp | 262 | ||||
-rw-r--r-- | src/mongo/db/s/balancer/balancer.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/s/chunk_splitter.cpp | 23 | ||||
-rw-r--r-- | src/mongo/db/s/config/initial_split_policy.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/s/config/sharding_catalog_manager_shard_collection_test.cpp | 14 | ||||
-rw-r--r-- | src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp | 2 | ||||
-rw-r--r-- | src/mongo/s/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/request_types/auto_split_vector.idl | 71 | ||||
-rw-r--r-- | src/mongo/s/shard_util.cpp | 78 | ||||
-rw-r--r-- | src/mongo/s/shard_util.h | 3 | ||||
-rw-r--r-- | src/mongo/s/write_ops/cluster_write.cpp | 3 |
15 files changed, 999 insertions, 57 deletions
diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript index 75fa982f880..7f96b5499ff 100644 --- a/src/mongo/db/s/SConscript +++ b/src/mongo/db/s/SConscript @@ -33,6 +33,7 @@ env.Library( 'active_migrations_registry.cpp', 'active_move_primaries_registry.cpp', 'active_shard_collection_registry.cpp', + 'auto_split_vector.cpp', 'chunk_move_write_concern_options.cpp', 'chunk_splitter.cpp', 'collection_range_deleter.cpp', @@ -220,6 +221,7 @@ env.Library( env.Library( target='sharding_commands_d', source=[ + 'auto_split_vector_command.cpp', 'check_sharding_index_command.cpp', 'cleanup_orphaned_cmd.cpp', 'clone_catalog_data_command.cpp', @@ -304,6 +306,7 @@ env.CppUnitTest( 'active_migrations_registry_test.cpp', 'active_move_primaries_registry_test.cpp', 'active_shard_collection_registry_test.cpp', + 'auto_split_vector_test.cpp', 'catalog_cache_loader_mock.cpp', 'implicit_create_collection_test.cpp', 'migration_chunk_cloner_source_legacy_test.cpp', diff --git a/src/mongo/db/s/auto_split_vector.cpp b/src/mongo/db/s/auto_split_vector.cpp new file mode 100644 index 00000000000..a430d84593c --- /dev/null +++ b/src/mongo/db/s/auto_split_vector.cpp @@ -0,0 +1,358 @@ +/** + * Copyright (C) 2021-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_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding + +#include "mongo/platform/basic.h" + +#include "mongo/db/s/auto_split_vector.h" + +#include "mongo/base/status_with.h" +#include "mongo/db/bson/dotted_path_support.h" +#include "mongo/db/catalog/index_catalog.h" +#include "mongo/db/catalog_raii.h" +#include "mongo/db/dbhelpers.h" +#include "mongo/db/exec/working_set_common.h" +#include "mongo/db/index/index_descriptor.h" +#include "mongo/db/keypattern.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/query/internal_plans.h" +#include "mongo/db/query/plan_executor.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace { + +constexpr int estimatedAdditionalBytesPerItemInBSONArray{2}; + +constexpr int kMaxSplitPointsToReposition{3}; + +BSONObj prettyKey(const BSONObj& keyPattern, const BSONObj& key) { + return key.replaceFieldNames(keyPattern).clientReadable(); +} + +/* + * Takes the given min/max BSON objects that are a prefix of the shardKey and return two new BSON + * object extended to cover the entire shardKey. See KeyPattern::extendRangeBound documentation for + * some examples. + */ +const std::tuple<BSONObj, BSONObj> getMinMaxExtendedBounds(const IndexDescriptor* shardKeyIdx, + const BSONObj& min, + const BSONObj& max) { + KeyPattern kp(shardKeyIdx->keyPattern()); + + // Extend min to get (min, MinKey, MinKey, ....) + BSONObj minKey = Helpers::toKeyFormat(kp.extendRangeBound(min, false /* upperInclusive */)); + BSONObj maxKey; + if (max.isEmpty()) { + // if max not specified, make it (MaxKey, Maxkey, MaxKey...) + maxKey = Helpers::toKeyFormat(kp.extendRangeBound(max, true /* upperInclusive */)); + } else { + // otherwise make it (max,MinKey,MinKey...) so that bound is non-inclusive + maxKey = Helpers::toKeyFormat(kp.extendRangeBound(max, false /* upperInclusive*/)); + } + + return std::tuple<BSONObj, BSONObj>(minKey, maxKey); +} + +/* + * Returns true if the final key in the range is the same as the first key, false otherwise. + */ +bool maxKeyEqualToMinKey(OperationContext* opCtx, + const Collection* collection, + const IndexDescriptor* shardKeyIdx, + const BSONObj& minBound, + const BSONObj& maxBound, + const BSONObj& minKeyInChunk) { + BSONObj maxKeyInChunk; + { + auto backwardIdxScanner = InternalPlanner::indexScan(opCtx, + collection, + shardKeyIdx, + maxBound, + minBound, + BoundInclusion::kIncludeEndKeyOnly, + PlanExecutor::YIELD_AUTO, + InternalPlanner::BACKWARD); + + PlanExecutor::ExecState state = backwardIdxScanner->getNext(&maxKeyInChunk, nullptr); + uassert(ErrorCodes::OperationFailed, + "can't open a cursor to find final key in range (desired range is possibly empty)", + state == PlanExecutor::ADVANCED); + } + + if (minKeyInChunk.woCompare(maxKeyInChunk) == 0) { + // Range contains only documents with a single key value. So we cannot possibly find a + // split point, and there is no need to scan any further. + warning() << "possible low cardinality key detected in " << collection->ns().toString() + << " - range " << redact(prettyKey(shardKeyIdx->keyPattern(), minKeyInChunk)) + << " -->> " << redact(prettyKey(shardKeyIdx->keyPattern(), maxKeyInChunk)) + << " contains only the key " + << redact(prettyKey(shardKeyIdx->keyPattern(), minKeyInChunk)); + return true; + } + + return false; +} + +/* + * Reshuffle fields according to the shard key pattern. + */ +auto orderShardKeyFields(const BSONObj& keyPattern, BSONObj& key) { + return dotted_path_support::extractElementsBasedOnTemplate( + prettyKey(keyPattern, key.getOwned()), keyPattern); +} + +} // namespace + +std::vector<BSONObj> autoSplitVector(OperationContext* opCtx, + const NamespaceString& nss, + const BSONObj& keyPattern, + const BSONObj& min, + const BSONObj& max, + long long maxChunkSizeBytes) { + std::vector<BSONObj> splitKeys; + + int elapsedMillisToFindSplitPoints; + + // Contains each key appearing multiple times and estimated to be able to fill-in a chunk alone + auto tooFrequentKeys = SimpleBSONObjComparator::kInstance.makeBSONObjSet(); + + { + AutoGetCollection autoGetColl(opCtx, nss, MODE_IS); + auto* collection = autoGetColl.getCollection(); + + uassert(ErrorCodes::NamespaceNotFound, "ns not found", collection); + + // Get the size estimate for this namespace + const long long totalLocalCollDocuments = collection->numRecords(opCtx); + const long long dataSize = collection->dataSize(opCtx); + + // Return empty vector if current estimated data size is less than max chunk size + if (dataSize < maxChunkSizeBytes || totalLocalCollDocuments == 0) { + return {}; + } + + // Allow multiKey based on the invariant that shard keys must be single-valued. Therefore, + // any multi-key index prefixed by shard key cannot be multikey over the shard key fields. + auto catalog = collection->getIndexCatalog(); + auto shardKeyIdx = + catalog->findShardKeyPrefixedIndex(opCtx, keyPattern, false /* requireSingleKey */); + uassert(ErrorCodes::IndexNotFound, + str::stream() << "couldn't find index over splitting key " + << keyPattern.clientReadable().toString(), + shardKeyIdx); + + const auto minKeyAndMaxKey = getMinMaxExtendedBounds(shardKeyIdx, min, max); + const auto minKey = std::get<0>(minKeyAndMaxKey); + const auto maxKey = std::get<1>(minKeyAndMaxKey); + + // Setup the index scanner that will be used to find the split points + auto forwardIdxScanner = InternalPlanner::indexScan(opCtx, + &(*collection), + shardKeyIdx, + minKey, + maxKey, + BoundInclusion::kIncludeStartKeyOnly, + PlanExecutor::YIELD_AUTO, + InternalPlanner::FORWARD); + + // Get minimum key belonging to the chunk + BSONObj minKeyInOriginalChunk; + { + PlanExecutor::ExecState state = + forwardIdxScanner->getNext(&minKeyInOriginalChunk, nullptr); + uassert(ErrorCodes::OperationFailed, + "can't open a cursor to scan the range (desired range is possibly empty)", + state == PlanExecutor::ADVANCED); + } + + // Return empty vector if chunk's min and max keys are the same. + if (maxKeyEqualToMinKey( + opCtx, collection, shardKeyIdx, minKey, maxKey, minKeyInOriginalChunk)) { + return {}; + } + + log() << "request split points lookup for chunk " << nss.toString() << " " << redact(minKey) + << " -->> " << redact(maxKey); + + // Use the average document size and number of documents to find the approximate number of + // keys each chunk should contain + const long long avgDocSize = dataSize / totalLocalCollDocuments; + + // Split at max chunk size + long long maxDocsPerChunk = maxChunkSizeBytes / avgDocSize; + + BSONObj currentKey; // Last key seen during the index scan + long long numScannedKeys = 1; // minKeyInOriginalChunk has already been scanned + std::size_t resultArraySize = 0; // Approximate size in bytes of the split points array + bool reachedMaxBSONSize = false; // True if the split points vector becomes too big + + // Lambda to check whether the split points vector would exceed BSONObjMaxUserSize in case + // of additional split key of the specified size. + auto checkMaxBSONSize = [&resultArraySize](const int additionalKeySize) { + return resultArraySize + additionalKeySize > BSONObjMaxUserSize; + }; + + // Reference to last split point that needs to be checked in order to avoid adding duplicate + // split points. Initialized to the min of the first chunk being split. + auto minKeyElement = orderShardKeyFields(keyPattern, minKeyInOriginalChunk); + auto lastSplitPoint = minKeyElement; + + Timer timer; // To measure time elapsed while searching split points + + // Traverse the index and add the maxDocsPerChunk-th key to the result vector + while (forwardIdxScanner->getNext(¤tKey, nullptr) == PlanExecutor::ADVANCED) { + if (++numScannedKeys >= maxDocsPerChunk) { + currentKey = orderShardKeyFields(keyPattern, currentKey); + + if (currentKey.woCompare(lastSplitPoint) == 0) { + // Do not add again the same split point in case of frequent shard key. + tooFrequentKeys.insert(currentKey.getOwned()); + continue; + } + + const auto additionalKeySize = + currentKey.objsize() + estimatedAdditionalBytesPerItemInBSONArray; + if (checkMaxBSONSize(additionalKeySize)) { + if (splitKeys.empty()) { + // Keep trying until finding at least one split point that isn't above + // the max object user size. Very improbable corner case: the shard key + // size for the chosen split point is exactly 16MB. + continue; + } + reachedMaxBSONSize = true; + break; + } + + resultArraySize += additionalKeySize; + splitKeys.push_back(currentKey.getOwned()); + lastSplitPoint = splitKeys.back(); + numScannedKeys = 0; + + LOG(4) << "picked a split key: " << redact(currentKey); + } + } + + // Avoid creating small chunks by fairly recalculating the last split points if the last + // chunk would be too small (containing less than `80% maxDocsPerChunk` documents). + bool lastChunk80PercentFull = numScannedKeys >= maxDocsPerChunk * 0.8; + if (!lastChunk80PercentFull && !splitKeys.empty() && !reachedMaxBSONSize) { + // Eventually recalculate the last split points (at most `kMaxSplitPointsToReposition`). + int nSplitPointsToReposition = splitKeys.size() > kMaxSplitPointsToReposition + ? kMaxSplitPointsToReposition + : splitKeys.size(); + + // Equivalent to: (nSplitPointsToReposition * maxDocsPerChunk + numScannedKeys) divided + // by the number of reshuffled chunks (nSplitPointsToReposition + 1). + const auto maxDocsPerNewChunk = maxDocsPerChunk - + ((maxDocsPerChunk - numScannedKeys) / (nSplitPointsToReposition + 1)); + + if (numScannedKeys < maxDocsPerChunk - maxDocsPerNewChunk) { + // If the surplus is not too much, simply keep a bigger last chunk. + // The surplus is considered enough if repositioning the split points would imply + // generating chunks with a number of documents lower than `67% maxDocsPerChunk`. + splitKeys.pop_back(); + } else { + // Fairly recalculate the last `nSplitPointsToReposition` split points. + splitKeys.erase(splitKeys.end() - nSplitPointsToReposition, splitKeys.end()); + + auto forwardIdxScanner = + InternalPlanner::indexScan(opCtx, + collection, + shardKeyIdx, + splitKeys.empty() ? minKeyElement : splitKeys.back(), + maxKey, + BoundInclusion::kIncludeStartKeyOnly, + PlanExecutor::YIELD_AUTO, + InternalPlanner::FORWARD); + + numScannedKeys = 0; + + auto previousSplitPoint = splitKeys.empty() ? minKeyElement : splitKeys.back(); + while (forwardIdxScanner->getNext(¤tKey, nullptr) == PlanExecutor::ADVANCED) { + if (++numScannedKeys >= maxDocsPerNewChunk) { + currentKey = orderShardKeyFields(keyPattern, currentKey); + + const auto compareWithPreviousSplitPoint = + currentKey.woCompare(previousSplitPoint); + if (compareWithPreviousSplitPoint > 0) { + const auto additionalKeySize = + currentKey.objsize() + estimatedAdditionalBytesPerItemInBSONArray; + if (checkMaxBSONSize(additionalKeySize)) { + reachedMaxBSONSize = true; + break; + } + + splitKeys.push_back(currentKey.getOwned()); + previousSplitPoint = splitKeys.back(); + numScannedKeys = 0; + + if (--nSplitPointsToReposition == 0) { + break; + } + } else if (compareWithPreviousSplitPoint == 0) { + // Don't add again the same split point in case of frequent shard key. + tooFrequentKeys.insert(currentKey.getOwned()); + } + } + } + } + } + + elapsedMillisToFindSplitPoints = timer.millis(); + + if (reachedMaxBSONSize) { + log() << "Max BSON response size reached for split vector before the end of chunk for " + "namespace " + << nss.toString() << " - range " + << redact(prettyKey(shardKeyIdx->keyPattern(), minKey)) << " --> " + << redact(prettyKey(shardKeyIdx->keyPattern(), maxKey)); + } + } + + // Emit a warning for each frequent key + for (const auto& frequentKey : tooFrequentKeys) { + warning() << "possible low cardinality key detected in " << nss.toString() + << " - key: " << redact(prettyKey(keyPattern, frequentKey)); + } + + if (elapsedMillisToFindSplitPoints > serverGlobalParams.slowMS) { + warning() << "Finding the auto split vector for " << nss.toString() << " completed over " + << redact(keyPattern) << " - numSplits: " << splitKeys.size() + << " - duration: " << elapsedMillisToFindSplitPoints << "ms"; + } + + std::sort( + splitKeys.begin(), splitKeys.end(), SimpleBSONObjComparator::kInstance.makeLessThan()); + + return splitKeys; +} + +} // namespace mongo diff --git a/src/mongo/db/s/auto_split_vector.h b/src/mongo/db/s/auto_split_vector.h new file mode 100644 index 00000000000..559c1f814d6 --- /dev/null +++ b/src/mongo/db/s/auto_split_vector.h @@ -0,0 +1,134 @@ +/** + * Copyright (C) 2021-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. + */ + +#pragma once + +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" + +namespace mongo { + +/** + * Given a chunk, determines whether it satisfies the requisites to be auto-splitted and - if so - + * returns the split points (shard keys representing the lower bounds of the new chunks to create). + * + * The logic implemented can be summarized as follows: given a `maxChunkSize` of `x` MB, the + * algorithm aims to choose the split points so that the resulting chunks' size would be around + * `maxChunkSize`. As it is too expensive to precisely determine the dimension of a chunk, it is + * assumed a uniform distribution of document sizes, hence the aim is to balance the number of + * documents per chunk. + * + * ======= ALGORITHM DESCRIPTION ======= + * + * The split points for a chunk `C` belonging to a collection `coll` are calculated as follows: + * - `averageDocumentSize` = `totalCollSizeOnShard / numberOfCollDocs` + * - `maxNumberOfDocsPerChunk` = `maxChunkSize / averageDocumentSize` + * - Scan forward the shard key index entries for `coll` that are belonging to chunk `C`: + * - (1) Choose a split point every `maxNumberOfDocsPerChunk` scanned keys. + * - (2) As it needs to be avoided the creation of small chunks, consider the number of documents + * `S` that the right-most chunk would contain given the calculated split points: + * --- (2.1) IF `S >= 80% maxNumberOfDocsPerChunk`, return the list of calculated split points. + * --- (2.2) ELSE IF `S` documents could be fairly redistributed in the last chunks so that their + * size would be at least `67% maxNumberOfDocsPerChunk`: recalculate the last split points (max 3). + * --- (2.3) ELSE simply remove the last split point and keep a bigger last chunk. + * + * + * ============== EXAMPLES ============= + * + * ========= EXAMPLE (CASE 2.1) ======== + * `maxChunkSize` = 100MB + * `averageDocumentSize` = 1MB + * `maxNumberOfDocsPerChunk` = 100 + * + * Shard key type: integer + * Chunk `C` bounds: `[0, maxKey)` . Chunk `C` contains 190 documents with shard keys [0...189]. + * + * (1) Initially calculated split points: [99]. + * (2) The last chunk would contain the interval `[99-189]` so `S = 90` + * (2.1) `S >= 80% maxChunkSize`, so keep the current split points. + * + * Returned split points: [99]. + * + * ========= EXAMPLE (CASE 2.2) ======== + * `maxChunkSize` = 100MB + * `averageDocumentSize` = 1MB + * `maxNumberOfDocsPerChunk` = 100 + * + * Shard key type: integer + * Chunk `C` bounds: `[0, maxKey)` . Chunk `C` contains 140 documents with shard keys [0...139]. + * + * (1) Initially calculated split points: [99]. + * (2) The last chunk would contain the interval `[99-139]` so `S = 40` + * (2.2) `S` documents can be redistributed on the last split point by generating chunks of size >= + * 67% maxChunkSize. Recalculate. + * + * Returned split points: [69]. + * + * ========= EXAMPLE (CASE 2.3) ======== + * `maxChunkSize` = 100MB + * `averageDocumentSize` = 1MB + * `maxNumberOfDocsPerChunk` = 100 + * + * Shard key type: integer + * Chunk `C` bounds: `[0, maxKey)` . Chunk `C` contains 120 documents with shard keys [0...119]. + * + * (1) Initially calculated split points: [99]. + * (2) The last chunk would contain the interval `[99-119]` so `S = 20` + * (2.3) `S` documents can't be redistributed on the last split point by generating chunks of size + * >= 67% maxChunkSize. So remove the last split point. + * + * Returned split points: []. + */ +std::vector<BSONObj> autoSplitVector(OperationContext* opCtx, + const NamespaceString& nss, + const BSONObj& keyPattern, + const BSONObj& min, + const BSONObj& max, + long long maxChunkSizeBytes); + +/* + * Utility function for deserializing autoSplitVector/splitVector responses. + */ +static std::vector<BSONObj> parseSplitKeys(const BSONElement& splitKeysArray) { + uassert(ErrorCodes::TypeMismatch, + "The split keys vector must be represented as a BSON array", + !splitKeysArray.eoo() && splitKeysArray.type() == BSONType::Array); + + std::vector<BSONObj> splitKeys; + for (const auto& elem : splitKeysArray.Obj()) { + uassert(ErrorCodes::TypeMismatch, + "Each element of the split keys array must be an object", + elem.type() == BSONType::Object); + splitKeys.push_back(elem.embeddedObject().getOwned()); + } + + return splitKeys; +} + +} // namespace mongo diff --git a/src/mongo/db/s/auto_split_vector_command.cpp b/src/mongo/db/s/auto_split_vector_command.cpp new file mode 100644 index 00000000000..b789fb86855 --- /dev/null +++ b/src/mongo/db/s/auto_split_vector_command.cpp @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2021-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/db/s/auto_split_vector.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/s/sharding_state.h" +#include "mongo/s/request_types/auto_split_vector_gen.h" + +namespace mongo { +namespace { + +class AutoSplitVectorCommand final : public TypedCommand<AutoSplitVectorCommand> { +public: + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + return Command::AllowedOnSecondary::kNever; + } + + std::string help() const override { + return "Internal command returning the split points for a chunk, given the maximum chunk " + "size."; + } + + using Request = AutoSplitVectorRequest; + using Response = AutoSplitVectorResponse; + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + Response typedRun(OperationContext* opCtx) { + uassert(ErrorCodes::IllegalOperation, + "The autoSplitVector command can only be invoked on shards (no CSRS).", + serverGlobalParams.clusterRole == ClusterRole::ShardServer); + + uassertStatusOK(ShardingState::get(opCtx)->canAcceptShardedCommands()); + + const auto& req = request(); + + auto splitKeys = autoSplitVector(opCtx, + ns(), + req.getKeyPattern(), + req.getMin(), + req.getMax(), + req.getMaxChunkSizeBytes()); + + AutoSplitVectorResponse res; + res.setSplitKeys(splitKeys); + return res; + } + + private: + NamespaceString ns() const override { + return request().getNamespace(); + } + + bool supportsWriteConcern() const override { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const override { + uassert(ErrorCodes::Unauthorized, + "Unauthorized", + AuthorizationSession::get(opCtx->getClient()) + ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), + ActionType::splitVector)); + } + }; +} autoSplitVectorCommand; + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/s/auto_split_vector_test.cpp b/src/mongo/db/s/auto_split_vector_test.cpp new file mode 100644 index 00000000000..88825cc25f3 --- /dev/null +++ b/src/mongo/db/s/auto_split_vector_test.cpp @@ -0,0 +1,262 @@ +/** + * Copyright (C) 2021-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_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding + +#include "mongo/db/s/auto_split_vector.h" +#include "mongo/db/catalog/create_collection.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/dbdirectclient.h" +#include "mongo/db/s/collection_sharding_runtime.h" +#include "mongo/db/s/operation_sharding_state.h" +#include "mongo/db/s/split_vector.h" +#include "mongo/platform/random.h" +#include "mongo/s/shard_server_test_fixture.h" +#include "mongo/util/log.h" + +namespace mongo { +namespace { + +const NamespaceString kNss = NamespaceString("autosplitDB", "coll"); +const std::string kPattern = "_id"; + +/* + * Call the autoSplitVector function of the test collection on a chunk with bounds [0, 100) and with + * the specified `maxChunkSizeMB`. + */ +std::vector<BSONObj> autoSplit(OperationContext* opCtx, int maxChunkSizeMB) { + return autoSplitVector(opCtx, + kNss, + BSON(kPattern << 1) /* shard key pattern */, + BSON(kPattern << 0) /* min */, + BSON(kPattern << 1000) /* max */, + maxChunkSizeMB * 1024 * 1024 /* max chunk size in bytes*/); +} + +class AutoSplitVectorTest : public ShardServerTestFixture { +public: + /* + * Before each test case: + * - Creates a sharded collection with shard key `_id` + */ + void setUp() { + ShardServerTestFixture::setUp(); + + auto opCtx = operationContext(); + + { + uassertStatusOK(createCollection( + operationContext(), kNss.db().toString(), BSON("create" << kNss.coll()))); + } + + DBDirectClient client(opCtx); + client.createIndex(kNss.ns(), BSON(kPattern << 1)); + } + + /* + * Insert the specified number of documents in the test collection, with incremental shard key + * `_id` starting from `_nextShardKey`. + */ + void insertNDocsOf1MB(OperationContext* opCtx, int nDocs) { + DBDirectClient client(opCtx); + + std::string s(1024 * 1024 - 24, 'a'); // To get a 1MB document + for (int i = 0; i < nDocs; i++) { + BSONObjBuilder builder; + builder.append(kPattern, _nextShardKey++); + builder.append("str", s); + BSONObj obj = builder.obj(); + ASSERT(obj.objsize() == 1024 * 1024); // 1 MB document + client.insert(kNss.toString(), obj); + } + } + + /* + * Get the number of documents inserted until now. + */ + int getInsertedSize() { + return _nextShardKey; + } + +private: + int _nextShardKey = 0; +}; + +class AutoSplitVectorTest10MB : public AutoSplitVectorTest { + /* + * Before each test case: + * - Creates a sharded collection with shard key `_id` + * - Inserts `10` documents of ~1MB size (shard keys [0...9]) + */ + void setUp() { + AutoSplitVectorTest::setUp(); + + auto opCtx = operationContext(); + + DBDirectClient client(opCtx); + client.createIndex(kNss.ns(), BSON(kPattern << 1)); + + insertNDocsOf1MB(opCtx, 10 /* nDocs */); + ASSERT_EQUALS(10UL, client.count(kNss.ns())); + } +}; + +// Throw exception upon calling autoSplitVector on dropped/unexisting collection +TEST_F(AutoSplitVectorTest10MB, NoCollection) { + ASSERT_THROWS_CODE(autoSplitVector(operationContext(), + NamespaceString("dummy", "collection"), + BSON(kPattern << 1) /* shard key pattern */, + BSON(kPattern << 0) /* min */, + BSON(kPattern << 100) /* max */, + 1 * 1024 * 1024 /* max chunk size in bytes*/), + DBException, + ErrorCodes::NamespaceNotFound); +} + +// No split points if estimated `data size < max chunk size` +TEST_F(AutoSplitVectorTest10MB, NoSplitIfDataLessThanMaxChunkSize) { + std::vector<BSONObj> splitKeys = autoSplit(operationContext(), 11 /* maxChunkSizeMB */); + ASSERT_EQ(splitKeys.size(), 0UL); +} + +// Do not split in case of `chunk size == maxChunkSize` +TEST_F(AutoSplitVectorTest10MB, NoSplitIfDataEqualMaxChunkSize) { + std::vector<BSONObj> splitKeys = autoSplit(operationContext(), 10 /* maxChunkSizeMB */); + ASSERT_EQ(splitKeys.size(), 0UL); +} + +// No split points if `chunk size > max chunk size` but threshold not reached +TEST_F(AutoSplitVectorTest10MB, NoSplitIfDataLessThanThreshold) { + const auto surplus = 2; + { + // Increase collection size so that the auto splitter can actually be triggered. Use a + // different range to don't interfere with the chunk getting splitted. + insertNDocsOf1MB(operationContext(), surplus /* nDocs */); + } + std::vector<BSONObj> splitKeys = autoSplit(operationContext(), 10 /* maxChunkSizeMB */); + ASSERT_EQ(splitKeys.size(), 0UL); +} + +// One split point if `chunk size > max chunk size` and threshold reached +TEST_F(AutoSplitVectorTest10MB, SplitIfDataSlightlyMoreThanThreshold) { + const auto surplus = 4; + insertNDocsOf1MB(operationContext(), surplus /* nDocs */); + std::vector<BSONObj> splitKeys = autoSplit(operationContext(), 10 /* maxChunkSizeMB */); + ASSERT_EQ(splitKeys.size(), 1UL); + ASSERT_EQ(6, splitKeys.front().getIntField(kPattern)); +} + +// Split points if `data size > max chunk size * 2` and threshold reached +TEST_F(AutoSplitVectorTest10MB, SplitIfDataMoreThanThreshold) { + const auto surplus = 14; + insertNDocsOf1MB(operationContext(), surplus /* nDocs */); + std::vector<BSONObj> splitKeys = autoSplit(operationContext(), 10 /* maxChunkSizeMB */); + ASSERT_EQ(splitKeys.size(), 2UL); + ASSERT_EQ(7, splitKeys.front().getIntField(kPattern)); + ASSERT_EQ(15, splitKeys.back().getIntField(kPattern)); +} + +// Split points are not recalculated if the right-most chunk is at least `80% maxChunkSize` +TEST_F(AutoSplitVectorTest10MB, NoRecalculateIfBigLastChunk) { + const auto surplus = 8; + insertNDocsOf1MB(operationContext(), surplus /* nDocs */); + std::vector<BSONObj> splitKeys = autoSplit(operationContext(), 10 /* maxChunkSizeMB */); + ASSERT_EQ(splitKeys.size(), 1UL); + ASSERT_EQ(9, splitKeys.front().getIntField(kPattern)); +} + +class RepositionLastSplitPointsTest : public AutoSplitVectorTest { +public: + /* + * Tests that last split points are properly repositioned in case the surplus allows so or not + * repositioned otherwise. + */ + void checkRepositioning(int maxDocsPerChunk, int surplus, int nSplitPoints) { + ASSERT(surplus >= 0 && surplus < maxDocsPerChunk); + + const auto maxDocsPerNewChunk = + maxDocsPerChunk - ((maxDocsPerChunk - surplus) / (nSplitPoints + 1)); + bool mustReposition = + surplus >= maxDocsPerChunk - maxDocsPerNewChunk && surplus < maxDocsPerChunk * 0.8; + + int toInsert = (maxDocsPerChunk * nSplitPoints) - getInsertedSize() + surplus; + insertNDocsOf1MB(operationContext(), toInsert); + + int expectedChunkSize = + mustReposition ? getInsertedSize() / (nSplitPoints + 1) : maxDocsPerChunk; + std::vector<BSONObj> splitKeys = + autoSplit(operationContext(), maxDocsPerChunk /* maxChunkSizeMB */); + + int approximateNextMin = expectedChunkSize; + for (const auto& splitKey : splitKeys) { + int _id = splitKey.getIntField(kPattern); + // Expect an approximate match due to integers rounding in the split points algorithm. + ASSERT(_id >= approximateNextMin - 2 && _id <= approximateNextMin + 2) + << BSON("approximateNextMin" << approximateNextMin << "splitKeys" << splitKeys + << "maxDocsPerChunk" + << maxDocsPerChunk + << "surplus" + << surplus + << "nSplitPoints" + << nSplitPoints + << "maxDocsPerNewChunk" + << maxDocsPerNewChunk + << "mustReposition" + << mustReposition + << "toInsert" + << toInsert + << "expectedChunkSize" + << expectedChunkSize); + approximateNextMin = _id + expectedChunkSize; + } + } +}; + + +// Test that last split points are recalculated fairly (if the surplus allows so) +TEST_F(RepositionLastSplitPointsTest, RandomRepositioningTest) { + PseudoRandom random(SecureRandom::create()->nextInt64()); + // Avoid small sizes already checked in other test cases. + // Random maxDocsPerChunk in interval: [10, 110). + int maxDocsPerChunk = random.nextInt32(100) + 10; + // Random surplus in interval: [0, maxDocsPerChunk). + int surplus = random.nextInt32(maxDocsPerChunk); + + log() << "RandomRepositioningTest parameters - maxDocsPerChunk: " << maxDocsPerChunk + << " - surplus: " << surplus; + + for (int nSplitPointsToReposition = 1; nSplitPointsToReposition < 4; + nSplitPointsToReposition++) { + checkRepositioning(maxDocsPerChunk, surplus, nSplitPointsToReposition); + } +} + + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/s/balancer/balancer.cpp b/src/mongo/db/s/balancer/balancer.cpp index 4f7caa95c1f..ae9b5e12f77 100644 --- a/src/mongo/db/s/balancer/balancer.cpp +++ b/src/mongo/db/s/balancer/balancer.cpp @@ -639,8 +639,7 @@ void Balancer::_splitOrMarkJumbo(OperationContext* opCtx, nss, cm->getShardKeyPattern(), ChunkRange(chunk.getMin(), chunk.getMax()), - Grid::get(opCtx)->getBalancerConfiguration()->getMaxChunkSizeBytes(), - boost::none)); + Grid::get(opCtx)->getBalancerConfiguration()->getMaxChunkSizeBytes())); uassert(ErrorCodes::CannotSplit, "No split points found", !splitPoints.empty()); diff --git a/src/mongo/db/s/chunk_splitter.cpp b/src/mongo/db/s/chunk_splitter.cpp index bd4c8878544..73eb4266aa5 100644 --- a/src/mongo/db/s/chunk_splitter.cpp +++ b/src/mongo/db/s/chunk_splitter.cpp @@ -39,9 +39,9 @@ #include "mongo/db/client.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/s/auto_split_vector.h" #include "mongo/db/s/sharding_state.h" #include "mongo/db/s/split_chunk.h" -#include "mongo/db/s/split_vector.h" #include "mongo/db/service_context.h" #include "mongo/s/balancer_configuration.h" #include "mongo/s/catalog/type_chunk.h" @@ -320,18 +320,15 @@ void ChunkSplitter::_runAutosplit(const NamespaceString& nss, << " dataWritten since last check: " << dataWritten << " maxChunkSizeBytes: " << maxChunkSizeBytes; - auto splitPoints = uassertStatusOK(splitVector(opCtx.get(), - nss, - cm->getShardKeyPattern().toBSON(), - chunk.getMin(), - chunk.getMax(), - false, - boost::none, - boost::none, - boost::none, - maxChunkSizeBytes)); - - if (splitPoints.size() <= 1) { + const auto& shardKeyPattern = cm->getShardKeyPattern(); + auto splitPoints = autoSplitVector(opCtx.get(), + nss, + shardKeyPattern.toBSON(), + chunk.getMin(), + chunk.getMax(), + maxChunkSizeBytes); + + if (splitPoints.empty()) { // No split points means there isn't enough data to split on; 1 split point means we // have between half the chunk size to full chunk size so there is no need to split yet return; diff --git a/src/mongo/db/s/config/initial_split_policy.cpp b/src/mongo/db/s/config/initial_split_policy.cpp index 9d1c8a7eee4..86d73344e4a 100644 --- a/src/mongo/db/s/config/initial_split_policy.cpp +++ b/src/mongo/db/s/config/initial_split_policy.cpp @@ -394,8 +394,7 @@ InitialSplitPolicy::ShardCollectionConfig InitialSplitPolicy::createFirstChunksU nss, shardKeyPattern, ChunkRange(keyPattern.globalMin(), keyPattern.globalMax()), - balancerConfig->getMaxChunkSizeBytes(), - 0)); + balancerConfig->getMaxChunkSizeBytes())); return generateShardCollectionInitialChunks(nss, shardKeyPattern, diff --git a/src/mongo/db/s/config/sharding_catalog_manager_shard_collection_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_shard_collection_test.cpp index 3b9645eb03a..62f0a9633a5 100644 --- a/src/mongo/db/s/config/sharding_catalog_manager_shard_collection_test.cpp +++ b/src/mongo/db/s/config/sharding_catalog_manager_shard_collection_test.cpp @@ -68,21 +68,21 @@ class ShardCollectionTestBase : public ConfigServerTestFixture { protected: void expectSplitVector(const HostAndPort& shardHost, const ShardKeyPattern& keyPattern, - const BSONObj& splitPoints) { + const BSONArray& splitPoints) { onCommand([&](const RemoteCommandRequest& request) { ASSERT_EQUALS(shardHost, request.target); string cmdName = request.cmdObj.firstElement().fieldName(); - ASSERT_EQUALS("splitVector", cmdName); - ASSERT_EQUALS(kNamespace.ns(), - request.cmdObj["splitVector"].String()); // splitVector uses full ns + ASSERT_EQUALS("autoSplitVector", cmdName); + // autoSplitVector concatenates the collection name to the command's db + const auto receivedNs = + request.dbname + '.' + request.cmdObj["autoSplitVector"].String(); + ASSERT_EQUALS(kNamespace.ns(), receivedNs); ASSERT_BSONOBJ_EQ(keyPattern.toBSON(), request.cmdObj["keyPattern"].Obj()); ASSERT_BSONOBJ_EQ(keyPattern.getKeyPattern().globalMin(), request.cmdObj["min"].Obj()); ASSERT_BSONOBJ_EQ(keyPattern.getKeyPattern().globalMax(), request.cmdObj["max"].Obj()); ASSERT_EQUALS(64 * 1024 * 1024ULL, static_cast<uint64_t>(request.cmdObj["maxChunkSizeBytes"].numberLong())); - ASSERT_EQUALS(0, request.cmdObj["maxSplitPoints"].numberLong()); - ASSERT_EQUALS(0, request.cmdObj["maxChunkObjects"].numberLong()); ASSERT_BSONOBJ_EQ( ReadPreferenceSetting(ReadPreference::PrimaryPreferred).toContainingBSON(), @@ -338,7 +338,7 @@ TEST_F(ConfigServerShardCollectionTest, RangeSharding_NoInitialSplitPoints_NoSpl }); // Respond to the splitVector command sent to the shard to figure out initial split points. - expectSplitVector(shardHost, keyPattern, BSONObj()); + expectSplitVector(shardHost, keyPattern, BSONArray()); // Expect the set shard version for that namespace. // We do not check for a specific ChunkVersion, because we cannot easily know the OID that was diff --git a/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp b/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp index e07695ac82f..c8300788eaf 100644 --- a/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp +++ b/src/mongo/db/s/migration_chunk_cloner_source_legacy.cpp @@ -737,7 +737,7 @@ Status MigrationChunkClonerSourceLegacy::_storeCurrentLocs(OperationContext* opC if (totalRecs > 0) { avgRecSize = collection->dataSize(opCtx) / totalRecs; maxRecsWhenFull = _args.getMaxChunkSizeBytes() / avgRecSize; - maxRecsWhenFull = 130 * maxRecsWhenFull / 100; // pad some slack + maxRecsWhenFull = 2 * maxRecsWhenFull; // pad some slack } else { avgRecSize = 0; maxRecsWhenFull = kMaxObjectPerChunk + 1; diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript index 5c409c2b9e5..e0cdbe622f4 100644 --- a/src/mongo/s/SConscript +++ b/src/mongo/s/SConscript @@ -145,6 +145,7 @@ env.Library( 'stale_exception.cpp', env.Idlc('catalog/type_chunk_base.idl')[0], env.Idlc('database_version.idl')[0], + env.Idlc('request_types/auto_split_vector.idl')[0], env.Idlc('request_types/clone_catalog_data.idl')[0], env.Idlc('request_types/clear_jumbo_flag.idl')[0], env.Idlc('request_types/create_collection.idl')[0], diff --git a/src/mongo/s/request_types/auto_split_vector.idl b/src/mongo/s/request_types/auto_split_vector.idl new file mode 100644 index 00000000000..42e55424c4e --- /dev/null +++ b/src/mongo/s/request_types/auto_split_vector.idl @@ -0,0 +1,71 @@ +# Copyright(C) 2021 - 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. +# + +# This IDL file describes the BSON format for the autoSplitVector command. + +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/db/s/auto_split_vector.h" + +imports: + - "mongo/idl/basic_types.idl" + +types: + bson_vector: + bson_serialization_type: any + description: "An array of objects representing the split keys." + cpp_type: "std::vector<mongo::BSONObj>" + deserializer: ::mongo::parseSplitKeys + +structs: + AutoSplitVectorResponse: + description: "The reply of an autoSplitVector command." + strict: false + fields: + splitKeys: bson_vector + +commands: + autoSplitVector: + cpp_name: AutoSplitVectorRequest + description: "Internal autoSplitVector command" + strict: false + namespace: concatenate_with_db + fields: + keyPattern: + type: object + description: "Shard key pattern of the collection" + min: + type: object + description: "Min key of the chunk" + max: + type: object + description: "Max key of the chunk" + maxChunkSizeBytes: + type: safeInt64 + description: "Max chunk size of the collection expressed in bytes" diff --git a/src/mongo/s/shard_util.cpp b/src/mongo/s/shard_util.cpp index a861e69e9e7..116d636ef92 100644 --- a/src/mongo/s/shard_util.cpp +++ b/src/mongo/s/shard_util.cpp @@ -39,8 +39,10 @@ #include "mongo/client/read_preference.h" #include "mongo/client/remote_command_targeter.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/s/auto_split_vector.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/grid.h" +#include "mongo/s/request_types/auto_split_vector_gen.h" #include "mongo/s/shard_key_pattern.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" @@ -94,45 +96,65 @@ StatusWith<std::vector<BSONObj>> selectChunkSplitPoints(OperationContext* opCtx, const NamespaceString& nss, const ShardKeyPattern& shardKeyPattern, const ChunkRange& chunkRange, - long long chunkSizeBytes, - boost::optional<int> maxObjs) { - BSONObjBuilder cmd; - cmd.append("splitVector", nss.ns()); - cmd.append("keyPattern", shardKeyPattern.toBSON()); - chunkRange.append(&cmd); - cmd.append("maxChunkSizeBytes", chunkSizeBytes); - if (maxObjs) { - cmd.append("maxChunkObjects", *maxObjs); - } - + long long chunkSizeBytes) { auto shardStatus = Grid::get(opCtx)->shardRegistry()->getShard(opCtx, shardId); if (!shardStatus.isOK()) { return shardStatus.getStatus(); } - auto cmdStatus = shardStatus.getValue()->runCommandWithFixedRetryAttempts( - opCtx, - ReadPreferenceSetting{ReadPreference::PrimaryPreferred}, - "admin", - cmd.obj(), - Shard::RetryPolicy::kIdempotent); - if (!cmdStatus.isOK()) { - return std::move(cmdStatus.getStatus()); - } - if (!cmdStatus.getValue().commandStatus.isOK()) { - return std::move(cmdStatus.getValue().commandStatus); + auto invokeSplitCommand = [&](const BSONObj& command, const StringData db) { + return shardStatus.getValue()->runCommandWithFixedRetryAttempts( + opCtx, + ReadPreferenceSetting{ReadPreference::PrimaryPreferred}, + db.toString(), + command, + Shard::RetryPolicy::kIdempotent); + }; + + AutoSplitVectorRequest req(nss); + req.setKeyPattern(shardKeyPattern.toBSON()); + req.setMin(chunkRange.getMin()); + req.setMax(chunkRange.getMax()); + req.setMaxChunkSizeBytes(chunkSizeBytes); + + + auto cmdStatus = invokeSplitCommand(req.toBSON({}), nss.db()); + + // Fallback to splitVector command in case of mixed binaries not supporting autoSplitVector + bool fallback = [&]() { + auto status = Shard::CommandResponse::getEffectiveStatus(cmdStatus); + return !status.isOK() && status.code() == ErrorCodes::CommandNotFound; + }(); + + if (fallback) { + BSONObjBuilder cmd; + cmd.append("splitVector", nss.ns()); + cmd.append("keyPattern", shardKeyPattern.toBSON()); + chunkRange.append(&cmd); + cmd.append("maxChunkSizeBytes", chunkSizeBytes); + cmdStatus = invokeSplitCommand(cmd.obj(), NamespaceString::kAdminDb); } - const auto response = std::move(cmdStatus.getValue().response); - std::vector<BSONObj> splitPoints; + auto status = Shard::CommandResponse::getEffectiveStatus(cmdStatus); + if (!status.isOK()) { + return status; + } + + if (fallback) { + const auto response = std::move(cmdStatus.getValue().response); + std::vector<BSONObj> splitPoints; - BSONObjIterator it(response.getObjectField("splitKeys")); - while (it.more()) { - splitPoints.push_back(it.next().Obj().getOwned()); + BSONObjIterator it(response.getObjectField("splitKeys")); + while (it.more()) { + splitPoints.push_back(it.next().Obj().getOwned()); + } + return std::move(splitPoints); } - return std::move(splitPoints); + const auto response = AutoSplitVectorResponse::parse( + IDLParserErrorContext("AutoSplitVectorResponse"), std::move(cmdStatus.getValue().response)); + return response.getSplitKeys(); } StatusWith<boost::optional<ChunkRange>> splitChunkAtMultiplePoints( diff --git a/src/mongo/s/shard_util.h b/src/mongo/s/shard_util.h index a44b3fae919..dba785df22c 100644 --- a/src/mongo/s/shard_util.h +++ b/src/mongo/s/shard_util.h @@ -78,8 +78,7 @@ StatusWith<std::vector<BSONObj>> selectChunkSplitPoints(OperationContext* opCtx, const NamespaceString& nss, const ShardKeyPattern& shardKeyPattern, const ChunkRange& chunkRange, - long long chunkSizeBytes, - boost::optional<int> maxObjs); + 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/write_ops/cluster_write.cpp b/src/mongo/s/write_ops/cluster_write.cpp index 1e9af832516..493c3ef7f28 100644 --- a/src/mongo/s/write_ops/cluster_write.cpp +++ b/src/mongo/s/write_ops/cluster_write.cpp @@ -302,8 +302,7 @@ void updateChunkWriteStatsAndSplitIfNeeded(OperationContext* opCtx, nss, manager->getShardKeyPattern(), chunkRange, - chunkSizeToUse, - boost::none)); + chunkSizeToUse)); if (splitPoints.size() <= 1) { // No split points means there isn't enough data to split on; 1 split point means we |