diff options
author | Anton Korshunov <anton.korshunov@mongodb.com> | 2020-09-29 14:06:19 +0100 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-10-23 20:47:32 +0000 |
commit | 46920134f2a6f4de507bbe5abbe3a3faa304bdf7 (patch) | |
tree | 3e963cdc7eedf51e3a4def31a84ffad83fb551c1 /src/mongo/db/query | |
parent | 66fb8deb11d0f1f8c8a7456ac005962d58e5692e (diff) | |
download | mongo-46920134f2a6f4de507bbe5abbe3a3faa304bdf7.tar.gz |
SERVER-50743 Support generation of "planSummary" stats in SBE
Diffstat (limited to 'src/mongo/db/query')
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 |