diff options
-rw-r--r-- | jstests/noPassthrough/explain_reports_correct_trial_period_statistics.js | 47 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_cursor.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/pipeline/plan_explainer_pipeline.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/pipeline/plan_explainer_pipeline.h | 1 | ||||
-rw-r--r-- | src/mongo/db/query/explain.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/plan_explainer.h | 5 | ||||
-rw-r--r-- | src/mongo/db/query/plan_explainer_impl.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/query/plan_explainer_impl.h | 1 | ||||
-rw-r--r-- | src/mongo/db/query/plan_explainer_sbe.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/query/plan_explainer_sbe.h | 1 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_cached_solution_planner.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_multi_planner.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/query/sbe_stage_builder.h | 4 |
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}; }; /** |