-
Notifications
You must be signed in to change notification settings - Fork 5.7k
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 13 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,30 @@ | ||
// 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" | ||
|
||
load("jstests/libs/analyze_plan.js"); | ||
|
||
"use strict"; | ||
|
||
db.foo.drop(); | ||
|
||
let bulk = db.foo.initializeUnorderedBulkOp(); | ||
|
||
for (let i = 0; i < 100000; ++i) { | ||
bulk.insert({_id: i, x: i, y: i}); | ||
} | ||
|
||
bulk.execute(); | ||
db.foo.ensureIndex({x: 1}); | ||
|
||
// Configure log level and lower the sort bytes limit. | ||
db.setLogLevel(5, "query"); | ||
|
||
db.adminCommand({setParameter: 1, internalQueryExecMaxBlockingSortBytes: 100}); | ||
|
||
const test1 = db.foo.find({x: {$gte: 90}}).sort({_id: 1}).explain(true); | ||
const test2 = db.foo.find({x: {$gte: 90000}}).sort({_id: 1}).explain(true); | ||
// This query will not use the backup plan, hence it generates only two stages: winningPlan and | ||
// rejectedPlans | ||
assert.eq(backupPlanUsed(test1), false, "test1 did not use a backup plan"); | ||
// This query will use backup plan, the exaplin output for this query will generate three | ||
// stages: winningPlan, rejectedPlans and originalWinningPlan | ||
assert(backupPlanUsed(test2), "backup plan invoked in test2"); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,9 +129,21 @@ class MultiPlanStage final : public PlanStage { | |
/** Return true if a best plan has been chosen */ | ||
bool bestPlanChosen() const; | ||
|
||
/** Return the index of the best plan chosen, for testing */ | ||
/* | ||
* Return the index of the best plan chosen | ||
*/ | ||
int bestPlanIdx() const; | ||
|
||
/** | ||
* Return the index of the backup plan chosen | ||
*/ | ||
int backupPlanIdx() const; | ||
|
||
/** | ||
* Return the index of the original winning plan chosen | ||
*/ | ||
int originalWinningPlanIdx() const; | ||
|
||
/** | ||
* Returns the QuerySolution for the best plan, or NULL if no best plan | ||
* | ||
|
@@ -198,10 +210,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 | ||
|
||
// uses -1 / kNoSuchPlan when best plan is not (yet) known | ||
int _backupPlanIdx; | ||
|
||
// Index into _candidates, of the original winner of the plan which can be used if a blocking plan .fails | ||
|
||
// This is set to 'kNoSuchPlan' if there is no backup plan, or when it 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 |
---|---|---|
|
@@ -62,6 +62,7 @@ using std::string; | |
using std::unique_ptr; | ||
using std::vector; | ||
|
||
bool backupPlanUsed = false; | ||
|
||
/** | ||
* Traverse the tree rooted at 'root', and add all tree nodes into the list 'flattened'. | ||
*/ | ||
|
@@ -280,6 +281,30 @@ std::vector<std::unique_ptr<PlanStageStats>> getRejectedPlansTrialStats(PlanExec | |
return res; | ||
} | ||
|
||
/** | ||
* Gather the PlanStages when a backup plan is used | ||
|
||
*/ | ||
std::vector<std::unique_ptr<PlanStageStats>> getRejectedPlansTrialStatsForAllPlanExecution(PlanExecutor* exec) { | ||
const auto mps = getMultiPlanStage(exec->getRootStage()); | ||
std::vector<std::unique_ptr<PlanStageStats>> res; | ||
|
||
if (backupPlanUsed) { | ||
// Get the stats from the trial period for all the plans. | ||
if (mps) { | ||
const auto mpsStats = mps->getStats(); | ||
for (size_t i = 0; i < mpsStats->children.size(); ++i) { | ||
if (i != static_cast<size_t>(mps->originalWinningPlanIdx())) { | ||
res.emplace_back(std::move(mpsStats->children[i])); | ||
} | ||
} | ||
} | ||
} else { | ||
res = getRejectedPlansTrialStats(exec); | ||
} | ||
|
||
return res; | ||
} | ||
|
||
/** | ||
* Get PlanExecutor's winning plan stats tree. | ||
*/ | ||
|
@@ -289,6 +314,18 @@ unique_ptr<PlanStageStats> getWinningPlanStatsTree(const PlanExecutor* exec) { | |
: std::move(exec->getRootStage()->getStats()); | ||
} | ||
|
||
|
||
/** | ||
* Returns the root of the roginal winning plan used by 'exec'. | ||
|
||
* This might be different than the final winning plan in the case that the MultiPlanStage selected a | ||
* blocking plan which failed, and fell back to a non-blocking plan instead. | ||
* If there is no MultiPlanStage in the tree, returns the root stage of 'exec' | ||
|
||
*/ | ||
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 +681,17 @@ void Explain::generatePlannerInfo(PlanExecutor* exec, | |
BSONObjBuilder plannerBob(out->subobjStart("queryPlanner")); | ||
|
||
plannerBob.append("plannerVersion", QueryPlanner::kPlannerVersion); | ||
|
||
const auto mps = getMultiPlanStage(exec->getRootStage()); | ||
// const bool backupPlanUsed = ((mps->originalWinningPlanIdx()) > -1); | ||
backupPlanUsed = ((mps->originalWinningPlanIdx()) > -1); | ||
|
||
if (backupPlanUsed) { | ||
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 +736,16 @@ void Explain::generatePlannerInfo(PlanExecutor* exec, | |
} | ||
allPlansBob.doneFast(); | ||
|
||
if (backupPlanUsed) { | ||
// 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(); | ||
} | ||
|
||
|
@@ -798,7 +856,7 @@ void Explain::generateExecutionInfo(PlanExecutor* exec, | |
planBob.doneFast(); | ||
} | ||
|
||
const vector<unique_ptr<PlanStageStats>> rejectedStats = getRejectedPlansTrialStats(exec); | ||
const vector<unique_ptr<PlanStageStats>> rejectedStats = getRejectedPlansTrialStatsForAllPlanExecution(exec); | ||
for (size_t i = 0; i < rejectedStats.size(); ++i) { | ||
BSONObjBuilder planBob(allPlansBob.subobjStart()); | ||
generateSinglePlanExecutionInfo( | ||
|
@@ -855,8 +913,9 @@ void Explain::explainStages(PlanExecutor* exec, | |
const Collection* collection, | ||
ExplainOptions::Verbosity verbosity, | ||
BSONObjBuilder* out) { | ||
|
||
auto winningPlanTrialStats = Explain::getWinningPlanTrialStats(exec); | ||
|
||
|
||
Status executePlanStatus = Status::OK(); | ||
|
||
// If we need execution stats, then run the plan in order to gather the stats. | ||
|
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.
Please put the contents of this test in an immediately-invoked-function-expression (https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)