summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/noPassthrough/explain_reports_correct_trial_period_statistics.js47
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.cpp3
-rw-r--r--src/mongo/db/pipeline/plan_explainer_pipeline.cpp5
-rw-r--r--src/mongo/db/pipeline/plan_explainer_pipeline.h1
-rw-r--r--src/mongo/db/query/explain.cpp2
-rw-r--r--src/mongo/db/query/plan_explainer.h5
-rw-r--r--src/mongo/db/query/plan_explainer_impl.cpp4
-rw-r--r--src/mongo/db/query/plan_explainer_impl.h1
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.cpp15
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.h1
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.cpp6
-rw-r--r--src/mongo/db/query/sbe_multi_planner.cpp5
-rw-r--r--src/mongo/db/query/sbe_stage_builder.h4
13 files changed, 96 insertions, 3 deletions
diff --git a/jstests/noPassthrough/explain_reports_correct_trial_period_statistics.js b/jstests/noPassthrough/explain_reports_correct_trial_period_statistics.js
new file mode 100644
index 00000000000..05289cfde86
--- /dev/null
+++ b/jstests/noPassthrough/explain_reports_correct_trial_period_statistics.js
@@ -0,0 +1,47 @@
+// This test checks that .explain() reports the correct trial period statistics for a winning plan
+// in the "allPlansExecution" section.
+(function() {
+"use strict";
+
+const dbName = "test";
+const collName = jsTestName();
+
+const conn = MongoRunner.runMongod({});
+assert.neq(conn, null, "mongod failed to start up");
+
+const db = conn.getDB(dbName);
+const coll = db[collName];
+coll.drop();
+
+assert.commandWorked(coll.createIndex({a: 1}));
+assert.commandWorked(coll.createIndex({b: 1}));
+
+// Configure the server such that the trial period should end after doing 10 reads from storage.
+assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryPlanEvaluationWorks: 10}));
+
+assert.commandWorked(coll.insert(Array.from({length: 20}, (v, i) => {
+ return {a: 1, b: 1, c: i};
+})));
+
+const explain = coll.find({a: 1, b: 1}).sort({c: 1}).explain("allPlansExecution");
+
+// Since there are 20 documents, we expect that the full execution plan reports at least 20 keys
+// examined and 20 docs examined.
+//
+// It is possible that more than 20 keys/docs are examined, because we expect the plan to be closed
+// and re-opened during the trial period when running with SBE (which does not clear the execution
+// stats from the first open).
+assert.gte(explain.executionStats.totalKeysExamined, 20);
+assert.gte(explain.executionStats.totalDocsExamined, 20);
+
+// Extract the first plan in the "allPlansExecution" array. The winning plan is always reported
+// first.
+const winningPlanTrialPeriodStats = explain.executionStats.allPlansExecution[0];
+
+// The number of keys examined and docs examined should both be less than 10, since we configured
+// the trial period for each candidate plan to end after 10 storage reads.
+assert.lte(winningPlanTrialPeriodStats.totalKeysExamined, 10);
+assert.lte(winningPlanTrialPeriodStats.totalDocsExamined, 10);
+
+MongoRunner.stopMongod(conn);
+}());
diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp
index 023554a1bed..0047782344e 100644
--- a/src/mongo/db/pipeline/document_source_cursor.cpp
+++ b/src/mongo/db/pipeline/document_source_cursor.cpp
@@ -319,8 +319,7 @@ DocumentSourceCursor::DocumentSourceCursor(
if (pExpCtx->explain) {
// It's safe to access the executor even if we don't have the collection lock since we're
// just going to call getStats() on it.
- _winningPlanTrialStats =
- explainer.getWinningPlanStats(ExplainOptions::Verbosity::kExecStats);
+ _winningPlanTrialStats = explainer.getWinningPlanTrialStats();
}
if (collection) {
diff --git a/src/mongo/db/pipeline/plan_explainer_pipeline.cpp b/src/mongo/db/pipeline/plan_explainer_pipeline.cpp
index 4b3bcfac0a6..947080867ae 100644
--- a/src/mongo/db/pipeline/plan_explainer_pipeline.cpp
+++ b/src/mongo/db/pipeline/plan_explainer_pipeline.cpp
@@ -102,6 +102,11 @@ PlanExplainer::PlanStatsDetails PlanExplainerPipeline::getWinningPlanStats(
return {};
}
+PlanExplainer::PlanStatsDetails PlanExplainerPipeline::getWinningPlanTrialStats() const {
+ // We are not supposed to call this method on a pipeline explainer.
+ MONGO_UNREACHABLE;
+}
+
std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerPipeline::getRejectedPlansStats(
ExplainOptions::Verbosity verbosity) const {
// Multi-planning is not supported for aggregation pipelines.
diff --git a/src/mongo/db/pipeline/plan_explainer_pipeline.h b/src/mongo/db/pipeline/plan_explainer_pipeline.h
index f28adc3c7ea..e964b75e45b 100644
--- a/src/mongo/db/pipeline/plan_explainer_pipeline.h
+++ b/src/mongo/db/pipeline/plan_explainer_pipeline.h
@@ -48,6 +48,7 @@ public:
std::string getPlanSummary() const final;
void getSummaryStats(PlanSummaryStats* statsOut) const final;
PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final;
+ PlanStatsDetails getWinningPlanTrialStats() const final;
std::vector<PlanStatsDetails> getRejectedPlansStats(
ExplainOptions::Verbosity verbosity) const final;
std::vector<PlanStatsDetails> getCachedPlanStats(const PlanCacheEntry::DebugInfo&,
diff --git a/src/mongo/db/query/explain.cpp b/src/mongo/db/query/explain.cpp
index c27f425b28e..f40c7a6b1c0 100644
--- a/src/mongo/db/query/explain.cpp
+++ b/src/mongo/db/query/explain.cpp
@@ -335,7 +335,7 @@ void Explain::explainStages(PlanExecutor* exec,
const BSONObj& command,
BSONObjBuilder* out) {
auto&& explainer = exec->getPlanExplainer();
- auto winningPlanTrialStats = explainer.getWinningPlanStats(verbosity);
+ auto winningPlanTrialStats = explainer.getWinningPlanTrialStats();
Status executePlanStatus = Status::OK();
const CollectionPtr* collectionPtr = &collection;
diff --git a/src/mongo/db/query/plan_explainer.h b/src/mongo/db/query/plan_explainer.h
index 6c5583a4518..a820a027a3c 100644
--- a/src/mongo/db/query/plan_explainer.h
+++ b/src/mongo/db/query/plan_explainer.h
@@ -100,6 +100,11 @@ public:
virtual PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const = 0;
/**
+ * Returns statistics for the trial period of the winning plan selected by the multi-planner.
+ */
+ virtual PlanStatsDetails getWinningPlanTrialStats() const = 0;
+
+ /**
* Returns statistics that detail candidate plans rejected by the multi-planner. If no
* multi-planning has been performed, an empty vector is returned.
*
diff --git a/src/mongo/db/query/plan_explainer_impl.cpp b/src/mongo/db/query/plan_explainer_impl.cpp
index f503fc8d1c4..2bbb1ff7726 100644
--- a/src/mongo/db/query/plan_explainer_impl.cpp
+++ b/src/mongo/db/query/plan_explainer_impl.cpp
@@ -742,6 +742,10 @@ PlanExplainer::PlanStatsDetails PlanExplainerImpl::getWinningPlanStats(
return {bob.obj(), std::move(summary)};
}
+PlanExplainer::PlanStatsDetails PlanExplainerImpl::getWinningPlanTrialStats() const {
+ return getWinningPlanStats(ExplainOptions::Verbosity::kExecStats);
+}
+
std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerImpl::getRejectedPlansStats(
ExplainOptions::Verbosity verbosity) const {
std::vector<PlanStatsDetails> res;
diff --git a/src/mongo/db/query/plan_explainer_impl.h b/src/mongo/db/query/plan_explainer_impl.h
index 859b633e0e1..3ecdaa83588 100644
--- a/src/mongo/db/query/plan_explainer_impl.h
+++ b/src/mongo/db/query/plan_explainer_impl.h
@@ -53,6 +53,7 @@ public:
std::string getPlanSummary() const final;
void getSummaryStats(PlanSummaryStats* statsOut) const final;
PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final;
+ PlanStatsDetails getWinningPlanTrialStats() const final;
std::vector<PlanStatsDetails> getRejectedPlansStats(
ExplainOptions::Verbosity verbosity) const final;
std::vector<PlanStatsDetails> getCachedPlanStats(const PlanCacheEntry::DebugInfo&,
diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp
index a3a7859c957..63f9a9fff86 100644
--- a/src/mongo/db/query/plan_explainer_sbe.cpp
+++ b/src/mongo/db/query/plan_explainer_sbe.cpp
@@ -487,6 +487,21 @@ PlanExplainer::PlanStatsDetails PlanExplainerSBE::getWinningPlanStats(
_solution->root(), stats.get(), buildExecPlanDebugInfo(_root, _rootData), verbosity);
}
+PlanExplainer::PlanStatsDetails PlanExplainerSBE::getWinningPlanTrialStats() const {
+ invariant(_rootData);
+ if (_rootData->savedStatsOnEarlyExit) {
+ invariant(_solution);
+ return buildPlanStatsDetails(
+ _solution->root(),
+ _rootData->savedStatsOnEarlyExit.get(),
+ // This parameter is not used in `buildPlanStatsDetails` if the last parameter is
+ // `ExplainOptions::Verbosity::kExecStats`, as is the case here.
+ boost::none,
+ ExplainOptions::Verbosity::kExecStats);
+ }
+ return getWinningPlanStats(ExplainOptions::Verbosity::kExecStats);
+}
+
std::vector<PlanExplainer::PlanStatsDetails> PlanExplainerSBE::getRejectedPlansStats(
ExplainOptions::Verbosity verbosity) const {
if (_rejectedCandidates.empty()) {
diff --git a/src/mongo/db/query/plan_explainer_sbe.h b/src/mongo/db/query/plan_explainer_sbe.h
index a66675dec63..08b05e97aa5 100644
--- a/src/mongo/db/query/plan_explainer_sbe.h
+++ b/src/mongo/db/query/plan_explainer_sbe.h
@@ -60,6 +60,7 @@ public:
std::string getPlanSummary() const final;
void getSummaryStats(PlanSummaryStats* statsOut) const final;
PlanStatsDetails getWinningPlanStats(ExplainOptions::Verbosity verbosity) const final;
+ PlanStatsDetails getWinningPlanTrialStats() const final;
std::vector<PlanStatsDetails> getRejectedPlansStats(
ExplainOptions::Verbosity verbosity) const final;
std::vector<PlanStatsDetails> getCachedPlanStats(const PlanCacheEntry::DebugInfo&,
diff --git a/src/mongo/db/query/sbe_cached_solution_planner.cpp b/src/mongo/db/query/sbe_cached_solution_planner.cpp
index f6838f2b28d..25128582727 100644
--- a/src/mongo/db/query/sbe_cached_solution_planner.cpp
+++ b/src/mongo/db/query/sbe_cached_solution_planner.cpp
@@ -103,6 +103,12 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::finalizeExecutionPlan(
// tree, as we cannot resume such execution tree from where the trial run has stopped, and, as
// a result, we cannot stash the results returned so far in the plan executor.
if (!stats->common.isEOF && candidate.exitedEarly) {
+ if (_cq.getExplain()) {
+ // We save the stats on early exit if it's either an explain operation, as closing and
+ // re-opening the winning plan (below) changes the stats.
+ candidate.data.savedStatsOnEarlyExit =
+ candidate.root->getStats(true /* includeDebugInfo */);
+ }
candidate.root->close();
candidate.root->open(false);
// Clear the results queue.
diff --git a/src/mongo/db/query/sbe_multi_planner.cpp b/src/mongo/db/query/sbe_multi_planner.cpp
index 51b16c14d70..46598a4106f 100644
--- a/src/mongo/db/query/sbe_multi_planner.cpp
+++ b/src/mongo/db/query/sbe_multi_planner.cpp
@@ -102,6 +102,11 @@ CandidatePlans MultiPlanner::finalizeExecutionPlans(
// the trial run has stopped, and, as a result, we cannot stash the results returned so far
// in the plan executor.
if (!winner.root->getCommonStats()->isEOF && winner.exitedEarly) {
+ if (_cq.getExplain()) {
+ // We save the stats on early exit if it's either an explain operation, as closing and
+ // re-opening the winning plan (below) changes the stats.
+ winner.data.savedStatsOnEarlyExit = winner.root->getStats(true /* includeDebugInfo */);
+ }
winner.root->close();
winner.root->open(false);
// Clear the results queue.
diff --git a/src/mongo/db/query/sbe_stage_builder.h b/src/mongo/db/query/sbe_stage_builder.h
index 81335f937e0..f9dd9a79389 100644
--- a/src/mongo/db/query/sbe_stage_builder.h
+++ b/src/mongo/db/query/sbe_stage_builder.h
@@ -247,6 +247,10 @@ struct PlanStageData {
// If this execution tree was built as a result of replanning of the cached plan, this string
// will include the reason for replanning.
std::optional<std::string> replanReason;
+
+ // If this candidate plan has completed the trial run early by achieving one of the trial run
+ // metrics, the stats are cached in here.
+ std::unique_ptr<sbe::PlanStageStats> savedStatsOnEarlyExit{nullptr};
};
/**