-
Notifications
You must be signed in to change notification settings - Fork 5.8k
SERVER-32721 Explain output should indicate when a backup plan is used #1231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 8 commits
ed35209
a7c1914
b9c7aed
6316125
8731c80
3ec2451
9c66dab
fa97a62
d0f1174
e2f2483
22982fd
ca5e444
ffd904b
d86caa4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Test that the explain will use backup plan if the original winning plan ran out of memory in the | ||
| // "executionStats" mode | ||
| // This test was designed to reproduce SERVER-32721" | ||
| (function() { | ||
| "use strict"; | ||
|
|
||
| db.foo.drop() let bulk = db.foo.initializeUnorderedBulkOp(); | ||
| for (let i = 0; i < 100000; ++i) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is great, but we tend to favor tests that don't need as much data to be inserted, since they will execute faster. In this case you configure the maxBlockingSortBytes, so do you need this many documents? |
||
| bulk.insert({_id: i, x: i, y: i}); | ||
| } | ||
|
|
||
| bulk.execute(); | ||
| db.foo | ||
| .ensureIndex({x: 1}) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing semi-colon, also on line 23 and 27. |
||
|
|
||
| // Configure log level and lower the sort bytes limit. | ||
| db.setLogLevel(5, "query"); | ||
| db.adminCommand({setParameter: 1, internalQueryExecMaxBlockingSortBytes: 100}); | ||
|
|
||
| // This query will not use the backup plan, hence it generates only two stages: winningPlan and | ||
| // rejectedPlans | ||
| assert | ||
| .commandWorked(db.foo.find({x: {$gte: 90}}).sort({_id: 1}).explain(true)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your comment here mentions what we expect, but you just assert that the command "worked", which just means it returned a document with an Check out the helper functions defined here: https://github.com/mongodb/mongo/blob/r3.7.2/jstests/libs/analyze_plan.js I think you should add a helper there which gets the 'originalWinningPlan', and assert that it is null here, and non-null below. |
||
|
|
||
| // This query will use backup plan, the exaplin output for this query will generate three | ||
| // stages: winningPlan, rejectedPlans and originalWinningPlan | ||
| assert.commandWorked(db.foo.find({x: {$gte: 90000}}).sort({_id: 1}).explain(true)) | ||
| }()); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,6 +71,7 @@ MultiPlanStage::MultiPlanStage(OperationContext* opCtx, | |
| _query(cq), | ||
| _bestPlanIdx(kNoSuchPlan), | ||
| _backupPlanIdx(kNoSuchPlan), | ||
| _originalWinningPlanIdx(kNoSuchPlan), | ||
| _failure(false), | ||
| _failureCount(0), | ||
| _statusMemberId(WorkingSet::INVALID_ID) { | ||
|
|
@@ -128,6 +129,7 @@ PlanStage::StageState MultiPlanStage::doWork(WorkingSetID* out) { | |
| // cached plan runner to fall back on a different solution | ||
| // if the best solution fails. Alternatively we could try to | ||
| // defer cache insertion to be after the first produced result. | ||
| _originalWinningPlanIdx = _bestPlanIdx; | ||
|
|
||
| _collection->infoCache()->getPlanCache()->remove(*_query).transitional_ignore(); | ||
|
|
||
|
|
@@ -245,6 +247,7 @@ Status MultiPlanStage::pickBestPlan(PlanYieldPolicy* yieldPolicy) { | |
| _backupPlanIdx = kNoSuchPlan; | ||
| if (bestSolution->hasBlockingStage && (0 == alreadyProduced.size())) { | ||
| LOG(5) << "Winner has blocking stage, looking for backup plan..."; | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big deal, but try not to include unnecessary changes like this in your patch. This one isn't particularly offensive, but I'd still recommend reverting this change. |
||
| for (size_t ix = 0; ix < _candidates.size(); ++ix) { | ||
| if (!_candidates[ix].solution->hasBlockingStage) { | ||
| LOG(5) << "Candidate " << ix << " is backup child"; | ||
|
|
@@ -452,6 +455,12 @@ void MultiPlanStage::doInvalidate(OperationContext* opCtx, | |
| bool MultiPlanStage::hasBackupPlan() const { | ||
| return kNoSuchPlan != _backupPlanIdx; | ||
| } | ||
| int MultiPlanStage::backupPlanIdx() const { | ||
| return _backupPlanIdx; | ||
| } | ||
| int MultiPlanStage::originalWinningPlanIdx() const { | ||
| return _originalWinningPlanIdx; | ||
| } | ||
|
|
||
| bool MultiPlanStage::bestPlanChosen() const { | ||
| return kNoSuchPlan != _bestPlanIdx; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,6 +132,11 @@ class MultiPlanStage final : public PlanStage { | |
| /** Return the index of the best plan chosen, for testing */ | ||
| int bestPlanIdx() const; | ||
|
|
||
| /** Return the index of the backup plan chosen, for testing */ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you just copied this from above, but in new code we tend to always prefer instead of Also, these new methods are used for explain output, not just for testing. I'm pretty sure Please update these, and any others in this file while you're at it. |
||
| int backupPlanIdx() const; | ||
|
|
||
| /** Return the index of the backup plan chosen, for testing */ | ||
| int originalWinningPlanIdx() const; | ||
| /** | ||
| * Returns the QuerySolution for the best plan, or NULL if no best plan | ||
| * | ||
|
|
@@ -198,10 +203,14 @@ class MultiPlanStage final : public PlanStage { | |
| // uses -1 / kNoSuchPlan when best plan is not (yet) known | ||
| int _bestPlanIdx; | ||
|
|
||
| // index into _candidates, of the backup plan for sort | ||
| // index into _candidates, of the backup of the plan competition | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still doesn't read particularly well. How about re-phrasing this to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this still hasn't been addressed? |
||
| // uses -1 / kNoSuchPlan when best plan is not (yet) known | ||
| int _backupPlanIdx; | ||
|
|
||
| // index into _candidates, of the original winner of the plan competition | ||
| // uses -1 / kNoSuchPlan when best plan is not (yet) known | ||
| int _originalWinningPlanIdx; | ||
|
|
||
| // 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: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -289,6 +289,15 @@ unique_ptr<PlanStageStats> getWinningPlanStatsTree(const PlanExecutor* exec) { | |
| : std::move(exec->getRootStage()->getStats()); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Get PlanExecutor's original winning plan stats tree. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please elaborate here that this will only be different from the actual winning plan if a non-blocking backup plan was used. How about |
||
| */ | ||
| unique_ptr<PlanStageStats> getOriginalWinningPlanStatsTree(const PlanExecutor* exec) { | ||
| MultiPlanStage* mps = getMultiPlanStage(exec->getRootStage()); | ||
| return mps ? std::move(mps->getStats()->children[mps->originalWinningPlanIdx()]) | ||
| : std::move(exec->getRootStage()->getStats()); | ||
| } | ||
| } // namespace | ||
|
|
||
| namespace mongo { | ||
|
|
@@ -644,6 +653,16 @@ void Explain::generatePlannerInfo(PlanExecutor* exec, | |
| BSONObjBuilder plannerBob(out->subobjStart("queryPlanner")); | ||
|
|
||
| plannerBob.append("plannerVersion", QueryPlanner::kPlannerVersion); | ||
|
|
||
| const auto mps = getMultiPlanStage(exec->getRootStage()); | ||
| int originaWinningPlanIdx = static_cast<size_t>(mps->originalWinningPlanIdx()); | ||
|
|
||
| if (originaWinningPlanIdx > -1) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like you never use the actual index, so how about changing line 658 to be |
||
| plannerBob.append("backupPlanUsed", true); | ||
| } else { | ||
| plannerBob.append("backupPlanUsed", false); | ||
| } | ||
|
|
||
| plannerBob.append("namespace", exec->nss().ns()); | ||
|
|
||
| // Find whether there is an index filter set for the query shape. The 'indexFilterSet' | ||
|
|
@@ -688,6 +707,16 @@ void Explain::generatePlannerInfo(PlanExecutor* exec, | |
| } | ||
| allPlansBob.doneFast(); | ||
|
|
||
| if (originaWinningPlanIdx > -1) { | ||
| // Generate array of original winning plan | ||
| BSONObjBuilder originalWinningPlanBob(plannerBob.subobjStart("originalWinningPlan")); | ||
| const auto originalWinnerStats = getOriginalWinningPlanStatsTree(exec); | ||
| statsToBSON(*originalWinnerStats.get(), | ||
| &originalWinningPlanBob, | ||
| ExplainOptions::Verbosity::kQueryPlanner); | ||
| originalWinningPlanBob.doneFast(); | ||
| } | ||
|
|
||
| plannerBob.doneFast(); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you're missing a semi-colon after
.drop()here.