diff options
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/exec/multi_plan.cpp | 57 | ||||
-rw-r--r-- | src/mongo/db/exec/multi_plan.h | 17 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_multi_planner.cpp | 1 | ||||
-rw-r--r-- | src/mongo/dbtests/query_stage_multiplan.cpp | 23 |
4 files changed, 86 insertions, 12 deletions
diff --git a/src/mongo/db/exec/multi_plan.cpp b/src/mongo/db/exec/multi_plan.cpp index def29719b63..8c816ebdcbd 100644 --- a/src/mongo/db/exec/multi_plan.cpp +++ b/src/mongo/db/exec/multi_plan.cpp @@ -190,14 +190,13 @@ PlanStage::StageState MultiPlanStage::doWork(WorkingSetID* out) { .getPlanCache() ->remove(plan_cache_key_factory::make<PlanCacheKey>(*_query, collection())); - _bestPlanIdx = _backupPlanIdx; - _backupPlanIdx = kNoSuchPlan; + switchToBackupPlan(); return _candidates[_bestPlanIdx].root->work(out); } if (hasBackupPlan() && PlanStage::ADVANCED == state) { LOGV2_DEBUG(20589, 5, "Best plan had a blocking stage, became unblocked"); - _backupPlanIdx = kNoSuchPlan; + removeBackupPlan(); } return state; @@ -294,6 +293,7 @@ Status MultiPlanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) { plan_cache_util::updatePlanCache( expCtx()->opCtx, collection(), _cachingMode, *_query, std::move(ranking), _candidates); + removeRejectedPlans(); return Status::OK(); } @@ -364,6 +364,54 @@ bool MultiPlanStage::workAllPlans(size_t numResults, PlanYieldPolicy* yieldPolic return !doneWorking; } +void MultiPlanStage::removeRejectedPlans() { + // Move the best plan and the backup plan to the front of 'children'. + if (_bestPlanIdx != 0) { + std::swap(_children[_bestPlanIdx], _children[0]); + std::swap(_candidates[_bestPlanIdx], _candidates[0]); + if (_backupPlanIdx == 0) { + _backupPlanIdx = _bestPlanIdx; + } + _bestPlanIdx = 0; + } + size_t startIndex = 1; + if (_backupPlanIdx != kNoSuchPlan) { + if (_bestPlanIdx != 1) { + std::swap(_children[_backupPlanIdx], _children[1]); + std::swap(_candidates[_backupPlanIdx], _candidates[1]); + _backupPlanIdx = 1; + } + startIndex = 2; + } + + _rejected.reserve(_children.size() - startIndex); + for (size_t i = startIndex; i < _children.size(); ++i) { + rejectPlan(i); + } + _children.resize(startIndex); +} + +void MultiPlanStage::switchToBackupPlan() { + std::swap(_children[_backupPlanIdx], _children[_bestPlanIdx]); + std::swap(_candidates[_backupPlanIdx], _candidates[_bestPlanIdx]); + removeBackupPlan(); +} + +void MultiPlanStage::rejectPlan(size_t planIdx) { + auto rejectedPlan = std::move(_children[planIdx]); + if (opCtx() != nullptr) { + rejectedPlan->saveState(); + rejectedPlan->detachFromOperationContext(); + } + _rejected.emplace_back(std::move(rejectedPlan)); +} + +void MultiPlanStage::removeBackupPlan() { + rejectPlan(_backupPlanIdx); + _children.resize(1); + _backupPlanIdx = kNoSuchPlan; +} + bool MultiPlanStage::hasBackupPlan() const { return kNoSuchPlan != _backupPlanIdx; } @@ -398,6 +446,9 @@ unique_ptr<PlanStageStats> MultiPlanStage::getStats() { for (auto&& child : _children) { ret->children.emplace_back(child->getStats()); } + for (auto&& child : _rejected) { + ret->children.emplace_back(child->getStats()); + } return ret; } diff --git a/src/mongo/db/exec/multi_plan.h b/src/mongo/db/exec/multi_plan.h index 14d8ea413af..12ab61659db 100644 --- a/src/mongo/db/exec/multi_plan.h +++ b/src/mongo/db/exec/multi_plan.h @@ -75,7 +75,6 @@ public: std::unique_ptr<PlanStageStats> getStats() final; - const SpecificStats* getSpecificStats() const final; boost::optional<double> getCandidateScore(size_t candidateIdx) const; @@ -163,6 +162,19 @@ private: */ void tryYield(PlanYieldPolicy* yieldPolicy); + /** + * Deletes all children, except for best and backup plans. + * + * This is necessary to release any resources that rejected plans might have. + * For example, if multi-update can be done by scanning several indexes, + * it will be slowed down by rejected index scans because of index cursors + * that need to be reopeneed after every update. + */ + void removeRejectedPlans(); + void rejectPlan(size_t planIdx); + void switchToBackupPlan(); + void removeBackupPlan(); + static const int kNoSuchPlan = -1; // Describes the cases in which we should write an entry for the winning plan to the plan cache. @@ -178,6 +190,9 @@ private: // one-to-one with _candidates. std::vector<plan_ranker::CandidatePlan> _candidates; + // Rejected plans in saved and detached state. + std::vector<std::unique_ptr<PlanStage>> _rejected; + // index into _candidates, of the winner of the plan competition // uses -1 / kNoSuchPlan when best plan is not (yet) known int _bestPlanIdx; diff --git a/src/mongo/db/query/sbe_multi_planner.cpp b/src/mongo/db/query/sbe_multi_planner.cpp index 5f2104a0668..a0d6e975694 100644 --- a/src/mongo/db/query/sbe_multi_planner.cpp +++ b/src/mongo/db/query/sbe_multi_planner.cpp @@ -32,7 +32,6 @@ #include "mongo/db/query/sbe_multi_planner.h" -#include "mongo/db/exec/multi_plan.h" #include "mongo/db/exec/sbe/expressions/expression.h" #include "mongo/db/exec/sbe/values/bson.h" #include "mongo/db/query/collection_query_info.h" diff --git a/src/mongo/dbtests/query_stage_multiplan.cpp b/src/mongo/dbtests/query_stage_multiplan.cpp index 3552cf095fd..51b13b17aa4 100644 --- a/src/mongo/dbtests/query_stage_multiplan.cpp +++ b/src/mongo/dbtests/query_stage_multiplan.cpp @@ -182,6 +182,11 @@ unique_ptr<PlanStage> getCollScanPlan(ExpressionContext* expCtx, return root; } +const PlanStage* getBestPlanRoot(const MultiPlanStage* mps) { + auto bestPlanIdx = mps->bestPlanIdx(); + return bestPlanIdx ? mps->getChildren()[bestPlanIdx.get()].get() : nullptr; +} + std::unique_ptr<MultiPlanStage> runMultiPlanner(ExpressionContext* expCtx, const NamespaceString& nss, const CollectionPtr& coll, @@ -191,6 +196,7 @@ std::unique_ptr<MultiPlanStage> runMultiPlanner(ExpressionContext* expCtx, // at least). unique_ptr<WorkingSet> sharedWs(new WorkingSet()); unique_ptr<PlanStage> ixScanRoot = getIxScanPlan(expCtx, coll, sharedWs.get(), desiredFooValue); + const auto* ixScanRootPtr = ixScanRoot.get(); // Plan 1: CollScan. BSONObj filterObj = BSON("foo" << desiredFooValue); @@ -209,7 +215,7 @@ std::unique_ptr<MultiPlanStage> runMultiPlanner(ExpressionContext* expCtx, NoopYieldPolicy yieldPolicy(expCtx->opCtx->getServiceContext()->getFastClockSource()); ASSERT_OK(mps->pickBestPlan(&yieldPolicy)); ASSERT(mps->bestPlanChosen()); - ASSERT_EQUALS(0, *mps->bestPlanIdx()); + ASSERT_EQUALS(getBestPlanRoot(mps.get()), ixScanRootPtr); return mps; } @@ -242,6 +248,8 @@ TEST_F(QueryStageMultiPlanTest, MPSCollectionScanVsHighlySelectiveIXScan) { unique_ptr<WorkingSet> sharedWs(new WorkingSet()); unique_ptr<PlanStage> ixScanRoot = getIxScanPlan(_expCtx.get(), coll, sharedWs.get(), 7); + const auto* ixScanRootPtr = ixScanRoot.get(); + // Plan 1: CollScan with matcher. BSONObj filterObj = BSON("foo" << 7); unique_ptr<MatchExpression> filter = makeMatchExpressionFromFilter(_expCtx.get(), filterObj); @@ -256,11 +264,7 @@ TEST_F(QueryStageMultiPlanTest, MPSCollectionScanVsHighlySelectiveIXScan) { mps->addPlan(createQuerySolution(), std::move(ixScanRoot), sharedWs.get()); mps->addPlan(createQuerySolution(), std::move(collScanRoot), sharedWs.get()); - // Plan 0 aka the first plan aka the index scan should be the best. - NoopYieldPolicy yieldPolicy(_clock); - ASSERT_OK(mps->pickBestPlan(&yieldPolicy)); - ASSERT(mps->bestPlanChosen()); - ASSERT_EQUALS(0, *mps->bestPlanIdx()); + const auto* mpsPtr = mps.get(); // Takes ownership of arguments other than 'collection'. auto statusWithPlanExecutor = @@ -273,6 +277,9 @@ TEST_F(QueryStageMultiPlanTest, MPSCollectionScanVsHighlySelectiveIXScan) { ASSERT_OK(statusWithPlanExecutor.getStatus()); auto exec = std::move(statusWithPlanExecutor.getValue()); + ASSERT_TRUE(mpsPtr->bestPlanChosen()); + ASSERT_EQUALS(mpsPtr->getChildren()[mpsPtr->bestPlanIdx().get()].get(), ixScanRootPtr); + // Get all our results out. int results = 0; BSONObj obj; @@ -484,6 +491,8 @@ TEST_F(QueryStageMultiPlanTest, MPSExplainAllPlans) { auto ws = std::make_unique<WorkingSet>(); auto firstPlan = std::make_unique<MockStage>(_expCtx.get(), ws.get()); + const auto* firstPlanPtr = firstPlan.get(); + auto secondPlan = std::make_unique<MockStage>(_expCtx.get(), ws.get()); for (int i = 0; i < nDocs; ++i) { @@ -519,7 +528,7 @@ TEST_F(QueryStageMultiPlanTest, MPSExplainAllPlans) { auto root = static_cast<MultiPlanStage*>(execImpl->getRootStage()); ASSERT_TRUE(root->bestPlanChosen()); // The first candidate plan should have won. - ASSERT_EQ(*root->bestPlanIdx(), 0); + ASSERT_EQ(getBestPlanRoot(root), firstPlanPtr); BSONObjBuilder bob; Explain::explainStages(exec.get(), |