summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/resmokeconfig/suites/cqf_passthrough.yml1
-rw-r--r--jstests/core/sbe/from_plan_cache_flag.js46
-rw-r--r--jstests/noPassthrough/plan_cache_group_lookup.js34
-rw-r--r--src/mongo/db/curop.cpp6
-rw-r--r--src/mongo/db/curop.h1
-rw-r--r--src/mongo/db/query/cqf_get_executor.cpp3
-rw-r--r--src/mongo/db/query/get_executor.cpp25
-rw-r--r--src/mongo/db/query/plan_cache.h6
-rw-r--r--src/mongo/db/query/plan_executor_factory.cpp30
-rw-r--r--src/mongo/db/query/plan_executor_factory.h3
-rw-r--r--src/mongo/db/query/plan_executor_sbe.cpp5
-rw-r--r--src/mongo/db/query/plan_explainer_factory.cpp4
-rw-r--r--src/mongo/db/query/plan_explainer_factory.h1
-rw-r--r--src/mongo/db/query/plan_explainer_impl.cpp4
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.cpp1
-rw-r--r--src/mongo/db/query/plan_explainer_sbe.h7
-rw-r--r--src/mongo/db/query/plan_ranker.h2
-rw-r--r--src/mongo/db/query/plan_summary_stats.h2
-rw-r--r--src/mongo/db/query/sbe_cached_solution_planner.cpp10
19 files changed, 155 insertions, 36 deletions
diff --git a/buildscripts/resmokeconfig/suites/cqf_passthrough.yml b/buildscripts/resmokeconfig/suites/cqf_passthrough.yml
index 90a35ae3547..9ae9f4dd69a 100644
--- a/buildscripts/resmokeconfig/suites/cqf_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/cqf_passthrough.yml
@@ -35,6 +35,7 @@ selector:
- jstests/core/plan_cache_shell_helpers.js
- jstests/core/plan_cache_stats_shard_and_host.js
- jstests/core/profile_query_hash.js
+ - jstests/core/sbe/from_plan_cache_flag.js
- jstests/core/sbe/plan_cache_sbe_with_or_queries.js
- jstests/core/sbe_plan_cache_autoparameterize_collscan.js
# TODO SERVER-62034 Prevent distinct() from using CQF.
diff --git a/jstests/core/sbe/from_plan_cache_flag.js b/jstests/core/sbe/from_plan_cache_flag.js
new file mode 100644
index 00000000000..006b76d5af5
--- /dev/null
+++ b/jstests/core/sbe/from_plan_cache_flag.js
@@ -0,0 +1,46 @@
+// @tags: [
+// requires_profiling,
+// does_not_support_stepdowns
+// ]
+(function() {
+"use strict";
+
+load("jstests/libs/analyze_plan.js");
+load("jstests/libs/sbe_util.js"); // For checkSBEEnabled.
+load("jstests/libs/profiler.js"); // For getLatestProfilerEntry.
+load("jstests/libs/sbe_util.js"); // For checkSBEEnabled.
+
+if (!checkSBEEnabled(db, ["featureFlagSbeFull"], true /* checkAllNodes */)) {
+ jsTest.log("Skip running the test because SBE is not enabled");
+ return;
+}
+var testDB = db.getSiblingDB("profile_findandmodify");
+assert.commandWorked(testDB.dropDatabase());
+var coll = testDB.getCollection("test");
+assert.commandWorked(testDB.setProfilingLevel(2));
+coll.drop();
+coll.getPlanCache().clear();
+
+assert.commandWorked(coll.insert({a: 1}));
+assert.commandWorked(coll.insert({a: 2}));
+assert.commandWorked(coll.insert({a: 3}));
+assert.commandWorked(coll.insert({a: 2}));
+
+let pipeline = {$match: {a: 1}};
+coll.aggregate([pipeline]).toArray();
+let profileObj = getLatestProfilerEntry(testDB);
+/* fromPlanCache can be undefined in the profiler entry. The first ! determines the
+ * profileObj.fromPlanCache value's associated true/false value (important in the case where
+ * undefined) and then returns the opposite of the associated true/false value. The second !
+ * returns the opposite of the opposite value. In other words, the !! returns the boolean true/false
+ * association of a value. */
+assert.eq(!!profileObj.fromPlanCache, false);
+
+coll.aggregate({$match: {a: 2}}).toArray();
+profileObj = getLatestProfilerEntry(testDB);
+assert.eq(!!profileObj.fromPlanCache, true);
+
+coll.aggregate({$match: {a: 3}}).toArray();
+profileObj = getLatestProfilerEntry(testDB);
+assert.eq(!!profileObj.fromPlanCache, true);
+}()); \ No newline at end of file
diff --git a/jstests/noPassthrough/plan_cache_group_lookup.js b/jstests/noPassthrough/plan_cache_group_lookup.js
index 0c20d16e2c2..7fc63a5094b 100644
--- a/jstests/noPassthrough/plan_cache_group_lookup.js
+++ b/jstests/noPassthrough/plan_cache_group_lookup.js
@@ -36,12 +36,15 @@ assert.commandWorked(db.setProfilingLevel(2));
* Assert that the last aggregation command has a corresponding plan cache entry with the desired
* properties. 'version' is 1 if it's classic cache, 2 if it's SBE cache. 'isActive' is true if the
* cache entry is active. 'fromMultiPlanner' is true if the query part of aggregation has been
- * multi-planned. 'forcesClassicEngine' is true if the query is forced to use classic engine.
+ * multi-planned. 'fromPlanCache' is true if the winning plan was retrieved from the plan cache.
+ * 'forcesClassicEngine' is true if the query is forced to use classic engine.
*/
-function assertCacheUsage({version, fromMultiPlanner, isActive, forcesClassicEngine = false}) {
+function assertCacheUsage(
+ {version, fromMultiPlanner, fromPlanCache, isActive, forcesClassicEngine = false}) {
const profileObj = getLatestProfilerEntry(
db, {op: "command", "command.pipeline": {$exists: true}, ns: coll.getFullName()});
assert.eq(fromMultiPlanner, !!profileObj.fromMultiPlanner, profileObj);
+ assert.eq(fromPlanCache, !!profileObj.fromPlanCache, profileObj);
const entries = coll.getPlanCache().list();
assert.eq(entries.length, 1, entries);
@@ -71,19 +74,34 @@ function assertCacheUsage({version, fromMultiPlanner, isActive, forcesClassicEng
function testLoweredPipeline({pipeline, version, forcesClassicEngine = false}) {
let results = coll.aggregate(pipeline).toArray();
assert.eq(results.length, 1, results);
- const entry = assertCacheUsage(
- {version: version, fromMultiPlanner: true, isActive: false, forcesClassicEngine});
+ const entry = assertCacheUsage({
+ version: version,
+ fromMultiPlanner: true,
+ fromPlanCache: false,
+ isActive: false,
+ forcesClassicEngine
+ });
results = coll.aggregate(pipeline).toArray();
assert.eq(results.length, 1, results);
- let nextEntry = assertCacheUsage(
- {version: version, fromMultiPlanner: true, isActive: true, forcesClassicEngine});
+ let nextEntry = assertCacheUsage({
+ version: version,
+ fromMultiPlanner: true,
+ fromPlanCache: false,
+ isActive: true,
+ forcesClassicEngine
+ });
assert.eq(entry.planCacheKey, nextEntry.planCacheKey, {entry, nextEntry});
results = coll.aggregate(pipeline).toArray();
assert.eq(results.length, 1, results);
- nextEntry = assertCacheUsage(
- {version: version, fromMultiPlanner: false, isActive: true, forcesClassicEngine});
+ nextEntry = assertCacheUsage({
+ version: version,
+ fromMultiPlanner: false,
+ fromPlanCache: true,
+ isActive: true,
+ forcesClassicEngine
+ });
assert.eq(entry.planCacheKey, nextEntry.planCacheKey, {entry, nextEntry});
return nextEntry;
diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp
index 9c362854d97..a01bb33437f 100644
--- a/src/mongo/db/curop.cpp
+++ b/src/mongo/db/curop.cpp
@@ -831,6 +831,7 @@ void OpDebug::report(OperationContext* opCtx,
OPDEBUG_TOATTR_HELP_BOOL(hasSortStage);
OPDEBUG_TOATTR_HELP_BOOL(usedDisk);
OPDEBUG_TOATTR_HELP_BOOL(fromMultiPlanner);
+ OPDEBUG_TOATTR_HELP_BOOL(fromPlanCache);
if (replanReason) {
bool replanned = true;
OPDEBUG_TOATTR_HELP_BOOL(replanned);
@@ -999,6 +1000,7 @@ void OpDebug::append(OperationContext* opCtx,
OPDEBUG_APPEND_BOOL(b, hasSortStage);
OPDEBUG_APPEND_BOOL(b, usedDisk);
OPDEBUG_APPEND_BOOL(b, fromMultiPlanner);
+ OPDEBUG_APPEND_BOOL(b, fromPlanCache);
if (replanReason) {
bool replanned = true;
OPDEBUG_APPEND_BOOL(b, replanned);
@@ -1228,6 +1230,9 @@ std::function<BSONObj(ProfileFilter::Args)> OpDebug::appendStaged(StringSet requ
addIfNeeded("fromMultiPlanner", [](auto field, auto args, auto& b) {
OPDEBUG_APPEND_BOOL2(b, field, args.op.fromMultiPlanner);
});
+ addIfNeeded("fromPlanCache", [](auto field, auto args, auto& b) {
+ OPDEBUG_APPEND_BOOL2(b, field, args.op.fromPlanCache);
+ });
addIfNeeded("replanned", [](auto field, auto args, auto& b) {
if (args.op.replanReason) {
OPDEBUG_APPEND_BOOL2(b, field, true);
@@ -1443,6 +1448,7 @@ void OpDebug::setPlanSummaryMetrics(const PlanSummaryStats& planSummaryStats) {
sortTotalDataSizeBytes = planSummaryStats.sortTotalDataSizeBytes;
keysSorted = planSummaryStats.keysSorted;
fromMultiPlanner = planSummaryStats.fromMultiPlanner;
+ fromPlanCache = planSummaryStats.fromPlanCache;
replanReason = planSummaryStats.replanReason;
}
diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h
index d0e5d13313d..29a63981b68 100644
--- a/src/mongo/db/curop.h
+++ b/src/mongo/db/curop.h
@@ -251,6 +251,7 @@ public:
// single solution).
bool fromMultiPlanner{false};
+ bool fromPlanCache{false};
// True if a replan was triggered during the execution of this operation.
boost::optional<std::string> replanReason;
diff --git a/src/mongo/db/query/cqf_get_executor.cpp b/src/mongo/db/query/cqf_get_executor.cpp
index 3143822a986..355146d85ce 100644
--- a/src/mongo/db/query/cqf_get_executor.cpp
+++ b/src/mongo/db/query/cqf_get_executor.cpp
@@ -342,7 +342,8 @@ static std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> optimizeAndCreateExe
MultipleCollectionAccessor(collection),
QueryPlannerParams::Options::DEFAULT,
nss,
- std::move(yieldPolicy)));
+ std::move(yieldPolicy),
+ false /*isFromPlanCache*/));
return planExec;
}
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 3e8653eb229..cb1113404a2 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -521,10 +521,17 @@ public:
std::tuple<std::unique_ptr<PlanStage>, std::unique_ptr<QuerySolution>> extractResultData() {
return std::make_tuple(std::move(_root), std::move(_solution));
}
+ void setRecoveredFromPlanCache(bool val) {
+ _fromPlanCache = val;
+ }
+ bool isRecoveredFromPlanCache() {
+ return _fromPlanCache;
+ }
private:
std::unique_ptr<PlanStage> _root;
std::unique_ptr<QuerySolution> _solution;
+ bool _fromPlanCache{false};
};
/**
@@ -601,12 +608,21 @@ public:
_recoveredPinnedCacheEntry = pinnedEntry;
}
+ void setRecoveredFromPlanCache(bool val) {
+ _fromPlanCache = val;
+ }
+
+ bool isRecoveredFromPlanCache() {
+ return _fromPlanCache;
+ }
+
private:
QuerySolutionVector _solutions;
PlanStageVector _roots;
boost::optional<size_t> _decisionWorks;
bool _needSubplanning{false};
bool _recoveredPinnedCacheEntry{false};
+ bool _fromPlanCache{false};
};
/**
@@ -1156,6 +1172,7 @@ protected:
result->setDecisionWorks(cacheEntry->decisionWorks);
result->setRecoveredPinnedCacheEntry(cacheEntry->isPinned());
result->emplace(std::make_pair(std::move(root), std::move(stageData)));
+ result->setRecoveredFromPlanCache(true);
return result;
}
}
@@ -1197,7 +1214,7 @@ protected:
result->emplace(std::move(execTree), std::move(querySolution));
result->setDecisionWorks(cs->decisionWorks);
-
+ result->setRecoveredFromPlanCache(true);
return result;
}
}
@@ -1308,7 +1325,7 @@ std::unique_ptr<sbe::RuntimePlanner> makeRuntimePlannerIfNeeded(
invariant(numSolutions == 1);
// If we have a single solution and the plan is not pinned or plan contains a hash_lookup stage,
- // we will need we will need to do the runtime planning to check if the cached plan still
+ // we will need to do the runtime planning to check if the cached plan still
// performs efficiently, or requires re-planning.
tassert(6693503, "PlanStageData must be present", planStageData);
const bool hasHashLookup = !planStageData->foreignHashJoinCollections.empty();
@@ -1411,7 +1428,6 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe
// Prepare the SBE tree for execution.
stage_builder::prepareSlotBasedExecutableTree(
opCtx, root.get(), &data, *cq, collections, yieldPolicy.get(), true);
-
return plan_executor_factory::make(opCtx,
std::move(cq),
std::move(solutions[0]),
@@ -1420,7 +1436,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getSlotBasedExe
collections,
plannerParams.options,
std::move(nss),
- std::move(yieldPolicy));
+ std::move(yieldPolicy),
+ planningResult->isRecoveredFromPlanCache());
}
} // namespace
diff --git a/src/mongo/db/query/plan_cache.h b/src/mongo/db/query/plan_cache.h
index 752d5cfa4dd..2fc422c0ca2 100644
--- a/src/mongo/db/query/plan_cache.h
+++ b/src/mongo/db/query/plan_cache.h
@@ -706,8 +706,8 @@ private:
// We do nothing.
res.shouldBeCreated = false;
} else if (newWorks > oldWorks) {
- // This plan performed worse than expected. Rather than immediately overwriting the
- // cache, lower the bar to what is considered good performance and keep the entry
+ // The cached plan performed worse than expected. Rather than immediately overwriting
+ // the cache, lower the bar to what is considered good performance and keep the entry
// inactive.
// Be sure that 'works' always grows by at least 1, in case its current
@@ -725,7 +725,7 @@ private:
res.shouldBeCreated = true;
res.increasedWorks.emplace(increasedWorks);
} else {
- // This plan performed just as well or better than we expected, based on the
+ // This cached plan performed just as well or better than we expected, based on the
// inactive entry's works. We use this as an indicator that it's safe to
// cache (as an active entry) the plan this query used for the future.
if (callbacks) {
diff --git a/src/mongo/db/query/plan_executor_factory.cpp b/src/mongo/db/query/plan_executor_factory.cpp
index 2403ff7c353..1ff8d5660b8 100644
--- a/src/mongo/db/query/plan_executor_factory.cpp
+++ b/src/mongo/db/query/plan_executor_factory.cpp
@@ -27,6 +27,7 @@
* it in the license file.
*/
+#include <iostream>
#include "mongo/platform/basic.h"
@@ -128,7 +129,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
const MultipleCollectionAccessor& collections,
size_t plannerOptions,
NamespaceString nss,
- std::unique_ptr<PlanYieldPolicySBE> yieldPolicy) {
+ std::unique_ptr<PlanYieldPolicySBE> yieldPolicy,
+ bool planIsFromCache) {
auto&& [rootStage, data] = root;
LOGV2_DEBUG(4822860,
@@ -137,17 +139,21 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
"slots"_attr = data.debugString(),
"stages"_attr = sbe::DebugPrinter{}.print(*rootStage));
- return {{new PlanExecutorSBE(
- opCtx,
- std::move(cq),
- std::move(optimizerData),
- {makeVector<sbe::plan_ranker::CandidatePlan>(sbe::plan_ranker::CandidatePlan{
- std::move(solution), std::move(rootStage), std::move(data)}),
- 0},
- plannerOptions & QueryPlannerParams::RETURN_OWNED_DATA,
- std::move(nss),
- false,
- std::move(yieldPolicy)),
+ return {{new PlanExecutorSBE(opCtx,
+ std::move(cq),
+ std::move(optimizerData),
+ {makeVector<sbe::plan_ranker::CandidatePlan>(
+ sbe::plan_ranker::CandidatePlan{std::move(solution),
+ std::move(rootStage),
+ std::move(data),
+ false,
+ Status::OK(),
+ planIsFromCache}),
+ 0},
+ plannerOptions & QueryPlannerParams::RETURN_OWNED_DATA,
+ std::move(nss),
+ false,
+ std::move(yieldPolicy)),
PlanExecutor::Deleter{opCtx}}};
}
diff --git a/src/mongo/db/query/plan_executor_factory.h b/src/mongo/db/query/plan_executor_factory.h
index bf41f169af9..be7ab263a88 100644
--- a/src/mongo/db/query/plan_executor_factory.h
+++ b/src/mongo/db/query/plan_executor_factory.h
@@ -118,7 +118,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> make(
const MultipleCollectionAccessor& collections,
size_t plannerOptions,
NamespaceString nss,
- std::unique_ptr<PlanYieldPolicySBE> yieldPolicy);
+ std::unique_ptr<PlanYieldPolicySBE> yieldPolicy,
+ bool isFromPlanCache);
/**
* Similar to the factory function above in that it also constructs an executor for the winning SBE
diff --git a/src/mongo/db/query/plan_executor_sbe.cpp b/src/mongo/db/query/plan_executor_sbe.cpp
index 920cd17715b..bd62138da46 100644
--- a/src/mongo/db/query/plan_executor_sbe.cpp
+++ b/src/mongo/db/query/plan_executor_sbe.cpp
@@ -27,7 +27,6 @@
* it in the license file.
*/
-
#include "mongo/platform/basic.h"
#include "mongo/db/query/plan_executor_sbe.h"
@@ -102,9 +101,8 @@ PlanExecutorSBE::PlanExecutorSBE(OperationContext* opCtx,
_yieldPolicy->clearRegisteredPlans();
_yieldPolicy->registerPlan(_root.get());
}
-
const auto isMultiPlan = candidates.plans.size() > 1;
-
+ const auto isCachedCandidate = candidates.winner().isCachedCandidate;
if (!_cq || !_cq->getExpCtx()->explain) {
// If we're not in explain mode, there is no need to keep rejected candidate plans around.
candidates.plans.clear();
@@ -123,6 +121,7 @@ PlanExecutorSBE::PlanExecutorSBE(OperationContext* opCtx,
std::move(optimizerData),
std::move(candidates.plans),
isMultiPlan,
+ isCachedCandidate,
_rootData.debugInfo);
}
diff --git a/src/mongo/db/query/plan_explainer_factory.cpp b/src/mongo/db/query/plan_explainer_factory.cpp
index 544ab33fdd2..4f762aec167 100644
--- a/src/mongo/db/query/plan_explainer_factory.cpp
+++ b/src/mongo/db/query/plan_explainer_factory.cpp
@@ -57,6 +57,7 @@ std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root,
std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
bool isMultiPlan) {
// Pre-compute Debugging info for explain use.
+
auto debugInfoSBE = std::make_shared<const plan_cache_debug_info::DebugInfoSBE>(
plan_cache_util::buildDebugInfo(solution));
return std::make_unique<PlanExplainerSBE>(root,
@@ -65,6 +66,7 @@ std::unique_ptr<PlanExplainer> make(sbe::PlanStage* root,
std::move(optimizerData),
std::move(rejectedCandidates),
isMultiPlan,
+ false, /* isFromPlanCache */
debugInfoSBE);
}
@@ -75,6 +77,7 @@ std::unique_ptr<PlanExplainer> make(
std::unique_ptr<optimizer::AbstractABTPrinter> optimizerData,
std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
bool isMultiPlan,
+ bool isFromPlanCache,
std::shared_ptr<const plan_cache_debug_info::DebugInfoSBE> debugInfoSBE) {
// TODO SERVER-64882: Consider invariant(debugInfoSBE) as we may not need to create a
// DebugInfoSBE from QuerySolution after the feature flag is removed. We currently need it
@@ -90,6 +93,7 @@ std::unique_ptr<PlanExplainer> make(
std::move(optimizerData),
std::move(rejectedCandidates),
isMultiPlan,
+ isFromPlanCache,
debugInfoSBE);
}
} // namespace mongo::plan_explainer_factory
diff --git a/src/mongo/db/query/plan_explainer_factory.h b/src/mongo/db/query/plan_explainer_factory.h
index 5e24a755747..0b3c008304b 100644
--- a/src/mongo/db/query/plan_explainer_factory.h
+++ b/src/mongo/db/query/plan_explainer_factory.h
@@ -61,5 +61,6 @@ std::unique_ptr<PlanExplainer> make(
std::unique_ptr<optimizer::AbstractABTPrinter> optimizerData,
std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
bool isMultiPlan,
+ bool isFromPlanCache,
std::shared_ptr<const plan_cache_debug_info::DebugInfoSBE> debugInfo);
} // namespace mongo::plan_explainer_factory
diff --git a/src/mongo/db/query/plan_explainer_impl.cpp b/src/mongo/db/query/plan_explainer_impl.cpp
index 0b70636945a..dc118081b33 100644
--- a/src/mongo/db/query/plan_explainer_impl.cpp
+++ b/src/mongo/db/query/plan_explainer_impl.cpp
@@ -722,6 +722,10 @@ void PlanExplainerImpl::getSummaryStats(PlanSummaryStats* statsOut) const {
const CachedPlanStats* cachedStats =
static_cast<const CachedPlanStats*>(cachedPlan->getSpecificStats());
statsOut->replanReason = cachedStats->replanReason;
+ // Nonnull replanReason indicates cached plan was less effecient than expected and an
+ // alternative plan was chosen.
+ statsOut->replanReason ? statsOut->fromPlanCache = false
+ : statsOut->fromPlanCache = true;
} else if (STAGE_MULTI_PLAN == stages[i]->stageType()) {
statsOut->fromMultiPlanner = true;
} else if (STAGE_COLLSCAN == stages[i]->stageType()) {
diff --git a/src/mongo/db/query/plan_explainer_sbe.cpp b/src/mongo/db/query/plan_explainer_sbe.cpp
index e14eebb59b4..fd4232ce1a9 100644
--- a/src/mongo/db/query/plan_explainer_sbe.cpp
+++ b/src/mongo/db/query/plan_explainer_sbe.cpp
@@ -370,6 +370,7 @@ void PlanExplainerSBE::getSummaryStats(PlanSummaryStats* statsOut) const {
auto common = _root->getCommonStats();
statsOut->nReturned = common->advances;
statsOut->fromMultiPlanner = isMultiPlan();
+ statsOut->fromPlanCache = isFromCache();
statsOut->totalKeysExamined = 0;
statsOut->totalDocsExamined = 0;
statsOut->replanReason = _rootData->replanReason;
diff --git a/src/mongo/db/query/plan_explainer_sbe.h b/src/mongo/db/query/plan_explainer_sbe.h
index 5dc97f90641..f97e972de88 100644
--- a/src/mongo/db/query/plan_explainer_sbe.h
+++ b/src/mongo/db/query/plan_explainer_sbe.h
@@ -48,6 +48,7 @@ public:
std::unique_ptr<optimizer::AbstractABTPrinter> optimizerData,
std::vector<sbe::plan_ranker::CandidatePlan> rejectedCandidates,
bool isMultiPlan,
+ bool isCachedPlan,
std::shared_ptr<const plan_cache_debug_info::DebugInfoSBE> debugInfo)
: PlanExplainer{solution},
_root{root},
@@ -56,6 +57,7 @@ public:
_optimizerData(std::move(optimizerData)),
_rejectedCandidates{std::move(rejectedCandidates)},
_isMultiPlan{isMultiPlan},
+ _isFromPlanCache{isCachedPlan},
_debugInfo{debugInfo} {
tassert(5968203, "_debugInfo should not be null", _debugInfo);
}
@@ -63,7 +65,9 @@ public:
bool isMultiPlan() const final {
return _isMultiPlan;
}
-
+ bool isFromCache() const {
+ return _isFromPlanCache;
+ }
const ExplainVersion& getVersion() const final;
std::string getPlanSummary() const final;
void getSummaryStats(PlanSummaryStats* statsOut) const final;
@@ -97,6 +101,7 @@ private:
const std::vector<sbe::plan_ranker::CandidatePlan> _rejectedCandidates;
const bool _isMultiPlan{false};
+ const bool _isFromPlanCache{false};
// Pre-computed debugging info so we don't necessarily have to collect them from QuerySolution.
// All plans recovered from the same cached entry share the same debug info.
const std::shared_ptr<const plan_cache_debug_info::DebugInfoSBE> _debugInfo;
diff --git a/src/mongo/db/query/plan_ranker.h b/src/mongo/db/query/plan_ranker.h
index 8a6a2c18ad9..d456aa69d31 100644
--- a/src/mongo/db/query/plan_ranker.h
+++ b/src/mongo/db/query/plan_ranker.h
@@ -189,6 +189,8 @@ struct BaseCandidatePlan {
// If the candidate plan has failed in a recoverable fashion during the trial run, contains a
// non-OK status.
Status status{Status::OK()};
+ // Indicates whether this candidate plan was retrieved from the cache.
+ bool isCachedCandidate{false};
// Any results produced during the plan's execution prior to scoring are retained here.
std::deque<ResultType> results;
// This is used to track the original plan with clean PlanStage tree and the auxiliary data.
diff --git a/src/mongo/db/query/plan_summary_stats.h b/src/mongo/db/query/plan_summary_stats.h
index 59e3985662f..ae8e3e7abc8 100644
--- a/src/mongo/db/query/plan_summary_stats.h
+++ b/src/mongo/db/query/plan_summary_stats.h
@@ -110,6 +110,8 @@ struct PlanSummaryStats {
// candidates?
bool fromMultiPlanner = false;
+ // Was this plan recovered from the cache?
+ bool fromPlanCache = false;
// Was a replan triggered during the execution of this query?
boost::optional<std::string> replanReason;
diff --git a/src/mongo/db/query/sbe_cached_solution_planner.cpp b/src/mongo/db/query/sbe_cached_solution_planner.cpp
index 0cbf5623a5f..e7584b1f3d1 100644
--- a/src/mongo/db/query/sbe_cached_solution_planner.cpp
+++ b/src/mongo/db/query/sbe_cached_solution_planner.cpp
@@ -93,7 +93,6 @@ CandidatePlans CachedSolutionPlanner::plan(
_opCtx, _collections, _cq, *solutions[0], _yieldPolicy);
}
}
-
// If the '_decisionReads' is not present then we do not run a trial period, keeping the current
// plan.
if (!_decisionReads) {
@@ -110,7 +109,9 @@ CandidatePlans CachedSolutionPlanner::plan(
std::move(roots[0].first),
std::move(roots[0].second),
false /* exitedEarly*/,
- Status::OK()}),
+ Status::OK(),
+ true,
+ /*isFromPlanCache */}),
0};
}
@@ -131,6 +132,7 @@ CandidatePlans CachedSolutionPlanner::plan(
{}, /* optimizedData */
{}, /* rejectedCandidates */
false, /* isMultiPlan */
+ true, /* isFromPlanCache */
candidate.data.debugInfo
? std::make_unique<plan_cache_debug_info::DebugInfoSBE>(*candidate.data.debugInfo)
: nullptr);
@@ -185,7 +187,9 @@ plan_ranker::CandidatePlan CachedSolutionPlanner::collectExecutionStatsForCached
std::move(root),
std::move(data),
false /* exitedEarly*/,
- Status::OK()};
+ Status::OK(),
+ true,
+ /*is Cached plan*/};
ON_BLOCK_EXIT([rootPtr = candidate.root.get()] { rootPtr->detachFromTrialRunTracker(); });