summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/exec')
-rw-r--r--src/mongo/db/exec/delete.cpp6
-rw-r--r--src/mongo/db/exec/multi_plan.cpp25
-rw-r--r--src/mongo/db/exec/multi_plan.h19
-rw-r--r--src/mongo/db/exec/stagedebug_cmd.cpp8
-rw-r--r--src/mongo/db/exec/subplan.cpp130
-rw-r--r--src/mongo/db/exec/subplan.h48
-rw-r--r--src/mongo/db/exec/update.cpp31
-rw-r--r--src/mongo/db/exec/update.h5
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.