summaryrefslogtreecommitdiff
path: root/src/mongo/db/query
diff options
context:
space:
mode:
authorAnton Korshunov <anton.korshunov@mongodb.com>2020-09-29 14:06:19 +0100
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-10-23 20:47:32 +0000
commit46920134f2a6f4de507bbe5abbe3a3faa304bdf7 (patch)
tree3e963cdc7eedf51e3a4def31a84ffad83fb551c1 /src/mongo/db/query
parent66fb8deb11d0f1f8c8a7456ac005962d58e5692e (diff)
downloadmongo-46920134f2a6f4de507bbe5abbe3a3faa304bdf7.tar.gz
SERVER-50743 Support generation of "planSummary" stats in SBE
Diffstat (limited to 'src/mongo/db/query')
-rw-r--r--src/mongo/db/query/SConscript1
-rw-r--r--src/mongo/db/query/explain.cpp11
-rw-r--r--src/mongo/db/query/get_executor.cpp71
-rw-r--r--src/mongo/db/query/plan_executor_factory.cpp49
-rw-r--r--src/mongo/db/query/plan_executor_factory.h12
-rw-r--r--src/mongo/db/query/plan_executor_impl.cpp3
-rw-r--r--src/mongo/db/query/plan_executor_sbe.cpp66
-rw-r--r--src/mongo/db/query/plan_executor_sbe.h20
-rw-r--r--src/mongo/db/query/plan_explainer_factory.cpp53
-rw-r--r--src/mongo/db/query/plan_explainer_factory.h22
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.cpp73
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.h18
-rw-r--r--src/mongo/db/query/plan_ranker.h150
-rw-r--r--src/mongo/db/query/plan_ranker_test.cpp2
-rw-r--r--src/mongo/db/query/plan_ranker_util.h184
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.cpp28
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.h4
-rw-r--r--src/mongo/db/query/sbe_multi_planner.cpp10
-rw-r--r--src/mongo/db/query/sbe_multi_planner.h4
-rw-r--r--src/mongo/db/query/sbe_runtime_planner.h18
-rw-r--r--src/mongo/db/query/sbe_sub_planner.cpp18
-rw-r--r--src/mongo/db/query/sbe_sub_planner.h4
-rw-r--r--src/mongo/db/query/stage_types.cpp83
-rw-r--r--src/mongo/db/query/stage_types.h3
24 files changed, 606 insertions, 301 deletions
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index 769c1c30599..6559ae6d5be 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -53,6 +53,7 @@ env.Library(
"query_settings.cpp",
"query_solution.cpp",
"expression_index_knobs.idl",
+ "stage_types.cpp",
],
LIBDEPS=[
"$BUILD_DIR/mongo/base",
diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp
index 7d647f13d5c..a921e706501 100644
--- a/src/mongo/db/query/explain.cpp
+++ b/src/mongo/db/query/explain.cpp
@@ -62,6 +62,7 @@
#include "mongo/util/net/socket_utils.h"
#include "mongo/util/str.h"
#include "mongo/util/version.h"
+#include "mongo/util/visit_helper.h"
namespace mongo {
namespace {
@@ -362,7 +363,15 @@ void Explain::planCacheEntryToBSON(const PlanCacheEntry& entry, BSONObjBuilder*
}
}
- auto explainer = plan_explainer_factory::makePlanExplainer<PlanStage>(nullptr, nullptr);
+ auto explainer = stdx::visit(
+ visit_helper::Overloaded{
+ [](const std::vector<std::unique_ptr<PlanStageStats>>& stats) {
+ return plan_explainer_factory::make(nullptr);
+ },
+ [](const std::vector<std::unique_ptr<sbe::PlanStageStats>>& stats) {
+ return plan_explainer_factory::make(nullptr, nullptr);
+ }},
+ debugInfo.decision->stats);
auto plannerStats =
explainer->getCachedPlanStats(debugInfo, ExplainOptions::Verbosity::kQueryPlanner);
auto execStats =
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index c2da4d5ac05..20afe2d5b03 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -429,7 +429,7 @@ public:
std::string getPlanSummary() const final {
invariant(_root);
- auto explainer = plan_explainer_factory::makePlanExplainer(_root.get(), nullptr);
+ auto explainer = plan_explainer_factory::make(_root.get());
return explainer->getPlanSummary();
}
@@ -483,8 +483,7 @@ public:
invariant(_roots.size() == 1);
invariant(_solutions.size() == 1);
invariant(_roots[0].first);
- auto explainer =
- plan_explainer_factory::makePlanExplainer(_roots[0].first.get(), _solutions[0].get());
+ auto explainer = plan_explainer_factory::make(_roots[0].first.get(), _solutions[0].get());
return explainer->getPlanSummary();
}
@@ -1096,19 +1095,19 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe
yieldPolicy.get(),
plannerOptions)) {
// Do the runtime planning and pick the best candidate plan.
- auto plan = planner->plan(std::move(solutions), std::move(roots));
+ auto candidates = planner->plan(std::move(solutions), std::move(roots));
return plan_executor_factory::make(opCtx,
std::move(cq),
- {std::move(plan.root), std::move(plan.data)},
+ std::move(candidates),
collection,
std::move(nss),
- std::move(plan.results),
std::move(yieldPolicy));
}
// No need for runtime planning, just use the constructed plan stage tree.
invariant(roots.size() == 1);
return plan_executor_factory::make(opCtx,
std::move(cq),
+ std::move(solutions[0]),
std::move(roots[0]),
collection,
std::move(nss),
@@ -2090,20 +2089,22 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorForS
auto&& root = stage_builder::buildClassicExecutableTree(
opCtx, collection, *parsedDistinct->getQuery(), *soln, ws.get());
- auto explainer = plan_explainer_factory::makePlanExplainer(root.get(), soln.get());
- LOGV2_DEBUG(20931,
- 2,
- "Using fast distinct",
- "query"_attr = redact(parsedDistinct->getQuery()->toStringShort()),
- "planSummary"_attr = explainer->getPlanSummary());
+ auto exec = plan_executor_factory::make(parsedDistinct->releaseQuery(),
+ std::move(ws),
+ std::move(root),
+ coll,
+ yieldPolicy,
+ NamespaceString(),
+ std::move(soln));
+ if (exec.isOK()) {
+ LOGV2_DEBUG(20931,
+ 2,
+ "Using fast distinct",
+ "query"_attr = redact(parsedDistinct->getQuery()->toStringShort()),
+ "planSummary"_attr = exec.getValue()->getPlanExplainer().getPlanSummary());
+ }
- return plan_executor_factory::make(parsedDistinct->releaseQuery(),
- std::move(ws),
- std::move(root),
- coll,
- yieldPolicy,
- NamespaceString(),
- std::move(soln));
+ return exec;
}
// Checks each solution in the 'solutions' std::vector to see if one includes an IXSCAN that can be
@@ -2137,21 +2138,23 @@ getExecutorDistinctFromIndexSolutions(OperationContext* opCtx,
auto&& root = stage_builder::buildClassicExecutableTree(
opCtx, collection, *parsedDistinct->getQuery(), *currentSolution, ws.get());
- auto explainer =
- plan_explainer_factory::makePlanExplainer(root.get(), currentSolution.get());
- LOGV2_DEBUG(20932,
- 2,
- "Using fast distinct",
- "query"_attr = redact(parsedDistinct->getQuery()->toStringShort()),
- "planSummary"_attr = explainer->getPlanSummary());
-
- return plan_executor_factory::make(parsedDistinct->releaseQuery(),
- std::move(ws),
- std::move(root),
- coll,
- yieldPolicy,
- NamespaceString(),
- std::move(currentSolution));
+ auto exec = plan_executor_factory::make(parsedDistinct->releaseQuery(),
+ std::move(ws),
+ std::move(root),
+ coll,
+ yieldPolicy,
+ NamespaceString(),
+ std::move(currentSolution));
+ if (exec.isOK()) {
+ LOGV2_DEBUG(20932,
+ 2,
+ "Using fast distinct",
+ "query"_attr = redact(parsedDistinct->getQuery()->toStringShort()),
+ "planSummary"_attr =
+ exec.getValue()->getPlanExplainer().getPlanSummary());
+ }
+
+ return exec;
}
}
diff --git a/src/mongo/db/query/plan_executor_factory.cpp b/src/mongo/db/query/plan_executor_factory.cpp
index ff15d3741cf..b9583c4316c 100644
--- a/src/mongo/db/query/plan_executor_factory.cpp
+++ b/src/mongo/db/query/plan_executor_factory.cpp
@@ -37,6 +37,7 @@
#include "mongo/db/pipeline/plan_executor_pipeline.h"
#include "mongo/db/query/plan_executor_impl.h"
#include "mongo/db/query/plan_executor_sbe.h"
+#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/logv2/log.h"
namespace mongo::plan_executor_factory {
@@ -112,6 +113,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
OperationContext* opCtx,
std::unique_ptr<CanonicalQuery> cq,
+ std::unique_ptr<QuerySolution> solution,
std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root,
const CollectionPtr* collection,
NamespaceString nss,
@@ -127,43 +129,42 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
rootStage->prepare(data.ctx);
- auto exec = new PlanExecutorSBE(opCtx,
- std::move(cq),
- std::move(root),
- *collection,
- std::move(nss),
- false,
- boost::none,
- std::move(yieldPolicy));
- return {{exec, PlanExecutor::Deleter{opCtx}}};
+ return {{new PlanExecutorSBE(
+ opCtx,
+ std::move(cq),
+ {makeVector<sbe::plan_ranker::CandidatePlan>(sbe::plan_ranker::CandidatePlan{
+ std::move(solution), std::move(rootStage), std::move(data)}),
+ 0},
+ *collection,
+ std::move(nss),
+ false,
+ std::move(yieldPolicy)),
+ PlanExecutor::Deleter{opCtx}}};
}
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
OperationContext* opCtx,
std::unique_ptr<CanonicalQuery> cq,
- std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root,
+ sbe::CandidatePlans candidates,
const CollectionPtr* collection,
NamespaceString nss,
- std::queue<std::pair<BSONObj, boost::optional<RecordId>>> stash,
std::unique_ptr<PlanYieldPolicySBE> yieldPolicy) {
dassert(collection);
- auto&& [rootStage, data] = root;
LOGV2_DEBUG(4822861,
5,
"SBE plan",
- "slots"_attr = data.debugString(),
- "stages"_attr = sbe::DebugPrinter{}.print(rootStage.get()));
-
- auto exec = new PlanExecutorSBE(opCtx,
- std::move(cq),
- std::move(root),
- *collection,
- std::move(nss),
- true,
- stash,
- std::move(yieldPolicy));
- return {{exec, PlanExecutor::Deleter{opCtx}}};
+ "slots"_attr = candidates.winner().data.debugString(),
+ "stages"_attr = sbe::DebugPrinter{}.print(candidates.winner().root.get()));
+
+ return {{new PlanExecutorSBE(opCtx,
+ std::move(cq),
+ std::move(candidates),
+ *collection,
+ std::move(nss),
+ true,
+ std::move(yieldPolicy)),
+ PlanExecutor::Deleter{opCtx}}};
}
std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> make(
diff --git a/src/mongo/db/query/plan_executor_factory.h b/src/mongo/db/query/plan_executor_factory.h
index f78c8f36fad..0baf1342628 100644
--- a/src/mongo/db/query/plan_executor_factory.h
+++ b/src/mongo/db/query/plan_executor_factory.h
@@ -37,6 +37,8 @@
#include "mongo/db/query/plan_executor.h"
#include "mongo/db/query/plan_yield_policy_sbe.h"
#include "mongo/db/query/query_solution.h"
+#include "mongo/db/query/sbe_plan_ranker.h"
+#include "mongo/db/query/sbe_runtime_planner.h"
#include "mongo/db/query/sbe_stage_builder.h"
namespace mongo::plan_executor_factory {
@@ -104,23 +106,23 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
OperationContext* opCtx,
std::unique_ptr<CanonicalQuery> cq,
+ std::unique_ptr<QuerySolution> solution,
std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root,
const CollectionPtr* collection,
NamespaceString nss,
std::unique_ptr<PlanYieldPolicySBE> yieldPolicy);
/**
- * Similar to the factory function above in that it also constructs an executor for the SBE plan
- * 'root'. This overload allows callers to pass a pre-existing queue ('stash') of BSON objects or
- * record ids to return to the caller.
+ * Similar to the factory function above in that it also constructs an executor for the winning SBE
+ * plan passed in 'candidates' vector. This overload allows callers to pass a pre-existing queue
+ * ('stash') of BSON objects or record ids to return to the caller.
*/
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
OperationContext* opCtx,
std::unique_ptr<CanonicalQuery> cq,
- std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root,
+ sbe::CandidatePlans candidates,
const CollectionPtr* collection,
NamespaceString nss,
- std::queue<std::pair<BSONObj, boost::optional<RecordId>>> stash,
std::unique_ptr<PlanYieldPolicySBE> yieldPolicy);
/**
diff --git a/src/mongo/db/query/plan_executor_impl.cpp b/src/mongo/db/query/plan_executor_impl.cpp
index d571cced21b..36dedccdd53 100644
--- a/src/mongo/db/query/plan_executor_impl.cpp
+++ b/src/mongo/db/query/plan_executor_impl.cpp
@@ -56,6 +56,7 @@
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/query/mock_yield_policies.h"
#include "mongo/db/query/plan_explainer_factory.h"
+#include "mongo/db/query/plan_explainer_impl.h"
#include "mongo/db/query/plan_insert_listener.h"
#include "mongo/db/query/plan_yield_policy_impl.h"
#include "mongo/db/repl/replication_coordinator.h"
@@ -121,7 +122,7 @@ PlanExecutorImpl::PlanExecutorImpl(OperationContext* opCtx,
_workingSet(std::move(ws)),
_qs(std::move(qs)),
_root(std::move(rt)),
- _planExplainer(plan_explainer_factory::makePlanExplainer(_root.get(), nullptr)),
+ _planExplainer(plan_explainer_factory::make(_root.get())),
_nss(std::move(nss)),
// There's no point in yielding if the collection doesn't exist.
_yieldPolicy(
diff --git a/src/mongo/db/query/plan_executor_sbe.cpp b/src/mongo/db/query/plan_executor_sbe.cpp
index 16a3ee3865f..875956d117e 100644
--- a/src/mongo/db/query/plan_executor_sbe.cpp
+++ b/src/mongo/db/query/plan_executor_sbe.cpp
@@ -39,58 +39,57 @@
#include "mongo/db/query/sbe_stage_builder.h"
namespace mongo {
-PlanExecutorSBE::PlanExecutorSBE(
- OperationContext* opCtx,
- std::unique_ptr<CanonicalQuery> cq,
- std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root,
- const CollectionPtr& collection,
- NamespaceString nss,
- bool isOpen,
- boost::optional<std::queue<std::pair<BSONObj, boost::optional<RecordId>>>> stash,
- std::unique_ptr<PlanYieldPolicySBE> yieldPolicy)
+PlanExecutorSBE::PlanExecutorSBE(OperationContext* opCtx,
+ std::unique_ptr<CanonicalQuery> cq,
+ sbe::CandidatePlans candidates,
+ const CollectionPtr& collection,
+ NamespaceString nss,
+ bool isOpen,
+ std::unique_ptr<PlanYieldPolicySBE> yieldPolicy)
: _state{isOpen ? State::kOpened : State::kClosed},
_opCtx(opCtx),
_nss(std::move(nss)),
- _env{root.second.env},
- _ctx(std::move(root.second.ctx)),
- _root(std::move(root.first)),
+ _env{candidates.winner().data.env},
+ _ctx{std::move(candidates.winner().data.ctx)},
_cq{std::move(cq)},
- _yieldPolicy(std::move(yieldPolicy)),
- // TODO SERVER-50743: plumb through QuerySolution to init the PlanExplainer.
- _planExplainer{plan_explainer_factory::makePlanExplainer(_root.get(), nullptr)} {
- invariant(_root);
+ _yieldPolicy(std::move(yieldPolicy)) {
+
invariant(!_nss.isEmpty());
- auto&& data = root.second;
+ auto winner = std::move(candidates.plans[candidates.winnerIdx]);
+
+ _root = std::move(winner.root);
+ invariant(_root);
+ _solution = std::move(winner.solution);
- if (data.resultSlot) {
- _result = _root->getAccessor(data.ctx, *data.resultSlot);
+ if (winner.data.resultSlot) {
+ _result = _root->getAccessor(winner.data.ctx, *winner.data.resultSlot);
uassert(4822865, "Query does not have result slot.", _result);
}
- if (data.recordIdSlot) {
- _resultRecordId = _root->getAccessor(data.ctx, *data.recordIdSlot);
+ if (winner.data.recordIdSlot) {
+ _resultRecordId = _root->getAccessor(winner.data.ctx, *winner.data.recordIdSlot);
uassert(4822866, "Query does not have recordId slot.", _resultRecordId);
}
- if (data.oplogTsSlot) {
- _oplogTs = _root->getAccessor(data.ctx, *data.oplogTsSlot);
+ if (winner.data.oplogTsSlot) {
+ _oplogTs = _root->getAccessor(winner.data.ctx, *winner.data.oplogTsSlot);
uassert(4822867, "Query does not have oplogTs slot.", _oplogTs);
}
- if (data.shouldUseTailableScan) {
+ if (winner.data.shouldUseTailableScan) {
_resumeRecordIdSlot = _env->getSlot("resumeRecordId"_sd);
}
- _shouldTrackLatestOplogTimestamp = data.shouldTrackLatestOplogTimestamp;
- _shouldTrackResumeToken = data.shouldTrackResumeToken;
+ _shouldTrackLatestOplogTimestamp = winner.data.shouldTrackLatestOplogTimestamp;
+ _shouldTrackResumeToken = winner.data.shouldTrackResumeToken;
if (!isOpen) {
_root->attachFromOperationContext(_opCtx);
}
- if (stash) {
- _stash = std::move(*stash);
+ if (!winner.results.empty()) {
+ _stash = std::move(winner.results);
}
// Callers are allowed to disable yielding for this plan by passing a null yield policy.
@@ -101,6 +100,17 @@ PlanExecutorSBE::PlanExecutorSBE(
_yieldPolicy->clearRegisteredPlans();
_yieldPolicy->registerPlan(_root.get());
}
+
+ const auto isMultiPlan = candidates.plans.size() > 0;
+ if (!_cq->getExpCtx()->explain) {
+ // If we're not in explain mode, there is no need to keep rejected candidate plans around.
+ candidates.plans.clear();
+ } else {
+ // Keep only rejected candidate plans.
+ candidates.plans.erase(candidates.plans.begin() + candidates.winnerIdx);
+ }
+ _planExplainer = plan_explainer_factory::make(
+ _root.get(), _solution.get(), std::move(candidates.plans), isMultiPlan);
}
void PlanExecutorSBE::saveState() {
diff --git a/src/mongo/db/query/plan_executor_sbe.h b/src/mongo/db/query/plan_executor_sbe.h
index 8f402336a31..890eb28a56b 100644
--- a/src/mongo/db/query/plan_executor_sbe.h
+++ b/src/mongo/db/query/plan_executor_sbe.h
@@ -33,21 +33,22 @@
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/query/plan_executor.h"
+#include "mongo/db/query/plan_explainer_sbe.h"
#include "mongo/db/query/plan_yield_policy_sbe.h"
+#include "mongo/db/query/sbe_plan_ranker.h"
+#include "mongo/db/query/sbe_runtime_planner.h"
#include "mongo/db/query/sbe_stage_builder.h"
namespace mongo {
class PlanExecutorSBE final : public PlanExecutor {
public:
- PlanExecutorSBE(
- OperationContext* opCtx,
- std::unique_ptr<CanonicalQuery> cq,
- std::pair<std::unique_ptr<sbe::PlanStage>, stage_builder::PlanStageData> root,
- const CollectionPtr& collection,
- NamespaceString nss,
- bool isOpen,
- boost::optional<std::queue<std::pair<BSONObj, boost::optional<RecordId>>>> stash,
- std::unique_ptr<PlanYieldPolicySBE> yieldPolicy);
+ PlanExecutorSBE(OperationContext* opCtx,
+ std::unique_ptr<CanonicalQuery> cq,
+ sbe::CandidatePlans candidates,
+ const CollectionPtr& collection,
+ NamespaceString nss,
+ bool isOpen,
+ std::unique_ptr<PlanYieldPolicySBE> yieldPolicy);
CanonicalQuery* getCanonicalQuery() const override {
return _cq.get();
@@ -137,6 +138,7 @@ private:
sbe::RuntimeEnvironment* _env{nullptr};
sbe::CompileCtx _ctx;
std::unique_ptr<sbe::PlanStage> _root;
+ std::unique_ptr<QuerySolution> _solution;
sbe::value::SlotAccessor* _result{nullptr};
sbe::value::SlotAccessor* _resultRecordId{nullptr};
diff --git a/src/mongo/db/query/plan_explainer_factory.cpp b/src/mongo/db/query/plan_explainer_factory.cpp
new file mode 100644
index 00000000000..e990e231165
--- /dev/null
+++ b/src/mongo/db/query/plan_explainer_factory.cpp
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2020-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/query/plan_explainer_factory.h"
+
+#include "mongo/db/query/plan_explainer_impl.h"
+#include "mongo/db/query/plan_explainer_sbe.h"
+
+namespace mongo::plan_explainer_factory {
+std::unique_ptr<PlanExplainer> make(PlanStage* root) {
+ return std::make_unique<PlanExplainerImpl>(root);
+}
+
+std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, const QuerySolution* solution) {
+ return make(root, solution, {}, false);
+}
+
+std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root,
+ const QuerySolution* solution,
+ std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
+ bool isMultiPlan) {
+ return std::make_unique<PlanExplainerSBE>(
+ root, solution, std::move(rejectedCandidates), isMultiPlan);
+}
+} // namespace mongo::plan_explainer_factory
diff --git a/src/mongo/db/query/plan_explainer_factory.h b/src/mongo/db/query/plan_explainer_factory.h
index 12a0967f276..178d1c7dd28 100644
--- a/src/mongo/db/query/plan_explainer_factory.h
+++ b/src/mongo/db/query/plan_explainer_factory.h
@@ -29,17 +29,17 @@
#pragma once
-#include "mongo/db/query/plan_explainer_impl.h"
-#include "mongo/db/query/plan_explainer_sbe.h"
+#include "mongo/db/exec/plan_stage.h"
+#include "mongo/db/exec/sbe/stages/stages.h"
+#include "mongo/db/query/plan_explainer.h"
+#include "mongo/db/query/query_solution.h"
+#include "mongo/db/query/sbe_plan_ranker.h"
namespace mongo::plan_explainer_factory {
-template <typename T>
-std::unique_ptr<PlanExplainer> makePlanExplainer(T* root, const QuerySolution* solution) {
- if constexpr (std::is_same_v<T, sbe::PlanStage>) {
- return std::make_unique<PlanExplainerSBE>(root, solution);
- } else {
- static_assert(std::is_same_v<T, PlanStage>);
- return std::make_unique<PlanExplainerImpl>(root);
- }
-}
+std::unique_ptr<PlanExplainer> make(PlanStage* root);
+std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root, const QuerySolution* solution);
+std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root,
+ const QuerySolution* solution,
+ std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
+ bool isMultiPlan);
} // namespace mongo::plan_explainer_factory
diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp
index 42b5e29a546..1b930e0bb3c 100644
--- a/src/mongo/db/query/plan_explainer_sbe.cpp
+++ b/src/mongo/db/query/plan_explainer_sbe.cpp
@@ -31,10 +31,77 @@
#include "mongo/db/query/plan_explainer_sbe.h"
+#include <queue>
+
+#include "mongo/db/keypattern.h"
+
namespace mongo {
std::string PlanExplainerSBE::getPlanSummary() const {
- // TODO: SERVER-50743
- return "unsupported";
+ if (!_solution) {
+ return {};
+ }
+
+ StringBuilder sb;
+ std::queue<const QuerySolutionNode*> queue;
+ queue.push(_solution->root());
+
+ while (!queue.empty()) {
+ auto node = queue.front();
+ queue.pop();
+
+ sb << stageTypeToString(node->getType());
+
+ switch (node->getType()) {
+ case STAGE_COUNT_SCAN: {
+ auto csn = static_cast<const CountScanNode*>(node);
+ const KeyPattern keyPattern{csn->index.keyPattern};
+ sb << " " << keyPattern;
+ break;
+ }
+ case STAGE_DISTINCT_SCAN: {
+ auto dn = static_cast<const DistinctNode*>(node);
+ const KeyPattern keyPattern{dn->index.keyPattern};
+ sb << " " << keyPattern;
+ break;
+ }
+ case STAGE_GEO_NEAR_2D: {
+ auto geo2d = static_cast<const GeoNear2DNode*>(node);
+ const KeyPattern keyPattern{geo2d->index.keyPattern};
+ sb << " " << keyPattern;
+ break;
+ }
+ case STAGE_GEO_NEAR_2DSPHERE: {
+ auto geo2dsphere = static_cast<const GeoNear2DSphereNode*>(node);
+ const KeyPattern keyPattern{geo2dsphere->index.keyPattern};
+ sb << " " << keyPattern;
+ break;
+ }
+ case STAGE_IXSCAN: {
+ auto ixn = static_cast<const IndexScanNode*>(node);
+ const KeyPattern keyPattern{ixn->index.keyPattern};
+ sb << " " << keyPattern;
+ break;
+ }
+ case STAGE_TEXT: {
+ auto tn = static_cast<const TextNode*>(node);
+ const KeyPattern keyPattern{tn->indexPrefix};
+ sb << " " << keyPattern;
+ break;
+ }
+ default:
+ break;
+ }
+
+ for (auto&& child : node->children) {
+ queue.push(child);
+ }
+
+ if (!queue.empty()) {
+ sb << ", ";
+ }
+ }
+
+ return sb.str();
}
void PlanExplainerSBE::getSummaryStats(PlanSummaryStats* statsOut) const {
@@ -44,7 +111,7 @@ void PlanExplainerSBE::getSummaryStats(PlanSummaryStats* statsOut) const {
PlanExplainer::PlanStatsDetails PlanExplainerSBE::getWinningPlanStats(
ExplainOptions::Verbosity verbosity) const {
// TODO: SERVER-50728
- return {{}, {}};
+ return {{}, PlanSummaryStats{}};
}
std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getRejectedPlansStats(
diff --git a/src/mongo/db/query/plan_explainer_sbe.h b/src/mongo/db/query/plan_explainer_sbe.h
index 7ce6513c240..f8f7eae2973 100644
--- a/src/mongo/db/query/plan_explainer_sbe.h
+++ b/src/mongo/db/query/plan_explainer_sbe.h
@@ -32,6 +32,7 @@
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/query/plan_explainer.h"
#include "mongo/db/query/query_solution.h"
+#include "mongo/db/query/sbe_plan_ranker.h"
namespace mongo {
/**
@@ -39,10 +40,17 @@ namespace mongo {
*/
class PlanExplainerSBE final : public PlanExplainer {
public:
- PlanExplainerSBE(const sbe::PlanStage* root, const QuerySolution* solution) {}
+ PlanExplainerSBE(const sbe::PlanStage* root,
+ const QuerySolution* solution,
+ std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
+ bool isMultiPlan)
+ : _root{root},
+ _solution{solution},
+ _rejectedCandidates{std::move(rejectedCandidates)},
+ _isMultiPlan{isMultiPlan} {}
bool isMultiPlan() const final {
- return false;
+ return _isMultiPlan;
}
std::string getPlanSummary() const final;
@@ -52,5 +60,11 @@ public:
ExplainOptions::Verbosity verbosity) const final;
std::vector<PlanStatsDetails> getCachedPlanStats(const PlanCacheEntry::DebugInfo&,
ExplainOptions::Verbosity) const final;
+
+private:
+ const sbe::PlanStage* _root{nullptr};
+ const QuerySolution* _solution{nullptr};
+ const std::vector<sbe::plan_ranker::CandidatePlan> _rejectedCandidates;
+ const bool _isMultiPlan{false};
};
} // namespace mongo
diff --git a/src/mongo/db/query/plan_ranker.h b/src/mongo/db/query/plan_ranker.h
index 5432bb598c5..1e878c40b59 100644
--- a/src/mongo/db/query/plan_ranker.h
+++ b/src/mongo/db/query/plan_ranker.h
@@ -35,7 +35,6 @@
#include "mongo/db/exec/sbe/stages/plan_stats.h"
#include "mongo/db/exec/working_set.h"
#include "mongo/db/query/explain.h"
-#include "mongo/db/query/plan_explainer_factory.h"
#include "mongo/db/query/plan_ranking_decision.h"
#include "mongo/db/query/query_solution.h"
#include "mongo/util/container_size_helper.h"
@@ -194,153 +193,4 @@ struct BaseCandidatePlan {
};
using CandidatePlan = BaseCandidatePlan<PlanStage*, WorkingSetID, WorkingSet*>;
-
-/**
- * A factory function to create a plan scorer for a plan stage stats tree.
- */
-std::unique_ptr<PlanScorer<PlanStageStats>> makePlanScorer();
-} // namespace mongo::plan_ranker
-
-// Forward declaration.
-namespace mongo::sbe::plan_ranker {
-std::unique_ptr<mongo::plan_ranker::PlanScorer<PlanStageStats>> makePlanScorer(
- const QuerySolution* solution);
-} // namespace mongo::sbe::plan_ranker
-
-namespace mongo::plan_ranker {
-/**
- * Returns a PlanRankingDecision which has the ranking and the information about the ranking
- * process with status OK if everything worked. 'candidateOrder' within the PlanRankingDecision
- * holds indices into candidates ordered by score (winner in first element).
- *
- * Returns an error if there was an issue with plan ranking (e.g. there was no viable plan).
- */
-template <typename PlanStageStatsType, typename PlanStageType, typename ResultType, typename Data>
-StatusWith<std::unique_ptr<PlanRankingDecision>> pickBestPlan(
- const std::vector<BaseCandidatePlan<PlanStageType, ResultType, Data>>& candidates) {
- invariant(!candidates.empty());
- // A plan that hits EOF is automatically scored above
- // its peers. If multiple plans hit EOF during the same
- // set of round-robin calls to work(), then all such plans
- // receive the bonus.
- double eofBonus = 1.0;
-
- // Get stat trees from each plan.
- std::vector<std::unique_ptr<PlanStageStatsType>> statTrees;
- for (size_t i = 0; i < candidates.size(); ++i) {
- statTrees.push_back(candidates[i].root->getStats());
- }
-
- // Holds (score, candidateIndex).
- // Used to derive scores and candidate ordering.
- std::vector<std::pair<double, size_t>> scoresAndCandidateIndices;
- std::vector<size_t> failed;
-
- // Compute score for each tree. Record the best.
- for (size_t i = 0; i < statTrees.size(); ++i) {
- if (!candidates[i].failed) {
- log_detail::logScoringPlan(
- [& candidate = candidates[i]]() { return candidate.solution->toString(); },
- [root = &*candidates[i].root, solution = candidates[i].solution.get()]() {
- auto explainer = plan_explainer_factory::makePlanExplainer(root, solution);
- auto&& [stats, _] =
- explainer->getWinningPlanStats(ExplainOptions::Verbosity::kExecStats);
- return stats.jsonString(ExtendedRelaxedV2_0_0, true);
- },
- [root = &*candidates[i].root, solution = candidates[i].solution.get()]() {
- auto explainer = plan_explainer_factory::makePlanExplainer(root, solution);
- return explainer->getPlanSummary();
- },
- i,
- statTrees[i]->common.isEOF);
- auto scorer = [solution = candidates[i].solution.get()]()
- -> std::unique_ptr<PlanScorer<PlanStageStatsType>> {
- if constexpr (std::is_same_v<PlanStageStatsType, PlanStageStats>) {
- return makePlanScorer();
- } else {
- static_assert(std::is_same_v<PlanStageStatsType, mongo::sbe::PlanStageStats>);
- return sbe::plan_ranker::makePlanScorer(solution);
- }
- }();
- double score = scorer->calculateScore(statTrees[i].get());
- log_detail::logScore(score);
- if (statTrees[i]->common.isEOF) {
- log_detail::logEOFBonus(eofBonus);
- score += 1;
- }
-
- scoresAndCandidateIndices.push_back(std::make_pair(score, i));
- } else {
- failed.push_back(i);
- log_detail::logFailedPlan(
- [root = &*candidates[i].root, solution = candidates[i].solution.get()] {
- auto explainer = plan_explainer_factory::makePlanExplainer(root, solution);
- return explainer->getPlanSummary();
- });
- }
- }
-
- // If there isn't a viable plan we should error.
- if (scoresAndCandidateIndices.size() == 0U) {
- return {ErrorCodes::Error(31157),
- "No viable plan was found because all candidate plans failed."};
- }
-
- // Sort (scores, candidateIndex). Get best child and populate candidate ordering.
- std::stable_sort(scoresAndCandidateIndices.begin(),
- scoresAndCandidateIndices.end(),
- [](const auto& lhs, const auto& rhs) {
- // Just compare score in lhs.first and rhs.first;
- // Ignore candidate array index in lhs.second and rhs.second.
- return lhs.first > rhs.first;
- });
-
- auto why = std::make_unique<PlanRankingDecision>();
- why->stats = std::vector<std::unique_ptr<PlanStageStatsType>>{};
-
- // Determine whether plans tied for the win.
- if (scoresAndCandidateIndices.size() > 1U) {
- double bestScore = scoresAndCandidateIndices[0].first;
- double runnerUpScore = scoresAndCandidateIndices[1].first;
- const double epsilon = 1e-10;
- why->tieForBest = std::abs(bestScore - runnerUpScore) < epsilon;
- }
-
- // Update results in 'why'
- // Stats and scores in 'why' are sorted in descending order by score.
- why->failedCandidates = std::move(failed);
- for (size_t i = 0; i < scoresAndCandidateIndices.size(); ++i) {
- double score = scoresAndCandidateIndices[i].first;
- size_t candidateIndex = scoresAndCandidateIndices[i].second;
-
- // We shouldn't cache the scores with the EOF bonus included, as this is just a
- // tie-breaking measure for plan selection. Plans not run through the multi plan runner
- // will not receive the bonus.
- //
- // An example of a bad thing that could happen if we stored scores with the EOF bonus
- // included:
- //
- // Let's say Plan A hits EOF, is the highest ranking plan, and gets cached as such. On
- // subsequent runs it will not receive the bonus. Eventually the plan cache feedback
- // mechanism will evict the cache entry - the scores will appear to have fallen due to
- // the missing EOF bonus.
- //
- // This raises the question, why don't we include the EOF bonus in scoring of cached plans
- // as well? The problem here is that the cached plan runner always runs plans to completion
- // before scoring. Queries that don't get the bonus in the multi plan runner might get the
- // bonus after being run from the plan cache.
- if (statTrees[candidateIndex]->common.isEOF) {
- score -= eofBonus;
- }
-
- why->getStats<PlanStageStatsType>().push_back(std::move(statTrees[candidateIndex]));
- why->scores.push_back(score);
- why->candidateOrder.push_back(candidateIndex);
- }
- for (auto& i : why->failedCandidates) {
- why->getStats<PlanStageStatsType>().push_back(std::move(statTrees[i]));
- }
-
- return StatusWith<std::unique_ptr<PlanRankingDecision>>(std::move(why));
-}
} // namespace mongo::plan_ranker
diff --git a/src/mongo/db/query/plan_ranker_test.cpp b/src/mongo/db/query/plan_ranker_test.cpp
index 229b7e4fae3..20034580e79 100644
--- a/src/mongo/db/query/plan_ranker_test.cpp
+++ b/src/mongo/db/query/plan_ranker_test.cpp
@@ -32,7 +32,7 @@
*/
#include "mongo/db/query/plan_ranker.h"
-
+#include "mongo/db/query/plan_ranker_util.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/assert_util.h"
diff --git a/src/mongo/db/query/plan_ranker_util.h b/src/mongo/db/query/plan_ranker_util.h
new file mode 100644
index 00000000000..648d9c70c66
--- /dev/null
+++ b/src/mongo/db/query/plan_ranker_util.h
@@ -0,0 +1,184 @@
+/**
+ * Copyright (C) 2020-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/query/plan_explainer_factory.h"
+#include "mongo/db/query/plan_ranker.h"
+
+// Forward declarations.
+namespace mongo::sbe::plan_ranker {
+std::unique_ptr<mongo::plan_ranker::PlanScorer<PlanStageStats>> makePlanScorer(
+ const QuerySolution* solution);
+} // namespace mongo::sbe::plan_ranker
+
+namespace mongo::plan_ranker {
+/**
+ * A factory function to create a plan scorer for a plan stage stats tree.
+ */
+std::unique_ptr<PlanScorer<PlanStageStats>> makePlanScorer();
+
+/**
+ * Returns a PlanRankingDecision which has the ranking and the information about the ranking
+ * process with status OK if everything worked. 'candidateOrder' within the PlanRankingDecision
+ * holds indices into candidates ordered by score (winner in first element).
+ *
+ * Returns an error if there was an issue with plan ranking (e.g. there was no viable plan).
+ */
+template <typename PlanStageStatsType, typename PlanStageType, typename ResultType, typename Data>
+StatusWith<std::unique_ptr<PlanRankingDecision>> pickBestPlan(
+ const std::vector<BaseCandidatePlan<PlanStageType, ResultType, Data>>& candidates) {
+ invariant(!candidates.empty());
+ // A plan that hits EOF is automatically scored above
+ // its peers. If multiple plans hit EOF during the same
+ // set of round-robin calls to work(), then all such plans
+ // receive the bonus.
+ double eofBonus = 1.0;
+
+ // Get stat trees from each plan.
+ std::vector<std::unique_ptr<PlanStageStatsType>> statTrees;
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ statTrees.push_back(candidates[i].root->getStats());
+ }
+
+ // Holds (score, candidateIndex).
+ // Used to derive scores and candidate ordering.
+ std::vector<std::pair<double, size_t>> scoresAndCandidateIndices;
+ std::vector<size_t> failed;
+
+ // Compute score for each tree. Record the best.
+ for (size_t i = 0; i < statTrees.size(); ++i) {
+ auto explainer = [&]() {
+ if constexpr (std::is_same_v<PlanStageStatsType, PlanStageStats>) {
+ return plan_explainer_factory::make(candidates[i].root);
+ } else {
+ static_assert(std::is_same_v<PlanStageStatsType, mongo::sbe::PlanStageStats>);
+ return plan_explainer_factory::make(candidates[i].root.get(),
+ candidates[i].solution.get());
+ }
+ }();
+
+ if (!candidates[i].failed) {
+
+ log_detail::logScoringPlan([&]() { return candidates[i].solution->toString(); },
+ [&]() {
+ auto&& [stats, _] = explainer->getWinningPlanStats(
+ ExplainOptions::Verbosity::kExecStats);
+ return stats.jsonString(ExtendedRelaxedV2_0_0, true);
+ },
+ [&]() { return explainer->getPlanSummary(); },
+ i,
+ statTrees[i]->common.isEOF);
+ auto scorer = [solution = candidates[i].solution.get()]()
+ -> std::unique_ptr<PlanScorer<PlanStageStatsType>> {
+ if constexpr (std::is_same_v<PlanStageStatsType, PlanStageStats>) {
+ return makePlanScorer();
+ } else {
+ static_assert(std::is_same_v<PlanStageStatsType, mongo::sbe::PlanStageStats>);
+ return sbe::plan_ranker::makePlanScorer(solution);
+ }
+ }();
+ double score = scorer->calculateScore(statTrees[i].get());
+ log_detail::logScore(score);
+ if (statTrees[i]->common.isEOF) {
+ log_detail::logEOFBonus(eofBonus);
+ score += 1;
+ }
+
+ scoresAndCandidateIndices.push_back(std::make_pair(score, i));
+ } else {
+ failed.push_back(i);
+ log_detail::logFailedPlan([&] { return explainer->getPlanSummary(); });
+ }
+ }
+
+ // If there isn't a viable plan we should error.
+ if (scoresAndCandidateIndices.size() == 0U) {
+ return {ErrorCodes::Error(31157),
+ "No viable plan was found because all candidate plans failed."};
+ }
+
+ // Sort (scores, candidateIndex). Get best child and populate candidate ordering.
+ std::stable_sort(scoresAndCandidateIndices.begin(),
+ scoresAndCandidateIndices.end(),
+ [](const auto& lhs, const auto& rhs) {
+ // Just compare score in lhs.first and rhs.first;
+ // Ignore candidate array index in lhs.second and rhs.second.
+ return lhs.first > rhs.first;
+ });
+
+ auto why = std::make_unique<PlanRankingDecision>();
+ why->stats = std::vector<std::unique_ptr<PlanStageStatsType>>{};
+
+ // Determine whether plans tied for the win.
+ if (scoresAndCandidateIndices.size() > 1U) {
+ double bestScore = scoresAndCandidateIndices[0].first;
+ double runnerUpScore = scoresAndCandidateIndices[1].first;
+ const double epsilon = 1e-10;
+ why->tieForBest = std::abs(bestScore - runnerUpScore) < epsilon;
+ }
+
+ // Update results in 'why'
+ // Stats and scores in 'why' are sorted in descending order by score.
+ why->failedCandidates = std::move(failed);
+ for (size_t i = 0; i < scoresAndCandidateIndices.size(); ++i) {
+ double score = scoresAndCandidateIndices[i].first;
+ size_t candidateIndex = scoresAndCandidateIndices[i].second;
+
+ // We shouldn't cache the scores with the EOF bonus included, as this is just a
+ // tie-breaking measure for plan selection. Plans not run through the multi plan runner
+ // will not receive the bonus.
+ //
+ // An example of a bad thing that could happen if we stored scores with the EOF bonus
+ // included:
+ //
+ // Let's say Plan A hits EOF, is the highest ranking plan, and gets cached as such. On
+ // subsequent runs it will not receive the bonus. Eventually the plan cache feedback
+ // mechanism will evict the cache entry - the scores will appear to have fallen due to
+ // the missing EOF bonus.
+ //
+ // This raises the question, why don't we include the EOF bonus in scoring of cached plans
+ // as well? The problem here is that the cached plan runner always runs plans to completion
+ // before scoring. Queries that don't get the bonus in the multi plan runner might get the
+ // bonus after being run from the plan cache.
+ if (statTrees[candidateIndex]->common.isEOF) {
+ score -= eofBonus;
+ }
+
+ why->getStats<PlanStageStatsType>().push_back(std::move(statTrees[candidateIndex]));
+ why->scores.push_back(score);
+ why->candidateOrder.push_back(candidateIndex);
+ }
+ for (auto& i : why->failedCandidates) {
+ why->getStats<PlanStageStatsType>().push_back(std::move(statTrees[i]));
+ }
+
+ return StatusWith<std::unique_ptr<PlanRankingDecision>>(std::move(why));
+}
+} // namespace mongo::plan_ranker
diff --git a/src/mongo/db/query/sbe_cached_solution_planner.cpp b/src/mongo/db/query/sbe_cached_solution_planner.cpp
index e8f4c263c1b..bfe2ebaeb77 100644
--- a/src/mongo/db/query/sbe_cached_solution_planner.cpp
+++ b/src/mongo/db/query/sbe_cached_solution_planner.cpp
@@ -37,10 +37,11 @@
#include "mongo/db/query/query_planner.h"
#include "mongo/db/query/sbe_multi_planner.h"
#include "mongo/db/query/stage_builder_util.h"
+#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/logv2/log.h"
namespace mongo::sbe {
-plan_ranker::CandidatePlan CachedSolutionPlanner::plan(
+CandidatePlans CachedSolutionPlanner::plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots) {
invariant(solutions.size() == 1);
@@ -51,12 +52,11 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::plan(
invariant(candidates.size() == 1);
return std::move(candidates[0]);
}();
+ auto explainer = plan_explainer_factory::make(candidate.root.get(), candidate.solution.get());
if (candidate.failed) {
// On failure, fall back to replanning the whole query. We neither evict the existing cache
// entry, nor cache the result of replanning.
- auto explainer = plan_explainer_factory::makePlanExplainer(candidate.root.get(),
- candidate.solution.get());
LOGV2_DEBUG(2057901,
1,
"Execution of cached plan failed, falling back to replan",
@@ -70,13 +70,13 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::plan(
// If the cached plan hit EOF quickly enough, or still as efficient as before, then no need to
// replan. Finalize the cached plan and return it.
if (stats->common.isEOF || numReads <= _decisionReads) {
- return finalizeExecutionPlan(std::move(stats), std::move(candidate));
+ return {makeVector<plan_ranker::CandidatePlan>(
+ finalizeExecutionPlan(std::move(stats), std::move(candidate))),
+ 0};
}
// If we're here, the trial period took more than 'maxReadsBeforeReplan' physical reads. This
// plan may not be efficient any longer, so we replan from scratch.
- auto explainer =
- plan_explainer_factory::makePlanExplainer(candidate.root.get(), candidate.solution.get());
LOGV2_DEBUG(
2058001,
1,
@@ -104,7 +104,7 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::finalizeExecutionPlan(
return candidate;
}
-plan_ranker::CandidatePlan CachedSolutionPlanner::replan(bool shouldCache) const {
+CandidatePlans CachedSolutionPlanner::replan(bool shouldCache) const {
// The plan drawn from the cache is being discarded, and should no longer be registered with the
// yield policy.
_yieldPolicy->clearRegisteredPlans();
@@ -123,7 +123,7 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::replan(bool shouldCache) const
_opCtx, _collection, _cq, *solutions[0], _yieldPolicy, true);
prepareExecutionPlan(root.get(), &data);
- auto explainer = plan_explainer_factory::makePlanExplainer(root.get(), solutions[0].get());
+ auto explainer = plan_explainer_factory::make(root.get(), solutions[0].get());
LOGV2_DEBUG(
2058101,
1,
@@ -131,7 +131,9 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::replan(bool shouldCache) const
"query"_attr = redact(_cq.toStringShort()),
"planSummary"_attr = explainer->getPlanSummary(),
"shouldCache"_attr = (shouldCache ? "yes" : "no"));
- return {std::move(solutions[0]), std::move(root), std::move(data)};
+ return {makeVector<plan_ranker::CandidatePlan>(plan_ranker::CandidatePlan{
+ std::move(solutions[0]), std::move(root), std::move(data)}),
+ 0};
}
// Many solutions. Build a plan stage tree for each solution and create a multi planner to pick
@@ -149,15 +151,15 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::replan(bool shouldCache) const
const auto cachingMode =
shouldCache ? PlanCachingMode::AlwaysCache : PlanCachingMode::NeverCache;
MultiPlanner multiPlanner{_opCtx, _collection, _cq, cachingMode, _yieldPolicy};
- auto plan = multiPlanner.plan(std::move(solutions), std::move(roots));
- auto explainer =
- plan_explainer_factory::makePlanExplainer(plan.root.get(), plan.solution.get());
+ auto&& [candidates, winnerIdx] = multiPlanner.plan(std::move(solutions), std::move(roots));
+ auto explainer = plan_explainer_factory::make(candidates[winnerIdx].root.get(),
+ candidates[winnerIdx].solution.get());
LOGV2_DEBUG(2058201,
1,
"Query plan after replanning and its cache status",
"query"_attr = redact(_cq.toStringShort()),
"planSummary"_attr = explainer->getPlanSummary(),
"shouldCache"_attr = (shouldCache ? "yes" : "no"));
- return plan;
+ return {std::move(candidates), winnerIdx};
}
} // namespace mongo::sbe
diff --git a/src/mongo/db/query/sbe_cached_solution_planner.h b/src/mongo/db/query/sbe_cached_solution_planner.h
index 9474cca0073..14d0efd1419 100644
--- a/src/mongo/db/query/sbe_cached_solution_planner.h
+++ b/src/mongo/db/query/sbe_cached_solution_planner.h
@@ -52,7 +52,7 @@ public:
_queryParams{queryParams},
_decisionReads{decisionReads} {}
- plan_ranker::CandidatePlan plan(
+ CandidatePlans plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots)
final;
@@ -73,7 +73,7 @@ private:
*
* The plan cache is modified only if 'shouldCache' is true.
*/
- plan_ranker::CandidatePlan replan(bool shouldCache) const;
+ CandidatePlans replan(bool shouldCache) const;
// Query parameters used to create a query solution when the plan was first created. Used during
// replanning.
diff --git a/src/mongo/db/query/sbe_multi_planner.cpp b/src/mongo/db/query/sbe_multi_planner.cpp
index 90f2ab8370c..ce070cc8e8f 100644
--- a/src/mongo/db/query/sbe_multi_planner.cpp
+++ b/src/mongo/db/query/sbe_multi_planner.cpp
@@ -37,12 +37,13 @@
#include "mongo/db/exec/sbe/values/bson.h"
#include "mongo/db/query/collection_query_info.h"
#include "mongo/db/query/explain.h"
+#include "mongo/db/query/plan_ranker_util.h"
#include "mongo/db/query/query_planner.h"
#include "mongo/db/query/stage_builder_util.h"
#include "mongo/logv2/log.h"
namespace mongo::sbe {
-plan_ranker::CandidatePlan MultiPlanner::plan(
+CandidatePlans MultiPlanner::plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots) {
auto candidates = collectExecutionStats(std::move(solutions), std::move(roots));
@@ -50,7 +51,7 @@ plan_ranker::CandidatePlan MultiPlanner::plan(
return finalizeExecutionPlans(std::move(decision), std::move(candidates));
}
-plan_ranker::CandidatePlan MultiPlanner::finalizeExecutionPlans(
+CandidatePlans MultiPlanner::finalizeExecutionPlans(
std::unique_ptr<mongo::plan_ranker::PlanRankingDecision> decision,
std::vector<plan_ranker::CandidatePlan> candidates) const {
invariant(decision);
@@ -64,8 +65,7 @@ plan_ranker::CandidatePlan MultiPlanner::finalizeExecutionPlans(
LOGV2_DEBUG(
4822875, 5, "Winning solution", "bestSolution"_attr = redact(winner.solution->toString()));
- auto explainer =
- plan_explainer_factory::makePlanExplainer(winner.root.get(), winner.solution.get());
+ auto explainer = plan_explainer_factory::make(winner.root.get(), winner.solution.get());
LOGV2_DEBUG(4822876, 2, "Winning plan", "planSummary"_attr = explainer->getPlanSummary());
// Close all candidate plans but the winner.
@@ -90,6 +90,6 @@ plan_ranker::CandidatePlan MultiPlanner::finalizeExecutionPlans(
plan_cache_util::updatePlanCache(
_opCtx, _collection, _cachingMode, _cq, std::move(decision), candidates);
- return std::move(winner);
+ return {std::move(candidates), winnerIdx};
}
} // namespace mongo::sbe
diff --git a/src/mongo/db/query/sbe_multi_planner.h b/src/mongo/db/query/sbe_multi_planner.h
index 80ac325c3b3..a9aa22bb462 100644
--- a/src/mongo/db/query/sbe_multi_planner.h
+++ b/src/mongo/db/query/sbe_multi_planner.h
@@ -49,7 +49,7 @@ public:
PlanYieldPolicySBE* yieldPolicy)
: BaseRuntimePlanner{opCtx, collection, cq, yieldPolicy}, _cachingMode{cachingMode} {}
- plan_ranker::CandidatePlan plan(
+ CandidatePlans plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots)
final;
@@ -61,7 +61,7 @@ private:
* Calls 'close' method on all other candidate plans and updates the plan cache entry,
* if possible.
*/
- plan_ranker::CandidatePlan finalizeExecutionPlans(
+ CandidatePlans finalizeExecutionPlans(
std::unique_ptr<mongo::plan_ranker::PlanRankingDecision> decision,
std::vector<plan_ranker::CandidatePlan> candidates) const;
diff --git a/src/mongo/db/query/sbe_runtime_planner.h b/src/mongo/db/query/sbe_runtime_planner.h
index c7dbfbd73d2..5b653ee5c91 100644
--- a/src/mongo/db/query/sbe_runtime_planner.h
+++ b/src/mongo/db/query/sbe_runtime_planner.h
@@ -29,6 +29,7 @@
#pragma once
+#include "mongo/db/catalog/collection.h"
#include "mongo/db/exec/sbe/stages/stages.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/plan_yield_policy_sbe.h"
@@ -37,6 +38,20 @@
namespace mongo::sbe {
/**
+ * This struct holds a vector with all candidate plans evaluated by this RuntimePlanner, and an
+ * index pointing to the winning plan within this vector.
+ */
+struct CandidatePlans {
+ std::vector<plan_ranker::CandidatePlan> plans;
+ size_t winnerIdx;
+
+ auto& winner() {
+ invariant(winnerIdx < plans.size());
+ return plans[winnerIdx];
+ }
+};
+
+/**
* An interface to be implemented by all classes which can evaluate the cost of a PlanStage tree in
* order to pick the the best plan amongst those specified in 'roots' vector. Evaluation is done in
* runtime by collecting execution stats for each of the plans, and the best candidate plan is
@@ -46,7 +61,7 @@ class RuntimePlanner {
public:
virtual ~RuntimePlanner() = default;
- virtual plan_ranker::CandidatePlan plan(
+ virtual CandidatePlans plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots) = 0;
};
@@ -65,7 +80,6 @@ public:
PlanYieldPolicySBE* yieldPolicy)
: _opCtx(opCtx), _collection(collection), _cq(cq), _yieldPolicy(yieldPolicy) {
invariant(_opCtx);
- invariant(_collection);
}
protected:
diff --git a/src/mongo/db/query/sbe_sub_planner.cpp b/src/mongo/db/query/sbe_sub_planner.cpp
index 05dadabbf5c..1f136b7966c 100644
--- a/src/mongo/db/query/sbe_sub_planner.cpp
+++ b/src/mongo/db/query/sbe_sub_planner.cpp
@@ -34,9 +34,10 @@
#include "mongo/db/query/query_planner.h"
#include "mongo/db/query/sbe_multi_planner.h"
#include "mongo/db/query/stage_builder_util.h"
+#include "mongo/db/query/util/make_data_structure.h"
namespace mongo::sbe {
-plan_ranker::CandidatePlan SubPlanner::plan(
+CandidatePlans SubPlanner::plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots) {
// Plan each branch of the $or.
@@ -69,8 +70,9 @@ plan_ranker::CandidatePlan SubPlanner::plan(
// conservative about putting a potentially bad plan into the cache in the subplan path.
MultiPlanner multiPlanner{
_opCtx, _collection, *cq, PlanCachingMode::SometimesCache, _yieldPolicy};
- auto plan = multiPlanner.plan(std::move(solutions), std::move(roots));
- return std::move(plan.solution);
+ auto&& [candidates, winnerIdx] = multiPlanner.plan(std::move(solutions), std::move(roots));
+ invariant(winnerIdx < candidates.size());
+ return std::move(candidates[winnerIdx].solution);
};
auto subplanSelectStat = QueryPlanner::choosePlanForSubqueries(
@@ -90,10 +92,12 @@ plan_ranker::CandidatePlan SubPlanner::plan(
auto&& [root, data] = stage_builder::buildSlotBasedExecutableTree(
_opCtx, _collection, _cq, *compositeSolution, _yieldPolicy, false);
prepareExecutionPlan(root.get(), &data);
- return {std::move(compositeSolution), std::move(root), std::move(data)};
+ return {makeVector<plan_ranker::CandidatePlan>(plan_ranker::CandidatePlan{
+ std::move(compositeSolution), std::move(root), std::move(data)}),
+ 0};
}
-plan_ranker::CandidatePlan SubPlanner::planWholeQuery() const {
+CandidatePlans SubPlanner::planWholeQuery() const {
// Use the query planning module to plan the whole query.
auto solutions = uassertStatusOK(QueryPlanner::plan(_cq, _queryParams));
@@ -102,7 +106,9 @@ plan_ranker::CandidatePlan SubPlanner::planWholeQuery() const {
auto&& [root, data] = stage_builder::buildSlotBasedExecutableTree(
_opCtx, _collection, _cq, *solutions[0], _yieldPolicy, false);
prepareExecutionPlan(root.get(), &data);
- return {std::move(solutions[0]), std::move(root), std::move(data)};
+ return {makeVector<plan_ranker::CandidatePlan>(plan_ranker::CandidatePlan{
+ std::move(solutions[0]), std::move(root), std::move(data)}),
+ 0};
}
// Many solutions. Build a plan stage tree for each solution and create a multi planner to pick
diff --git a/src/mongo/db/query/sbe_sub_planner.h b/src/mongo/db/query/sbe_sub_planner.h
index 5f708a2cd4d..06c0b468dd1 100644
--- a/src/mongo/db/query/sbe_sub_planner.h
+++ b/src/mongo/db/query/sbe_sub_planner.h
@@ -49,13 +49,13 @@ public:
PlanYieldPolicySBE* yieldPolicy)
: BaseRuntimePlanner{opCtx, collection, cq, yieldPolicy}, _queryParams{queryParams} {}
- plan_ranker::CandidatePlan plan(
+ CandidatePlans plan(
std::vector<std::unique_ptr<QuerySolution>> solutions,
std::vector<std::pair<std::unique_ptr<PlanStage>, stage_builder::PlanStageData>> roots)
final;
private:
- plan_ranker::CandidatePlan planWholeQuery() const;
+ CandidatePlans planWholeQuery() const;
// Query parameters used to create a query solution for each $or branch.
const QueryPlannerParams _queryParams;
diff --git a/src/mongo/db/query/stage_types.cpp b/src/mongo/db/query/stage_types.cpp
new file mode 100644
index 00000000000..2f37ba89e0f
--- /dev/null
+++ b/src/mongo/db/query/stage_types.cpp
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2020-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/query/stage_types.h"
+#include "mongo/stdx/unordered_map.h"
+
+namespace mongo {
+StringData stageTypeToString(StageType stageType) {
+ static const stdx::unordered_map<StageType, StringData> kStageTypesMap = {
+ {STAGE_AND_HASH, "AND_HASH"_sd},
+ {STAGE_AND_SORTED, "AND_SORTED"_sd},
+ {STAGE_CACHED_PLAN, "CACHED_PLAN"},
+ {STAGE_COLLSCAN, "COLLSCAN"_sd},
+ {STAGE_COUNT, "COUNT"_sd},
+ {STAGE_COUNT_SCAN, "COUNT_SCAN"_sd},
+ {STAGE_DELETE, "DELETE"_sd},
+ {STAGE_DISTINCT_SCAN, "DISTINCT_SCAN"_sd},
+ {STAGE_ENSURE_SORTED, "SORTED"_sd},
+ {STAGE_EOF, "EOF"_sd},
+ {STAGE_FETCH, "FETCH"_sd},
+ {STAGE_GEO_NEAR_2D, "GEO_NEAR_2D"_sd},
+ {STAGE_GEO_NEAR_2DSPHERE, "GEO_NEAR_2DSPHERE"_sd},
+ {STAGE_IDHACK, "IDHACK"_sd},
+ {STAGE_IXSCAN, "IXSCAN"_sd},
+ {STAGE_LIMIT, "LIMIT"_sd},
+ {STAGE_MOCK, "MOCK"_sd},
+ {STAGE_MULTI_ITERATOR, "MULTI_ITERATOR"_sd},
+ {STAGE_MULTI_PLAN, "MULTI_PLAN"_sd},
+ {STAGE_OR, "OR"_sd},
+ {STAGE_PROJECTION_DEFAULT, "PROJECTION_DEFAULT"_sd},
+ {STAGE_PROJECTION_COVERED, "PROJECTION_COVERED"_sd},
+ {STAGE_PROJECTION_SIMPLE, "PROJECTION_SIMPLE"_sd},
+ {STAGE_QUEUED_DATA, "QUEUED_DATA"_sd},
+ {STAGE_RECORD_STORE_FAST_COUNT, "RECORD_STORE_FAST_COUNT"_sd},
+ {STAGE_RETURN_KEY, "RETURN_KEY"_sd},
+ {STAGE_SHARDING_FILTER, "SHARDING_FILTER"_sd},
+ {STAGE_SKIP, "SKIP"_sd},
+ {STAGE_SORT_DEFAULT, "SORT_DEFAULT"_sd},
+ {STAGE_SORT_SIMPLE, "SORT_SIMPLE"_sd},
+ {STAGE_SORT_KEY_GENERATOR, "SORT_KEY_GENERATOR"_sd},
+ {STAGE_SORT_MERGE, "SORT_MERGE"_sd},
+ {STAGE_SUBPLAN, "SUBPLAN"_sd},
+ {STAGE_TEXT, "TEXT"_sd},
+ {STAGE_TEXT_OR, "TEXT_OR"_sd},
+ {STAGE_TEXT_MATCH, "TEXT_MATCH"_sd},
+ {STAGE_TRIAL, "TRIAL"_sd},
+ {STAGE_UNKNOWN, "UNKNOWN"_sd},
+ {STAGE_UPDATE, "UPDATE"_sd},
+ };
+ if (auto it = kStageTypesMap.find(stageType); it != kStageTypesMap.end()) {
+ return it->second;
+ }
+ return kStageTypesMap.at(STAGE_UNKNOWN);
+}
+} // namespace mongo
diff --git a/src/mongo/db/query/stage_types.h b/src/mongo/db/query/stage_types.h
index 1c23d7ed196..6b16e4575d0 100644
--- a/src/mongo/db/query/stage_types.h
+++ b/src/mongo/db/query/stage_types.h
@@ -31,6 +31,8 @@
#include <cstdint>
+#include "mongo/base/string_data.h"
+
namespace mongo {
/**
* This type acts as an identifier for a node in a query plan tree, such as a 'QuerySolution' tree
@@ -140,4 +142,5 @@ inline bool isSortStageType(StageType stageType) {
}
}
+StringData stageTypeToString(StageType stageType);
} // namespace mongo