diff options
Diffstat (limited to 'src/mongo/db/exec')
-rw-r--r-- | src/mongo/db/exec/delete.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/exec/multi_plan.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/exec/multi_plan.h | 19 | ||||
-rw-r--r-- | src/mongo/db/exec/stagedebug_cmd.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/exec/subplan.cpp | 130 | ||||
-rw-r--r-- | src/mongo/db/exec/subplan.h | 48 | ||||
-rw-r--r-- | src/mongo/db/exec/update.cpp | 31 | ||||
-rw-r--r-- | src/mongo/db/exec/update.h | 5 |
8 files changed, 205 insertions, 67 deletions
diff --git a/src/mongo/db/exec/delete.cpp b/src/mongo/db/exec/delete.cpp index ddfeeaaffd0..f4633af95a8 100644 --- a/src/mongo/db/exec/delete.cpp +++ b/src/mongo/db/exec/delete.cpp @@ -164,6 +164,12 @@ namespace mongo { _txn = opCtx; ++_commonStats.unyields; _child->restoreState(opCtx); + + const NamespaceString& ns(_collection->ns()); + massert(28537, + str::stream() << "Demoted from primary while removing from " << ns.ns(), + !_params.shouldCallLogOp || + repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(ns.db())); } void DeleteStage::invalidate(const DiskLoc& dl, InvalidationType type) { diff --git a/src/mongo/db/exec/multi_plan.cpp b/src/mongo/db/exec/multi_plan.cpp index 3db8e3e4dd7..686555d15fd 100644 --- a/src/mongo/db/exec/multi_plan.cpp +++ b/src/mongo/db/exec/multi_plan.cpp @@ -148,7 +148,7 @@ namespace mongo { return state; } - void MultiPlanStage::pickBestPlan() { + Status MultiPlanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) { // Adds the amount of time taken by pickBestPlan() to executionTimeMillis. There's lots of // execution work that happens here, so this is needed for the time accounting to // make sense. @@ -182,11 +182,28 @@ namespace mongo { // Work the plans, stopping when a plan hits EOF or returns some // fixed number of results. for (size_t ix = 0; ix < numWorks; ++ix) { + // Yield, if it's time to yield. + if (NULL != yieldPolicy && yieldPolicy->shouldYield()) { + bool alive = yieldPolicy->yield(); + if (!alive) { + _failure = true; + Status failStat(ErrorCodes::OperationFailed, + "PlanExecutor killed during plan selection"); + _statusMemberId = WorkingSetCommon::allocateStatusMember(_candidates[0].ws, + failStat); + return failStat; + } + } + bool moreToDo = workAllPlans(numResults); if (!moreToDo) { break; } } - if (_failure) { return; } + if (_failure) { + invariant(WorkingSet::INVALID_ID != _statusMemberId); + WorkingSetMember* member = _candidates[0].ws->get(_statusMemberId); + return WorkingSetCommon::getMemberStatus(*member); + } // After picking best plan, ranking will own plan stats from // candidate solutions (winner and losers). @@ -290,6 +307,8 @@ namespace mongo { _collection->infoCache()->getPlanCache()->add(*_query, solutions, ranking.release()); } } + + return Status::OK(); } vector<PlanStageStats*> MultiPlanStage::generateCandidateStats() { @@ -341,8 +360,6 @@ namespace mongo { // Propagate most recent seen failure to parent. if (PlanStage::FAILURE == state) { - BSONObj objOut; - WorkingSetCommon::getStatusMemberObject(*candidate.ws, id, &objOut); _statusMemberId = id; } diff --git a/src/mongo/db/exec/multi_plan.h b/src/mongo/db/exec/multi_plan.h index 964d71443d0..956969eb2d2 100644 --- a/src/mongo/db/exec/multi_plan.h +++ b/src/mongo/db/exec/multi_plan.h @@ -36,6 +36,7 @@ #include "mongo/db/query/canonical_query.h" #include "mongo/db/query/query_solution.h" #include "mongo/db/query/plan_ranker.h" +#include "mongo/db/query/plan_yield_policy.h" namespace mongo { @@ -81,9 +82,15 @@ namespace mongo { /** * Runs all plans added by addPlan, ranks them, and picks a best. - * All further calls to getNext(...) will return results from the best plan. + * All further calls to work(...) will return results from the best plan. + * + * If 'yieldPolicy' is non-NULL, then all locks may be yielded in between round-robin + * works of the candidate plans. By default, 'yieldPolicy' is NULL and no yielding will + * take place. + * + * Returns a non-OK status if the plan was killed during yield. */ - void pickBestPlan(); + Status pickBestPlan(PlanYieldPolicy* yieldPolicy); /** Return true if a best plan has been chosen */ bool bestPlanChosen() const; @@ -156,20 +163,24 @@ namespace mongo { // uses -1 / kNoSuchPlan when best plan is not (yet) known int _backupPlanIdx; - // Did all plans fail while we were running them? Note that one plan can fail + // Set if this MultiPlanStage cannot continue, and the query must fail. This can happen in + // two ways. The first is that all candidate plans fail. Note that one plan can fail // during normal execution of the plan competition. Here is an example: // // Plan 1: collection scan with sort. Sort runs out of memory. // Plan 2: ixscan that provides sort. Won't run out of memory. // // We want to choose plan 2 even if plan 1 fails. + // + // The second way for failure to occur is that the execution of this query is killed during + // a yield, by some concurrent event such as a collection drop. bool _failure; // If everything fails during the plan competition, we can't pick one. size_t _failureCount; // if pickBestPlan fails, this is set to the wsid of the statusMember - // returned by ::work() + // returned by ::work() WorkingSetID _statusMemberId; // Stats diff --git a/src/mongo/db/exec/stagedebug_cmd.cpp b/src/mongo/db/exec/stagedebug_cmd.cpp index 75bcfb3dce9..e39024a5205 100644 --- a/src/mongo/db/exec/stagedebug_cmd.cpp +++ b/src/mongo/db/exec/stagedebug_cmd.cpp @@ -147,11 +147,15 @@ namespace mongo { // TODO: Do we want to do this for the user? I think so. PlanStage* rootFetch = new FetchStage(txn, ws.get(), userRoot, NULL, collection); - PlanExecutor runner(txn, ws.release(), rootFetch, collection); + PlanExecutor* rawExec; + Status execStatus = PlanExecutor::make(txn, ws.release(), rootFetch, collection, + PlanExecutor::YIELD_MANUAL, &rawExec); + fassert(28536, execStatus); + boost::scoped_ptr<PlanExecutor> exec(rawExec); BSONArrayBuilder resultBuilder(result.subarrayStart("results")); - for (BSONObj obj; PlanExecutor::ADVANCED == runner.getNext(&obj, NULL); ) { + for (BSONObj obj; PlanExecutor::ADVANCED == exec->getNext(&obj, NULL); ) { resultBuilder.append(obj); } diff --git a/src/mongo/db/exec/subplan.cpp b/src/mongo/db/exec/subplan.cpp index 25af192a464..3159734c065 100644 --- a/src/mongo/db/exec/subplan.cpp +++ b/src/mongo/db/exec/subplan.cpp @@ -77,31 +77,6 @@ namespace mongo { } // static - Status SubplanStage::make(OperationContext* txn, - Collection* collection, - WorkingSet* ws, - const QueryPlannerParams& params, - CanonicalQuery* cq, - SubplanStage** out) { - auto_ptr<SubplanStage> autoStage(new SubplanStage(txn, collection, ws, params, cq)); - // Plan each branch of the $or. - Status planningStatus = autoStage->planSubqueries(); - if (!planningStatus.isOK()) { - return planningStatus; - } - - // Use the multi plan stage to select a winning plan for each branch, and then - // construct the overall winning plan from the resulting index tags. - Status multiPlanStatus = autoStage->pickBestPlan(); - if (!multiPlanStatus.isOK()) { - return multiPlanStatus; - } - - *out = autoStage.release(); - return Status::OK(); - } - - // static bool SubplanStage::canUseSubplanning(const CanonicalQuery& query) { const LiteParsedQuery& lpq = query.getParsed(); const MatchExpression* expr = query.root(); @@ -213,11 +188,7 @@ namespace mongo { return Status::OK(); } - Status SubplanStage::pickBestPlan() { - // Adds the amount of time taken by pickBestPlan() to executionTimeMillis. There's lots of - // work that happens here, so this is needed for the time accounting to make sense. - ScopedTimer timer(&_commonStats.executionTimeMillis); - + Status SubplanStage::choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy) { // This is what we annotate with the index selections and then turn into a solution. auto_ptr<OrMatchExpression> theOr( static_cast<OrMatchExpression*>(_query->root()->shallowClone())); @@ -277,9 +248,8 @@ namespace mongo { _ws->clear(); - auto_ptr<MultiPlanStage> multiPlanStage(new MultiPlanStage(_txn, - _collection, - orChildCQ.get())); + _child.reset(new MultiPlanStage(_txn, _collection, orChildCQ.get())); + MultiPlanStage* multiPlanStage = static_cast<MultiPlanStage*>(_child.get()); // Dump all the solutions into the MPR. for (size_t ix = 0; ix < solutions.size(); ++ix) { @@ -294,7 +264,11 @@ namespace mongo { multiPlanStage->addPlan(solutions[ix], nextPlanRoot, _ws); } - multiPlanStage->pickBestPlan(); + Status planSelectStat = multiPlanStage->pickBestPlan(yieldPolicy); + if (!planSelectStat.isOK()) { + return planSelectStat; + } + if (!multiPlanStage->bestPlanChosen()) { mongoutils::str::stream ss; ss << "Failed to pick best plan for subchild " @@ -303,7 +277,6 @@ namespace mongo { } QuerySolution* bestSoln = multiPlanStage->bestSolution(); - _child.reset(multiPlanStage.release()); // Check that we have good cache data. For example, we don't cache things // for 2d indices. @@ -372,12 +345,13 @@ namespace mongo { // with stats obtained in the same fashion as a competitive ranking would have obtained // them. _ws->clear(); - auto_ptr<MultiPlanStage> multiPlanStage(new MultiPlanStage(_txn, _collection, _query)); + _child.reset(new MultiPlanStage(_txn, _collection, _query)); + MultiPlanStage* multiPlanStage = static_cast<MultiPlanStage*>(_child.get()); PlanStage* root; verify(StageBuilder::build(_txn, _collection, *soln, _ws, &root)); multiPlanStage->addPlan(soln, root, _ws); // Takes ownership first two arguments. - multiPlanStage->pickBestPlan(); + multiPlanStage->pickBestPlan(yieldPolicy); if (! multiPlanStage->bestPlanChosen()) { mongoutils::str::stream ss; ss << "Failed to pick best plan for subchild " @@ -385,7 +359,87 @@ namespace mongo { return Status(ErrorCodes::BadValue, ss); } - _child.reset(multiPlanStage.release()); + return Status::OK(); + } + + Status SubplanStage::choosePlanWholeQuery(PlanYieldPolicy* yieldPolicy) { + // Clear out the working set. We'll start with a fresh working set. + _ws->clear(); + + // Use the query planning module to plan the whole query. + vector<QuerySolution*> solutions; + Status status = QueryPlanner::plan(*_query, _plannerParams, &solutions); + if (!status.isOK()) { + return Status(ErrorCodes::BadValue, + "error processing query: " + _query->toString() + + " planner returned error: " + status.reason()); + } + + // We cannot figure out how to answer the query. Perhaps it requires an index + // we do not have? + if (0 == solutions.size()) { + return Status(ErrorCodes::BadValue, + str::stream() + << "error processing query: " + << _query->toString() + << " No query solutions"); + } + + if (1 == solutions.size()) { + PlanStage* root; + // Only one possible plan. Run it. Build the stages from the solution. + verify(StageBuilder::build(_txn, _collection, *solutions[0], _ws, &root)); + _child.reset(root); + return Status::OK(); + } + else { + // Many solutions. Create a MultiPlanStage to pick the best, update the cache, + // and so on. The working set will be shared by all candidate plans. + _child.reset(new MultiPlanStage(_txn, _collection, _query)); + MultiPlanStage* multiPlanStage = static_cast<MultiPlanStage*>(_child.get()); + + for (size_t ix = 0; ix < solutions.size(); ++ix) { + if (solutions[ix]->cacheData.get()) { + solutions[ix]->cacheData->indexFilterApplied = + _plannerParams.indexFiltersApplied; + } + + // version of StageBuild::build when WorkingSet is shared + PlanStage* nextPlanRoot; + verify(StageBuilder::build(_txn, _collection, *solutions[ix], _ws, + &nextPlanRoot)); + + // Owns none of the arguments + multiPlanStage->addPlan(solutions[ix], nextPlanRoot, _ws); + } + + // Delegate the the MultiPlanStage's plan selection facility. + Status planSelectStat = multiPlanStage->pickBestPlan(yieldPolicy); + if (!planSelectStat.isOK()) { + return planSelectStat; + } + + return Status::OK(); + } + } + + Status SubplanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) { + // Adds the amount of time taken by pickBestPlan() to executionTimeMillis. There's lots of + // work that happens here, so this is needed for the time accounting to make sense. + ScopedTimer timer(&_commonStats.executionTimeMillis); + + // Plan each branch of the $or. + Status subplanningStatus = planSubqueries(); + if (!subplanningStatus.isOK()) { + return choosePlanWholeQuery(yieldPolicy); + } + + // Use the multi plan stage to select a winning plan for each branch, and then construct + // the overall winning plan from the resulting index tags. + Status subplanSelectStat = choosePlanForSubqueries(yieldPolicy); + if (!subplanSelectStat.isOK()) { + return choosePlanWholeQuery(yieldPolicy); + } return Status::OK(); } diff --git a/src/mongo/db/exec/subplan.h b/src/mongo/db/exec/subplan.h index 477d6853194..a446eb746df 100644 --- a/src/mongo/db/exec/subplan.h +++ b/src/mongo/db/exec/subplan.h @@ -35,6 +35,7 @@ #include "mongo/base/status.h" #include "mongo/db/diskloc.h" #include "mongo/db/exec/plan_stage.h" +#include "mongo/db/query/plan_yield_policy.h" #include "mongo/db/query/query_planner_params.h" #include "mongo/db/query/query_solution.h" @@ -51,18 +52,11 @@ namespace mongo { */ class SubplanStage : public PlanStage { public: - /** - * Used to create SubplanStage instances. The caller owns the instance - * returned through 'out'. - * - * 'out' is valid only if an OK status is returned. - */ - static Status make(OperationContext* txn, - Collection* collection, - WorkingSet* ws, - const QueryPlannerParams& params, - CanonicalQuery* cq, - SubplanStage** out); + SubplanStage(OperationContext* txn, + Collection* collection, + WorkingSet* ws, + const QueryPlannerParams& params, + CanonicalQuery* cq); virtual ~SubplanStage(); @@ -87,13 +81,22 @@ namespace mongo { static const char* kStageType; - private: - SubplanStage(OperationContext* txn, - Collection* collection, - WorkingSet* ws, - const QueryPlannerParams& params, - CanonicalQuery* cq); + /** + * Selects a plan using subplanning. First uses the query planning results from + * planSubqueries() and the multi plan stage to select the best plan for each branch. + * + * If this effort fails, then falls back on planning the whole query normally rather + * then planning $or branches independently. + * + * If 'yieldPolicy' is non-NULL, then all locks may be yielded in between round-robin + * works of the candidate plans. By default, 'yieldPolicy' is NULL and no yielding will + * take place. + * + * Returns a non-OK status if the plan was killed during yield or if planning fails. + */ + Status pickBestPlan(PlanYieldPolicy* yieldPolicy); + private: /** * Plan each branch of the $or independently, and store the resulting * lists of query solutions in '_solutions'. @@ -107,8 +110,15 @@ namespace mongo { /** * Uses the query planning results from planSubqueries() and the multi plan stage * to select the best plan for each branch. + * + * Helper for pickBestPlan(). + */ + Status choosePlanForSubqueries(PlanYieldPolicy* yieldPolicy); + + /** + * Used as a fallback if subplanning fails. Helper for pickBestPlan(). */ - Status pickBestPlan(); + Status choosePlanWholeQuery(PlanYieldPolicy* yieldPolicy); // transactional context for read locks. Not owned by us OperationContext* _txn; diff --git a/src/mongo/db/exec/update.cpp b/src/mongo/db/exec/update.cpp index 7e16ebd9b7a..c3a44531f7b 100644 --- a/src/mongo/db/exec/update.cpp +++ b/src/mongo/db/exec/update.cpp @@ -789,9 +789,40 @@ namespace mongo { _child->saveState(); } + Status UpdateStage::restoreUpdateState(OperationContext* opCtx) { + const UpdateRequest& request = *_params.request; + const NamespaceString& nsString(request.getNamespaceString()); + + // We may have stepped down during the yield. + if (request.shouldCallLogOp() && + !repl::getGlobalReplicationCoordinator()->canAcceptWritesForDatabase(nsString.db())) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Demoted from primary while performing update on " + << nsString.ns()); + } + + if (request.getLifecycle()) { + UpdateLifecycle* lifecycle = request.getLifecycle(); + lifecycle->setCollection(_collection); + + if (!lifecycle->canContinue()) { + return Status(ErrorCodes::IllegalOperation, + "Update aborted due to invalid state transitions after yield.", + 17270); + } + + _params.driver->refreshIndexKeys(lifecycle->getIndexKeys(opCtx)); + } + + return Status::OK(); + } + void UpdateStage::restoreState(OperationContext* opCtx) { ++_commonStats.unyields; + // Restore our child. _child->restoreState(opCtx); + // Restore self. + uassertStatusOK(restoreUpdateState(opCtx)); } void UpdateStage::invalidate(const DiskLoc& dl, InvalidationType type) { diff --git a/src/mongo/db/exec/update.h b/src/mongo/db/exec/update.h index 862074e4001..b543ebb5fdf 100644 --- a/src/mongo/db/exec/update.h +++ b/src/mongo/db/exec/update.h @@ -125,6 +125,11 @@ namespace mongo { */ bool needInsert(); + /** + * Helper for restoring the state of this update. + */ + Status restoreUpdateState(OperationContext* opCtx); + UpdateStageParams _params; // Not owned by us. |