From 7a59ba6da674462e7f86a3a7111d58b0721a4138 Mon Sep 17 00:00:00 2001 From: David Storch Date: Thu, 3 Sep 2015 13:07:08 -0400 Subject: SERVER-20267 use the explain command path to answer OP_QUERY with $explain on mongos --- jstests/core/explain_find.js | 9 +++++++++ src/mongo/db/dbmessage.cpp | 2 +- src/mongo/db/dbmessage.h | 2 +- src/mongo/s/commands/cluster_find_cmd.cpp | 31 +++++------------------------ src/mongo/s/query/cluster_find.cpp | 33 +++++++++++++++++++++++++++++++ src/mongo/s/query/cluster_find.h | 20 +++++++++++++++++++ src/mongo/s/strategy.cpp | 31 ++++++++++++++++++++++++++++- 7 files changed, 99 insertions(+), 29 deletions(-) diff --git a/jstests/core/explain_find.js b/jstests/core/explain_find.js index fe6ac234986..2e2699ea05b 100644 --- a/jstests/core/explain_find.js +++ b/jstests/core/explain_find.js @@ -32,3 +32,12 @@ explain = db.runCommand({ printjson(explain); assert.commandWorked(explain); assert.eq(2, explain.executionStats.nReturned); + +// Compatibility test for the $explain OP_QUERY flag. This can only run if find command is disabled. +if (!db.getMongo().useReadCommands()) { + var explain = t.find({$query: {a: 4}, $explain: true}).limit(-1).next(); + assert("queryPlanner" in explain); + assert("executionStats" in explain); + assert.eq(1, explain.executionStats.nReturned); + assert("allPlansExecution" in explain.executionStats); +} diff --git a/src/mongo/db/dbmessage.cpp b/src/mongo/db/dbmessage.cpp index b1e6a245c28..eb50ecb063a 100644 --- a/src/mongo/db/dbmessage.cpp +++ b/src/mongo/db/dbmessage.cpp @@ -172,7 +172,7 @@ T DbMessage::readAndAdvance() { void replyToQuery(int queryResultFlags, AbstractMessagingPort* p, Message& requestMsg, - void* data, + const void* data, int size, int nReturned, int startingFrom, diff --git a/src/mongo/db/dbmessage.h b/src/mongo/db/dbmessage.h index 73c30f09bac..aa74fd59ffb 100644 --- a/src/mongo/db/dbmessage.h +++ b/src/mongo/db/dbmessage.h @@ -327,7 +327,7 @@ struct DbResponse { void replyToQuery(int queryResultFlags, AbstractMessagingPort* p, Message& requestMsg, - void* data, + const void* data, int size, int nReturned, int startingFrom = 0, diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp index 68cd2c00953..937515d17c7 100644 --- a/src/mongo/s/commands/cluster_find_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_cmd.cpp @@ -35,10 +35,7 @@ #include "mongo/db/commands.h" #include "mongo/db/query/cursor_response.h" #include "mongo/db/stats/counters.h" -#include "mongo/s/cluster_explain.h" #include "mongo/s/query/cluster_find.h" -#include "mongo/s/strategy.h" -#include "mongo/util/timer.h" namespace mongo { namespace { @@ -116,31 +113,13 @@ public: // Parse the command BSON to a LiteParsedQuery. bool isExplain = true; - auto lpqStatus = LiteParsedQuery::makeFromFindCommand(std::move(nss), cmdObj, isExplain); - if (!lpqStatus.isOK()) { - return lpqStatus.getStatus(); + auto lpq = LiteParsedQuery::makeFromFindCommand(std::move(nss), cmdObj, isExplain); + if (!lpq.isOK()) { + return lpq.getStatus(); } - auto& lpq = lpqStatus.getValue(); - - BSONObjBuilder explainCmdBob; - int options = 0; - ClusterExplain::wrapAsExplain( - cmdObj, verbosity, serverSelectionMetadata, &explainCmdBob, &options); - - // We will time how long it takes to run the commands on the shards. - Timer timer; - - vector shardResults; - Strategy::commandOp( - txn, dbname, explainCmdBob.obj(), options, fullns, lpq->getFilter(), &shardResults); - - long long millisElapsed = timer.millis(); - - const char* mongosStageName = ClusterExplain::getStageNameForReadOp(shardResults, cmdObj); - - return ClusterExplain::buildExplainResult( - txn, shardResults, mongosStageName, millisElapsed, out); + return ClusterFind::runExplain( + txn, cmdObj, *lpq.getValue(), verbosity, serverSelectionMetadata, out); } bool run(OperationContext* txn, diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp index 0d0fb418333..4f40dd3c795 100644 --- a/src/mongo/s/query/cluster_find.cpp +++ b/src/mongo/s/query/cluster_find.cpp @@ -42,9 +42,11 @@ #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/find_common.h" #include "mongo/db/query/getmore_request.h" +#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/catalog/catalog_cache.h" #include "mongo/s/chunk_manager.h" #include "mongo/s/client/shard_registry.h" +#include "mongo/s/cluster_explain.h" #include "mongo/s/config.h" #include "mongo/s/grid.h" #include "mongo/s/query/cluster_client_cursor_impl.h" @@ -329,6 +331,37 @@ StatusWith ClusterFind::runGetMore(OperationContext* txn, return CursorResponse(request.nss, idToReturn, std::move(batch), startingFrom); } +Status ClusterFind::runExplain(OperationContext* txn, + const BSONObj& findCommand, + const LiteParsedQuery& lpq, + ExplainCommon::Verbosity verbosity, + const rpc::ServerSelectionMetadata& serverSelectionMetadata, + BSONObjBuilder* out) { + BSONObjBuilder explainCmdBob; + int options = 0; + ClusterExplain::wrapAsExplain( + findCommand, verbosity, serverSelectionMetadata, &explainCmdBob, &options); + + // We will time how long it takes to run the commands on the shards. + Timer timer; + + std::vector shardResults; + Strategy::commandOp(txn, + lpq.nss().db().toString(), + explainCmdBob.obj(), + options, + lpq.nss().toString(), + lpq.getFilter(), + &shardResults); + + long long millisElapsed = timer.millis(); + + const char* mongosStageName = ClusterExplain::getStageNameForReadOp(shardResults, findCommand); + + return ClusterExplain::buildExplainResult( + txn, shardResults, mongosStageName, millisElapsed, out); +} + StatusWith ClusterFind::extractUnwrappedReadPref(const BSONObj& cmdObj, const bool isSlaveOk) { BSONElement queryOptionsElt; diff --git a/src/mongo/s/query/cluster_find.h b/src/mongo/s/query/cluster_find.h index ad878a11b37..7a7d3deac69 100644 --- a/src/mongo/s/query/cluster_find.h +++ b/src/mongo/s/query/cluster_find.h @@ -33,6 +33,7 @@ #include "mongo/bson/bsonobj.h" #include "mongo/db/cursor_id.h" #include "mongo/db/query/cursor_response.h" +#include "mongo/db/query/explain_common.h" namespace mongo { @@ -43,6 +44,10 @@ class OperationContext; struct GetMoreRequest; struct ReadPreferenceSetting; +namespace rpc { +class ServerSelectionMetadata; +} // namespace rpc + /** * Methods for running find and getMore operations across a sharded cluster. */ @@ -70,6 +75,21 @@ public: static StatusWith runGetMore(OperationContext* txn, const GetMoreRequest& request); + /** + * Helper to run an explain of a find operation on the shards. Fills 'out' with the result of + * the of the explain command on success. On failure, returns a non-OK status and does not + * modify 'out'. + * + * Used both if mongos receives an explain command and if it receives an OP_QUERY find with the + * $explain modifier. + */ + static Status runExplain(OperationContext* txn, + const BSONObj& findCommand, + const LiteParsedQuery& lpq, + ExplainCommon::Verbosity verbosity, + const rpc::ServerSelectionMetadata& serverSelectionMetadata, + BSONObjBuilder* out); + /** * Extracts the read preference from 'cmdObj', or determines the read pref based on 'isSlaveOk' * if 'cmdObj' does not contain a read preference. diff --git a/src/mongo/s/strategy.cpp b/src/mongo/s/strategy.cpp index 89bc8a0e2d4..357cd8843c0 100644 --- a/src/mongo/s/strategy.cpp +++ b/src/mongo/s/strategy.cpp @@ -49,6 +49,7 @@ #include "mongo/db/query/lite_parsed_query.h" #include "mongo/db/query/getmore_request.h" #include "mongo/db/stats/counters.h" +#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/bson_serializable.h" #include "mongo/s/catalog/catalog_cache.h" #include "mongo/s/client/shard_registry.h" @@ -194,6 +195,34 @@ void Strategy::queryOp(OperationContext* txn, Request& request) { auto canonicalQuery = CanonicalQuery::canonicalize(q, WhereCallbackNoop()); uassertStatusOK(canonicalQuery.getStatus()); + // If the $explain flag was set, we must run the operation on the shards as an explain + // command rather than a find command. + if (canonicalQuery.getValue()->getParsed().isExplain()) { + const LiteParsedQuery& lpq = canonicalQuery.getValue()->getParsed(); + BSONObj findCommand = lpq.asFindCommand(); + + // We default to allPlansExecution verbosity. + auto verbosity = ExplainCommon::EXEC_ALL_PLANS; + + const bool secondaryOk = (readPreference.pref != ReadPreference::PrimaryOnly); + rpc::ServerSelectionMetadata metadata(secondaryOk, readPreference); + + BSONObjBuilder explainBuilder; + uassertStatusOK(ClusterFind::runExplain( + txn, findCommand, lpq, verbosity, metadata, &explainBuilder)); + + BSONObj explainObj = explainBuilder.done(); + replyToQuery(0, // query result flags + request.p(), + request.m(), + static_cast(explainObj.objdata()), + explainObj.objsize(), + 1, // numResults + 0, // startingFrom + CursorId(0)); + return; + } + // Do the work to generate the first batch of results. This blocks waiting to get responses // from the shard(s). std::vector batch; @@ -205,11 +234,11 @@ void Strategy::queryOp(OperationContext* txn, Request& request) { ClusterFind::runQuery(txn, *canonicalQuery.getValue(), readPreference, &batch); uassertStatusOK(cursorId.getStatus()); - // Build the response document. // TODO: this constant should be shared between mongos and mongod, and should // not be inside ShardedClientCursor. BufBuilder buffer(ShardedClientCursor::INIT_REPLY_BUFFER_SIZE); + // Fill out the response buffer. int numResults = 0; for (const auto& obj : batch) { buffer.appendBuf((void*)obj.objdata(), obj.objsize()); -- cgit v1.2.1