diff options
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/s/commands/cluster_find_and_modify_cmd.cpp | 128 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_find_and_modify_cmd.h | 18 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp | 93 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_write_cmd.cpp | 82 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_write_cmd.h | 9 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_write_without_shard_key_cmd.cpp | 52 | ||||
-rw-r--r-- | src/mongo/s/write_ops/write_without_shard_key_util.cpp | 87 | ||||
-rw-r--r-- | src/mongo/s/write_ops/write_without_shard_key_util.h | 12 |
8 files changed, 416 insertions, 65 deletions
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 3d223e1e3fc..600b2ff3dd3 100644 --- a/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.cpp @@ -517,6 +517,7 @@ Status FindAndModifyCmd::explain(OperationContext* opCtx, rpc::ReplyBuilderInterface* result) const { const DatabaseName dbName = DatabaseNameUtil::deserialize(request.getValidatedTenantId(), request.getDatabase()); + auto bodyBuilder = result->getBodyBuilder(); const BSONObj& cmdObj = [&]() { // Check whether the query portion needs to be rewritten for FLE. auto findAndModifyRequest = write_ops::FindAndModifyCommandRequest::parse( @@ -538,20 +539,11 @@ Status FindAndModifyCmd::explain(OperationContext* opCtx, const auto& cm = cri.cm; std::shared_ptr<Shard> shard; - if (cm.isSharded()) { - const BSONObj query = cmdObj.getObjectField("query"); - const BSONObj collation = getCollation(cmdObj); - const auto let = getLet(cmdObj); - const auto rc = getLegacyRuntimeConstants(cmdObj); - const BSONObj shardKey = - getShardKey(makeExpCtx(opCtx, nss, collation, boost::none, let, rc), cm, query); - const auto chunk = cm.findIntersectingChunk(shardKey, collation); - - shard = - uassertStatusOK(Grid::get(opCtx)->shardRegistry()->getShard(opCtx, chunk.getShardId())); - } else { - shard = uassertStatusOK(Grid::get(opCtx)->shardRegistry()->getShard(opCtx, cm.dbPrimary())); - } + const BSONObj query = cmdObj.getObjectField("query"); + const BSONObj collation = getCollation(cmdObj); + const auto isUpsert = cmdObj.getBoolField("upsert"); + const auto let = getLet(cmdObj); + const auto rc = getLegacyRuntimeConstants(cmdObj); const auto explainCmd = ClusterExplain::wrapAsExplain( appendLegacyRuntimeConstantsToCommandObject(opCtx, cmdObj), verbosity); @@ -561,16 +553,31 @@ Status FindAndModifyCmd::explain(OperationContext* opCtx, BSONObjBuilder bob; if (cm.isSharded()) { - _runCommand(opCtx, - shard->getId(), - cri.getShardVersion(shard->getId()), - boost::none, - nss, - applyReadWriteConcern(opCtx, false, false, explainCmd), - true /* isExplain */, - boost::none /* allowShardKeyUpdatesWithoutFullShardKeyInQuery */, - &bob); + if (write_without_shard_key::useTwoPhaseProtocol( + opCtx, nss, false /* isUpdateOrDelete */, isUpsert, query, collation, let, rc)) { + _runExplainWithoutShardKey(opCtx, nss, explainCmd, verbosity, &bob); + bodyBuilder.appendElementsUnique(bob.obj()); + return Status::OK(); + } else { + const BSONObj shardKey = + getShardKey(makeExpCtx(opCtx, nss, collation, boost::none, let, rc), cm, query); + const auto chunk = cm.findIntersectingChunk(shardKey, collation); + shard = uassertStatusOK( + Grid::get(opCtx)->shardRegistry()->getShard(opCtx, chunk.getShardId())); + + _runCommand(opCtx, + shard->getId(), + cri.getShardVersion(shard->getId()), + boost::none, + nss, + applyReadWriteConcern(opCtx, false, false, explainCmd), + true /* isExplain */, + boost::none /* allowShardKeyUpdatesWithoutFullShardKeyInQuery */, + &bob); + } } else { + shard = uassertStatusOK(Grid::get(opCtx)->shardRegistry()->getShard(opCtx, cm.dbPrimary())); + _runCommand(opCtx, shard->getId(), boost::make_optional(!cm.dbVersion().isFixed(), ShardVersion::UNSHARDED()), @@ -591,7 +598,6 @@ Status FindAndModifyCmd::explain(OperationContext* opCtx, AsyncRequestsSender::Response arsResponse{ shard->getId(), response, shard->getConnString().getServers().front()}; - auto bodyBuilder = result->getBodyBuilder(); return ClusterExplain::buildExplainResult( opCtx, {arsResponse}, ClusterExplain::kSingleShard, millisElapsed, cmdObj, &bodyBuilder); } @@ -681,12 +687,8 @@ bool FindAndModifyCmd::run(OperationContext* opCtx, cmdObjForShard = replaceNamespaceByBucketNss(cmdObjForShard, nss); } - _runCommandWithoutShardKey(opCtx, - nss, - applyReadWriteConcern(opCtx, this, cmdObjForShard), - false /* isExplain */, - allowShardKeyUpdatesWithoutFullShardKeyInQuery, - &result); + _runCommandWithoutShardKey( + opCtx, nss, applyReadWriteConcern(opCtx, this, cmdObjForShard), &result); } } else { findAndModifyTargetedShardedCount.increment(1); @@ -801,19 +803,18 @@ void FindAndModifyCmd::_constructResult(OperationContext* opCtx, } // Two-phase protocol to run a findAndModify command without a shard key or _id. -void FindAndModifyCmd::_runCommandWithoutShardKey( - OperationContext* opCtx, - const NamespaceString& nss, - const BSONObj& cmdObj, - bool isExplain, - boost::optional<bool> allowShardKeyUpdatesWithoutFullShardKeyInQuery, - BSONObjBuilder* result) { +void FindAndModifyCmd::_runCommandWithoutShardKey(OperationContext* opCtx, + const NamespaceString& nss, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + auto allowShardKeyUpdatesWithoutFullShardKeyInQuery = + opCtx->isRetryableWrite() || opCtx->inMultiDocumentTransaction(); auto cmdObjForPassthrough = prepareCmdObjForPassthrough(opCtx, cmdObj, nss, - isExplain, + false /* isExplain */, boost::none /* dbVersion */, boost::none /* shardVersion */, allowShardKeyUpdatesWithoutFullShardKeyInQuery); @@ -855,6 +856,50 @@ void FindAndModifyCmd::_runCommandWithoutShardKey( result); } +// Two-phase protocol to run an explain for a findAndModify command without a shard key or _id. +void FindAndModifyCmd::_runExplainWithoutShardKey(OperationContext* opCtx, + const NamespaceString& nss, + const BSONObj& cmdObj, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* result) { + auto cmdObjForPassthrough = prepareCmdObjForPassthrough( + opCtx, + cmdObj, + nss, + true /* isExplain */, + boost::none /* dbVersion */, + boost::none /* shardVersion */, + boost::none /* allowShardKeyUpdatesWithoutFullShardKeyInQuery */); + + // Explain currently cannot be run within a transaction, so each command is instead run + // separately outside of a transaction, and we compose the results at the end. + auto clusterQueryWithoutShardKeyExplainRes = [&] { + ClusterQueryWithoutShardKey clusterQueryWithoutShardKeyCommand(cmdObjForPassthrough); + const auto explainClusterQueryWithoutShardKeyCmd = + ClusterExplain::wrapAsExplain(clusterQueryWithoutShardKeyCommand.toBSON({}), verbosity); + auto opMsg = OpMsgRequest::fromDBAndBody(nss.db(), explainClusterQueryWithoutShardKeyCmd); + return CommandHelpers::runCommandDirectly(opCtx, opMsg).getOwned(); + }(); + + auto clusterWriteWithoutShardKeyExplainRes = [&] { + // Since 'explain' does not return the results of the query, we do not have an _id + // document to target by from the 'Read Phase'. We instead will use a dummy _id + // target document for the 'Write Phase'. + ClusterWriteWithoutShardKey clusterWriteWithoutShardKeyCommand( + cmdObjForPassthrough, + clusterQueryWithoutShardKeyExplainRes.getStringField("targetShardId").toString(), + write_without_shard_key::targetDocForExplain); + const auto explainClusterWriteWithoutShardKeyCmd = + ClusterExplain::wrapAsExplain(clusterWriteWithoutShardKeyCommand.toBSON({}), verbosity); + auto opMsg = OpMsgRequest::fromDBAndBody(nss.db(), explainClusterWriteWithoutShardKeyCmd); + return CommandHelpers::runCommandDirectly(opCtx, opMsg).getOwned(); + }(); + + auto output = write_without_shard_key::generateExplainResponseForTwoPhaseWriteProtocol( + clusterQueryWithoutShardKeyExplainRes, clusterWriteWithoutShardKeyExplainRes); + result->appendElementsUnique(output); +} + // Command invocation to be used if a shard key is specified or the collection is unsharded. void FindAndModifyCmd::_runCommand( OperationContext* opCtx, @@ -938,12 +983,7 @@ void FindAndModifyCmd::_handleWouldChangeOwningShardErrorRetryableWriteLegacy( getLet(cmdObj), getLegacyRuntimeConstants(cmdObj))) { findAndModifyNonTargetedShardedCount.increment(1); - _runCommandWithoutShardKey(opCtx, - nss, - stripWriteConcern(cmdObj), - false /* isExplain */, - true /* allowShardKeyUpdatesWithoutFullShardKeyInQuery */, - result); + _runCommandWithoutShardKey(opCtx, nss, stripWriteConcern(cmdObj), result); } else { findAndModifyTargetedShardedCount.increment(1); diff --git a/src/mongo/s/commands/cluster_find_and_modify_cmd.h b/src/mongo/s/commands/cluster_find_and_modify_cmd.h index ca0ec63fff3..8c2837645a2 100644 --- a/src/mongo/s/commands/cluster_find_and_modify_cmd.h +++ b/src/mongo/s/commands/cluster_find_and_modify_cmd.h @@ -156,13 +156,17 @@ private: BSONObjBuilder* result); // Two-phase protocol to run a findAndModify command without a shard key or _id. - static void _runCommandWithoutShardKey( - OperationContext* opCtx, - const NamespaceString& nss, - const BSONObj& cmdObj, - bool isExplain, - boost::optional<bool> allowShardKeyUpdatesWithoutFullShardKeyInQuery, - BSONObjBuilder* result); + static void _runCommandWithoutShardKey(OperationContext* opCtx, + const NamespaceString& nss, + const BSONObj& cmdObj, + BSONObjBuilder* result); + + // Two-phase protocol to run an explain for a findAndModify command without a shard key or _id. + static void _runExplainWithoutShardKey(OperationContext* opCtx, + const NamespaceString& nss, + const BSONObj& cmdObj, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* result); // Command invocation to be used if a shard key is specified or the collection is unsharded. static void _runCommand(OperationContext* opCtx, diff --git a/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp b/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp index cab695ddab4..e87fca927e2 100644 --- a/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp +++ b/src/mongo/s/commands/cluster_query_without_shard_key_cmd.cpp @@ -28,6 +28,7 @@ */ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" +#include "mongo/db/explain_gen.h" #include "mongo/db/internal_transactions_feature_flag_gen.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/pipeline/document_source_limit.h" @@ -39,6 +40,7 @@ #include "mongo/db/update/update_util.h" #include "mongo/logv2/log.h" #include "mongo/s/cluster_commands_helpers.h" +#include "mongo/s/commands/cluster_explain.h" #include "mongo/s/grid.h" #include "mongo/s/is_mongos.h" #include "mongo/s/multi_statement_transaction_requests_sender.h" @@ -190,9 +192,8 @@ BSONObj createAggregateCmdObj( return aggregate.toBSON({}); } -ParsedCommandInfo parseWriteCommand(OperationContext* opCtx, - StringData commandName, - const BSONObj& writeCmdObj) { +ParsedCommandInfo parseWriteCommand(OperationContext* opCtx, const BSONObj& writeCmdObj) { + auto commandName = writeCmdObj.firstElementFieldNameStringData(); ParsedCommandInfo parsedInfo; if (commandName == write_ops::UpdateCommandRequest::kCommandName) { auto updateRequest = write_ops::UpdateCommandRequest::parse( @@ -253,7 +254,8 @@ ParsedCommandInfo parseWriteCommand(OperationContext* opCtx, parsedInfo.collation = *parsedCollation; } } else { - uasserted(ErrorCodes::InvalidOptions, "Not a supported write command"); + uasserted(ErrorCodes::InvalidOptions, + str::stream() << commandName << " is not a supported batch write command"); } return parsedInfo; } @@ -276,22 +278,19 @@ public: "Running read phase for a write without a shard key.", "clientWriteRequest"_attr = request().getWriteCmd()); + const auto writeCmdObj = request().getWriteCmd(); + // Get all shard ids for shards that have chunks in the desired namespace. - const NamespaceString nss( - CommandHelpers::parseNsCollectionRequired(ns().dbName(), request().getWriteCmd())); + const NamespaceString nss = + CommandHelpers::parseNsCollectionRequired(ns().dbName(), writeCmdObj); hangBeforeMetadataRefreshClusterQuery.pauseWhileSet(opCtx); const auto cri = uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, nss)); // Parse into OpMsgRequest to append the $db field, which is required for command // parsing. - const auto opMsgRequest = - OpMsgRequest::fromDBAndBody(nss.db(), request().getWriteCmd()); - - auto parsedInfoFromRequest = - parseWriteCommand(opCtx, - request().getWriteCmd().firstElementFieldNameStringData(), - opMsgRequest.body); + const auto opMsgRequest = OpMsgRequest::fromDBAndBody(ns().db(), writeCmdObj); + auto parsedInfoFromRequest = parseWriteCommand(opCtx, opMsgRequest.body); auto allShardsContainingChunksForNs = getShardsToTarget(opCtx, cri.cm, nss, parsedInfoFromRequest); @@ -409,6 +408,74 @@ public: } private: + void explain(OperationContext* opCtx, + ExplainOptions::Verbosity verbosity, + rpc::ReplyBuilderInterface* result) override { + const auto writeCmdObj = [&] { + const auto explainCmdObj = request().getWriteCmd(); + const auto opMsgRequestExplainCmd = + OpMsgRequest::fromDBAndBody(ns().db(), explainCmdObj); + auto explainRequest = ExplainCommandRequest::parse( + IDLParserContext("_clusterQueryWithoutShardKeyExplain"), + opMsgRequestExplainCmd.body); + return explainRequest.getCommandParameter().getOwned(); + }(); + + // Get all shard ids for shards that have chunks in the desired namespace. + const NamespaceString nss = + CommandHelpers::parseNsCollectionRequired(ns().dbName(), writeCmdObj); + const auto cri = uassertStatusOK(getCollectionRoutingInfoForTxnCmd(opCtx, nss)); + + // Parse into OpMsgRequest to append the $db field, which is required for command + // parsing. + const auto opMsgRequestWriteCmd = OpMsgRequest::fromDBAndBody(ns().db(), writeCmdObj); + auto parsedInfoFromRequest = parseWriteCommand(opCtx, opMsgRequestWriteCmd.body); + + auto allShardsContainingChunksForNs = + getShardsToTarget(opCtx, cri.cm, nss, parsedInfoFromRequest); + auto cmdObj = createAggregateCmdObj(opCtx, parsedInfoFromRequest, nss, boost::none); + + const auto aggExplainCmdObj = ClusterExplain::wrapAsExplain(cmdObj, verbosity); + + std::vector<AsyncRequestsSender::Request> requests; + for (const auto& shardId : allShardsContainingChunksForNs) { + requests.emplace_back( + shardId, appendShardVersion(aggExplainCmdObj, cri.getShardVersion(shardId))); + } + + Timer timer; + MultiStatementTransactionRequestsSender ars( + opCtx, + Grid::get(opCtx)->getExecutorPool()->getArbitraryExecutor(), + request().getDbName(), + requests, + ReadPreferenceSetting(ReadPreference::PrimaryOnly), + Shard::RetryPolicy::kNoRetry); + + ShardId shardId; + std::vector<AsyncRequestsSender::Response> responses; + + while (!ars.done()) { + auto response = ars.next(); + uassertStatusOK(response.swResponse); + responses.push_back(response); + shardId = response.shardId; + } + + const auto millisElapsed = timer.millis(); + + auto bodyBuilder = result->getBodyBuilder(); + uassertStatusOK(ClusterExplain::buildExplainResult( + opCtx, + responses, + parsedInfoFromRequest.sort ? ClusterExplain::kMergeSortFromShards + : ClusterExplain::kMergeFromShards, + millisElapsed, + writeCmdObj, + &bodyBuilder)); + bodyBuilder.append("targetShardId", shardId); + } + NamespaceString ns() const override { return NamespaceString(request().getDbName()); } diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp index 147f8ad0924..783433e02f1 100644 --- a/src/mongo/s/commands/cluster_write_cmd.cpp +++ b/src/mongo/s/commands/cluster_write_cmd.cpp @@ -50,11 +50,13 @@ #include "mongo/s/commands/cluster_explain.h" #include "mongo/s/commands/document_shard_key_update_util.h" #include "mongo/s/grid.h" +#include "mongo/s/request_types/cluster_commands_without_shard_key_gen.h" #include "mongo/s/session_catalog_router.h" #include "mongo/s/transaction_router.h" #include "mongo/s/transaction_router_resource_yielder.h" #include "mongo/s/would_change_owning_shard_exception.h" #include "mongo/s/write_ops/batched_command_response.h" +#include "mongo/s/write_ops/write_without_shard_key_util.h" #include "mongo/util/timer.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding @@ -632,6 +634,78 @@ void ClusterWriteCmd::InvocationBase::run(OperationContext* opCtx, CommandHelpers::appendSimpleCommandStatus(bob, ok); } +bool ClusterWriteCmd::InvocationBase::_runExplainWithoutShardKey( + OperationContext* opCtx, + const NamespaceString& nss, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* result) { + if (_batchedRequest.getBatchType() == BatchedCommandRequest::BatchType_Delete || + _batchedRequest.getBatchType() == BatchedCommandRequest::BatchType_Update) { + bool isMultiWrite = false; + BSONObj query; + BSONObj collation; + bool isUpsert = false; + if (_batchedRequest.getBatchType() == BatchedCommandRequest::BatchType_Update) { + auto updateOp = _batchedRequest.getUpdateRequest().getUpdates().begin(); + isMultiWrite = updateOp->getMulti(); + query = updateOp->getQ(); + collation = updateOp->getCollation().value_or(BSONObj()); + isUpsert = updateOp->getUpsert(); + } else { + auto deleteOp = _batchedRequest.getDeleteRequest().getDeletes().begin(); + isMultiWrite = deleteOp->getMulti(); + query = deleteOp->getQ(); + collation = deleteOp->getCollation().value_or(BSONObj()); + } + + if (!isMultiWrite && + write_without_shard_key::useTwoPhaseProtocol( + opCtx, + nss, + true /* isUpdateOrDelete */, + isUpsert, + query, + collation, + _batchedRequest.getLet(), + _batchedRequest.getLegacyRuntimeConstants())) { + // Explain currently cannot be run within a transaction, so each command is instead run + // separately outside of a transaction, and we compose the results at the end. + auto clusterQueryWithoutShardKeyExplainRes = [&] { + ClusterQueryWithoutShardKey clusterQueryWithoutShardKeyCommand( + ClusterExplain::wrapAsExplain(_batchedRequest.toBSON(), verbosity)); + const auto explainClusterQueryWithoutShardKeyCmd = ClusterExplain::wrapAsExplain( + clusterQueryWithoutShardKeyCommand.toBSON({}), verbosity); + auto opMsg = + OpMsgRequest::fromDBAndBody(nss.db(), explainClusterQueryWithoutShardKeyCmd); + return CommandHelpers::runCommandDirectly(opCtx, opMsg).getOwned(); + }(); + + // Since 'explain' does not return the results of the query, we do not have an _id + // document to target by from the 'Read Phase'. We instead will use a dummy _id target + // document 'Write Phase'. + auto clusterWriteWithoutShardKeyExplainRes = [&] { + ClusterWriteWithoutShardKey clusterWriteWithoutShardKeyCommand( + ClusterExplain::wrapAsExplain(_batchedRequest.toBSON(), verbosity), + clusterQueryWithoutShardKeyExplainRes.getStringField("targetShardId") + .toString(), + write_without_shard_key::targetDocForExplain); + const auto explainClusterWriteWithoutShardKeyCmd = ClusterExplain::wrapAsExplain( + clusterWriteWithoutShardKeyCommand.toBSON({}), verbosity); + auto opMsg = + OpMsgRequest::fromDBAndBody(nss.db(), explainClusterWriteWithoutShardKeyCmd); + return CommandHelpers::runCommandDirectly(opCtx, opMsg).getOwned(); + }(); + + auto output = write_without_shard_key::generateExplainResponseForTwoPhaseWriteProtocol( + clusterQueryWithoutShardKeyExplainRes, clusterWriteWithoutShardKeyExplainRes); + result->appendElementsUnique(output); + return true; + } + return false; + } + return false; +} + void ClusterWriteCmd::InvocationBase::explain(OperationContext* opCtx, ExplainOptions::Verbosity verbosity, rpc::ReplyBuilderInterface* result) { @@ -652,6 +726,13 @@ void ClusterWriteCmd::InvocationBase::explain(OperationContext* opCtx, auto nss = req ? req->getNS() : _batchedRequest.getNS(); auto requestBSON = req ? req->toBSON() : _request->body; auto requestPtr = req ? req.get() : &_batchedRequest; + auto bodyBuilder = result->getBodyBuilder(); + + // If we aren't running an explain for updateOne or deleteOne without shard key, continue and + // run the original explain path. + if (_runExplainWithoutShardKey(opCtx, nss, verbosity, &bodyBuilder)) { + return; + } const auto explainCmd = ClusterExplain::wrapAsExplain(requestBSON, verbosity); @@ -662,7 +743,6 @@ void ClusterWriteCmd::InvocationBase::explain(OperationContext* opCtx, BatchItemRef targetingBatchItem(requestPtr, 0); std::vector<AsyncRequestsSender::Response> shardResponses; _commandOpWrite(opCtx, nss, explainCmd, targetingBatchItem, &shardResponses); - auto bodyBuilder = result->getBodyBuilder(); uassertStatusOK(ClusterExplain::buildExplainResult(opCtx, shardResponses, ClusterExplain::kWriteOnShards, diff --git a/src/mongo/s/commands/cluster_write_cmd.h b/src/mongo/s/commands/cluster_write_cmd.h index f29f4182808..fa8a70d51c6 100644 --- a/src/mongo/s/commands/cluster_write_cmd.h +++ b/src/mongo/s/commands/cluster_write_cmd.h @@ -155,6 +155,15 @@ private: return static_cast<const ClusterWriteCmd*>(definition()); } + /** + * Runs a two-phase protocol to explain an updateOne/deleteOne without a shard key or _id. + * Returns true if we successfully ran the protocol, false otherwise. + */ + bool _runExplainWithoutShardKey(OperationContext* opCtx, + const NamespaceString& nss, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* result); + const OpMsgRequest* _request; BatchedCommandRequest _batchedRequest; diff --git a/src/mongo/s/commands/cluster_write_without_shard_key_cmd.cpp b/src/mongo/s/commands/cluster_write_without_shard_key_cmd.cpp index c3668182621..40ccace8e72 100644 --- a/src/mongo/s/commands/cluster_write_without_shard_key_cmd.cpp +++ b/src/mongo/s/commands/cluster_write_without_shard_key_cmd.cpp @@ -29,6 +29,7 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" +#include "mongo/db/explain_gen.h" #include "mongo/db/internal_transactions_feature_flag_gen.h" #include "mongo/db/query/collation/collator_factory_interface.h" #include "mongo/db/query/projection_parser.h" @@ -36,6 +37,7 @@ #include "mongo/db/update/update_util.h" #include "mongo/logv2/log.h" #include "mongo/s/cluster_commands_helpers.h" +#include "mongo/s/commands/cluster_explain.h" #include "mongo/s/commands/cluster_find_and_modify_cmd.h" #include "mongo/s/commands/cluster_write_cmd.h" #include "mongo/s/grid.h" @@ -293,6 +295,56 @@ public: } private: + void explain(OperationContext* opCtx, + ExplainOptions::Verbosity verbosity, + rpc::ReplyBuilderInterface* result) override { + const auto shardId = ShardId(request().getShardId().toString()); + const auto writeCmdObj = [&] { + const auto explainCmdObj = request().getWriteCmd(); + const auto opMsgRequestExplainCmd = + OpMsgRequest::fromDBAndBody(ns().db(), explainCmdObj); + auto explainRequest = ExplainCommandRequest::parse( + IDLParserContext("_clusterWriteWithoutShardKeyExplain"), + opMsgRequestExplainCmd.body); + return explainRequest.getCommandParameter().getOwned(); + }(); + + const NamespaceString nss = + CommandHelpers::parseNsCollectionRequired(ns().dbName(), writeCmdObj); + const auto targetDocId = request().getTargetDocId(); + const auto commandName = writeCmdObj.firstElementFieldNameStringData(); + + const BSONObj cmdObj = + _createCmdObj(opCtx, shardId, nss, commandName, writeCmdObj, targetDocId); + + const auto explainCmdObj = ClusterExplain::wrapAsExplain(cmdObj, verbosity); + + AsyncRequestsSender::Request arsRequest(shardId, explainCmdObj); + std::vector<AsyncRequestsSender::Request> arsRequestVector({arsRequest}); + + Timer timer; + MultiStatementTransactionRequestsSender ars( + opCtx, + Grid::get(opCtx)->getExecutorPool()->getArbitraryExecutor(), + request().getDbName(), + std::move(arsRequestVector), + ReadPreferenceSetting(ReadPreference::PrimaryOnly), + Shard::RetryPolicy::kNoRetry); + + auto response = ars.next(); + uassertStatusOK(response.swResponse); + + const auto millisElapsed = timer.millis(); + + auto bodyBuilder = result->getBodyBuilder(); + uassertStatusOK(ClusterExplain::buildExplainResult(opCtx, + {response}, + ClusterExplain::kWriteOnShards, + millisElapsed, + writeCmdObj, + &bodyBuilder)); + } + NamespaceString ns() const override { return NamespaceString(request().getDbName()); } diff --git a/src/mongo/s/write_ops/write_without_shard_key_util.cpp b/src/mongo/s/write_ops/write_without_shard_key_util.cpp index f3d1ed0e4ab..ae0fb38587e 100644 --- a/src/mongo/s/write_ops/write_without_shard_key_util.cpp +++ b/src/mongo/s/write_ops/write_without_shard_key_util.cpp @@ -302,5 +302,92 @@ StatusWith<ClusterWriteWithoutShardKeyResponse> runTwoPhaseWriteProtocol(Operati return StatusWith<ClusterWriteWithoutShardKeyResponse>(swResult.getStatus()); } } + +BSONObj generateExplainResponseForTwoPhaseWriteProtocol( + const BSONObj& clusterQueryWithoutShardKeyExplainObj, + const BSONObj& clusterWriteWithoutShardKeyExplainObj) { + // To express the two phase nature of the two phase write protocol, we use the output of the + // 'Read Phase' explain as the 'inputStage' of the 'Write Phase' explain for both queryPlanner + // and executionStats sections. + // + // An example output would look like: + + // "queryPlanner" : { + // "winningPlan" : { + // "stage" : "SHARD_WRITE", + // ... + // “inputStage”: { + // queryPlanner: { + // winningPlan: { + // stage: "SHARD_MERGE", + // ... + // + // } + // } + // } + // } + // } + // + // executionStats : { + // "executionStages" : { + // "stage" : "SHARD_WRITE", + // ... + // }, + // "inputStage" : { + // "stage" : "SHARD_MERGE", + // ... + // } + // + // } + // ... other explain result fields ... + + auto queryPlannerOutput = [&] { + auto clusterQueryWithoutShardKeyQueryPlannerObj = + clusterQueryWithoutShardKeyExplainObj.getObjectField("queryPlanner"); + auto clusterWriteWithoutShardKeyQueryPlannerObj = + clusterWriteWithoutShardKeyExplainObj.getObjectField("queryPlanner"); + + auto winningPlan = clusterWriteWithoutShardKeyQueryPlannerObj.getObjectField("winningPlan"); + BSONObjBuilder newWinningPlanBuilder(winningPlan); + newWinningPlanBuilder.appendObject("inputStage", + clusterQueryWithoutShardKeyQueryPlannerObj.objdata()); + auto newWinningPlan = newWinningPlanBuilder.obj(); + + auto queryPlannerObjNoWinningPlan = + clusterWriteWithoutShardKeyQueryPlannerObj.removeField("winningPlan"); + BSONObjBuilder newQueryPlannerBuilder(queryPlannerObjNoWinningPlan); + newQueryPlannerBuilder.appendObject("winningPlan", newWinningPlan.objdata()); + return newQueryPlannerBuilder.obj(); + }(); + + auto executionStatsOutput = [&] { + auto clusterQueryWithoutShardKeyExecutionStatsObj = + clusterQueryWithoutShardKeyExplainObj.getObjectField("executionStats"); + auto clusterWriteWithoutShardKeyExecutionStatsObj = + clusterWriteWithoutShardKeyExplainObj.getObjectField("executionStats"); + + if (clusterQueryWithoutShardKeyExecutionStatsObj.isEmpty() && + clusterWriteWithoutShardKeyExecutionStatsObj.isEmpty()) { + return BSONObj(); + } + + BSONObjBuilder newExecutionStatsBuilder(clusterWriteWithoutShardKeyExecutionStatsObj); + newExecutionStatsBuilder.appendObject( + "inputStage", clusterQueryWithoutShardKeyExecutionStatsObj.objdata()); + return newExecutionStatsBuilder.obj(); + }(); + + BSONObjBuilder explainOutputBuilder; + if (!queryPlannerOutput.isEmpty()) { + explainOutputBuilder.appendObject("queryPlanner", queryPlannerOutput.objdata()); + } + if (!executionStatsOutput.isEmpty()) { + explainOutputBuilder.appendObject("executionStats", executionStatsOutput.objdata()); + } + // This step is to get 'command', 'serverInfo', and 'serverParamter' fields to return in the + // final explain output. + explainOutputBuilder.appendElementsUnique(clusterWriteWithoutShardKeyExplainObj); + return explainOutputBuilder.obj(); +} } // namespace write_without_shard_key } // namespace mongo diff --git a/src/mongo/s/write_ops/write_without_shard_key_util.h b/src/mongo/s/write_ops/write_without_shard_key_util.h index bb7e4d0509b..5fb9bb35d68 100644 --- a/src/mongo/s/write_ops/write_without_shard_key_util.h +++ b/src/mongo/s/write_ops/write_without_shard_key_util.h @@ -38,6 +38,11 @@ namespace mongo { namespace write_without_shard_key { +// Used as a "dummy" target document for constructing explain responses for single writes without +// shard key. +const BSONObj targetDocForExplain = BSON("_id" + << "WriteWithoutShardKey"); + /** * Uses updateDriver to produce the document to insert. Only use when {upsert: true}. */ @@ -75,6 +80,13 @@ bool useTwoPhaseProtocol(OperationContext* opCtx, StatusWith<ClusterWriteWithoutShardKeyResponse> runTwoPhaseWriteProtocol(OperationContext* opCtx, NamespaceString nss, BSONObj cmdObj); +/** + * Return a formatted 'explain' response that describes the work done in the two phase write + * protocol. + **/ +BSONObj generateExplainResponseForTwoPhaseWriteProtocol( + const BSONObj& clusterQueryWithoutShardKeyExplainObj, + const BSONObj& clusterWriteWithoutShardKeyExplainObj); } // namespace write_without_shard_key } // namespace mongo |