SERVER-112823 Make deferred get_executor flag an IFR flag (#54369)

GitOrigin-RevId: 978ae0b95e8902c4f48a849a2e80c15ceba969bd
This commit is contained in:
Matthew Boros 2026-05-26 13:30:02 -04:00 committed by MongoDB Bot
parent a200e3f91a
commit 041aaf31f0
19 changed files with 60 additions and 37 deletions

View File

@ -58,7 +58,7 @@ assert(
);
// Skip if AND_HASH did not execute in SBE. When featureFlagGetExecutorDeferredEngineChoice is
// enabled, isPlanSbeEligible() rejects AND_HASH plans via AndHashOrSortedRule (SERVER-90818),
// enabled, isPlanSbeCompatible() rejects AND_HASH plans via AndHashOrSortedRule (SERVER-90818),
// causing them to fall back to the classic engine even with trySbeEngine set.
const execExplain = coll.explain("executionStats").aggregate(pipeline, {allowDiskUse: false});
if (execExplain.explainVersion !== "2") {

View File

@ -58,7 +58,7 @@ assert(
);
// Skip if AND_SORTED did not execute in SBE. When featureFlagGetExecutorDeferredEngineChoice is
// enabled, isPlanSbeEligible() rejects AND_SORTED plans via AndHashOrSortedRule (SERVER-90818),
// enabled, isPlanSbeCompatible() rejects AND_SORTED plans via AndHashOrSortedRule (SERVER-90818),
// causing them to fall back to the classic engine even with trySbeEngine set.
const execExplain = coll.explain("executionStats").aggregate(pipeline, {allowDiskUse: false});
if (execExplain.explainVersion !== "2") {

View File

@ -63,7 +63,8 @@ PlanRankingResult CachedPlanner::extractPlanRankingResult() {
tassert(11756603,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled());
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
std::vector<std::unique_ptr<QuerySolution>> solutions;
solutions.push_back(extractQuerySolution());
return PlanRankingResult{

View File

@ -110,7 +110,8 @@ PlanRankingResult IdHackPlanner::extractPlanRankingResult() {
tassert(11974301,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled());
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
return PlanRankingResult{.usedIdhack = true,
.execState = SavedExecState{ClassicExecState{.workingSet = extractWs(),
.root = extractRoot()}},

View File

@ -78,7 +78,8 @@ public:
tassert(11756604,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled());
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
std::vector<std::unique_ptr<QuerySolution>> v;
v.push_back(std::move(_candidate.solution));
return PlanRankingResult{
@ -259,6 +260,9 @@ std::unique_ptr<PlannerInterface> attemptToUsePlan(
.planSummary;
};
const bool deferredExecutorEnabled =
plannerData.cq->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice);
if (!candidate.status.isOK()) {
// On failure, fall back to replanning the whole query. We neither evict the existing cache
// entry, nor cache the result of replanning.
@ -271,8 +275,7 @@ std::unique_ptr<PlannerInterface> attemptToUsePlan(
std::string replanReason = str::stream() << "cached plan returned: " << candidate.status;
recoverWhereExpression(plannerData.cq, std::move(candidate));
if (MONGO_unlikely(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled())) {
if (deferredExecutorEnabled) {
// If we're using the deferred get_executor, we throw an exception which is caught be a
// higher level replanning try/catch, and will reuse the top-level planning path. If
// we're using the regular get_executor, this counter is incremented in `replan`.
@ -313,8 +316,7 @@ std::unique_ptr<PlannerInterface> attemptToUsePlan(
<< decisionReads << " reads but it took at least " << numReads << " reads";
recoverWhereExpression(plannerData.cq, std::move(candidate));
if (MONGO_unlikely(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled())) {
if (deferredExecutorEnabled) {
incrementReplanCounterCb();
uassertStatusOK(Status(ReplanningRequiredInfo(plan_cache_util::CacheMode::AlwaysCache,
*plannerData.cachedPlanSolutionHash),

View File

@ -71,6 +71,11 @@ EngineSelectionPlanner::EngineSelectionPlanner(std::unique_ptr<PlannerInterface>
CanonicalQuery* cq,
Pipeline* pipeline,
const MultipleCollectionAccessor& collections) {
tassert(11282303,
"Only expected an EngineSelectionPlanner to be created when the deferred get_executor "
"is enabled.",
cq->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
_result = innerPlanner->extractPlanRankingResult();
// Engine selection is only needed for non-trivial cases. ID hack and cached planners already

View File

@ -68,7 +68,8 @@ PlanRankingResult MultiPlanner::extractPlanRankingResult() {
tassert(11974300,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled());
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
auto querySolution = _multiplanStage->extractBestSolution();
if (!_maybeExplainData.has_value()) {

View File

@ -59,6 +59,11 @@ PreComputedRankingResultPlanner::PreComputedRankingResultPlanner(PlannerData pla
: DeferredEngineChoicePlannerInterface(std::move(plannerData)), _result(std::move(result)) {}
PlanRankingResult PreComputedRankingResultPlanner::extractPlanRankingResult() {
tassert(11282302,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
return std::move(_result);
}

View File

@ -43,7 +43,8 @@ PlanRankingResult SingleSolutionPassthroughPlanner::extractPlanRankingResult() {
tassert(11974302,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled());
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
return PlanRankingResult{.solutions = makeQsnResult(std::move(_querySolution)),
.maybeExplainData = std::move(_maybeExplainData),
.plannerParams = extractPlannerParams(),

View File

@ -69,7 +69,8 @@ PlanRankingResult SubPlanner::extractPlanRankingResult() {
tassert(11974303,
"Expected `extractPlanRankingResult` to only be called with get executor deferred "
"feature flag enabled.",
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled());
cq()->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice));
auto querySolution = _subPlanStage->extractBestWholeQuerySolution();
return PlanRankingResult{.solutions = makeQsnResult(std::move(querySolution)),

View File

@ -179,8 +179,8 @@ void CanonicalQuery::initCq(boost::intrusive_ptr<ExpressionContext> expCtx,
// When the deferred engine choice path is enabled, it is safe to always optimize because
// the SBE plan cache is not used, so there is no risk of caching an optimized-away
// variable reference.
bool shouldOptimizeProj =
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled() ||
bool shouldOptimizeProj = expCtx->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice) ||
expCtx->getSbeCompatibility() == SbeCompatibility::notCompatible ||
!_findCommand->getLet();
if (parsedFind->proj->requiresMatchDetails()) {

View File

@ -1355,10 +1355,10 @@ CanonicalQuery::QueryShapeString encodeClassic(const CanonicalQuery& cq) {
// Encode the deferred engine selection feature flag so that cache entries cannot be shared when
// the flag is changed. This could lead to unpredictable scenarios.
const bool deferredGetExecutorEnabled =
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled();
const bool deferredGetExecutorEnabled = cq.getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice);
keyBuilder << (deferredGetExecutorEnabled ? "t" : "f");
if (MONGO_unlikely(deferredGetExecutorEnabled)) {
if (deferredGetExecutorEnabled) {
encodeDeferredGetExecutorSubplanningData(cq, &keyBuilder);
} else {
encodeLegacyGetExecutorSubplanningData(cq, &keyBuilder);

View File

@ -125,7 +125,9 @@ bool isQuerySbeCompatible(const CollectionPtr& collection,
// Queries against collections with a particular shape of compound hashed indexes are not
// supported.
if (!feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled() && collection &&
const bool deferredExecutorEnabled = cq.getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice);
if (!deferredExecutorEnabled && collection &&
collectionHasIndexWithHashedPathPrefixOfNonHashedPath(collection, expCtx)) {
return false;
}
@ -140,7 +142,7 @@ bool isQuerySbeCompatible(const CollectionPtr& collection,
return false;
}
if (solution && !isPlanSbeEligible(solution)) {
if (solution && !isPlanSbeCompatible(solution)) {
return false;
}
@ -220,8 +222,8 @@ EngineSelectionResult chooseEngine(OperationContext* opCtx,
const QuerySolution* solution,
const std::function<void()>& extendSolutionWithPipelineFn) {
const bool hasSolution = solution != nullptr;
const bool deferredEngineChoice =
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled();
const bool deferredEngineChoice = cq->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice);
tassert(11742301,
"Expected to choose engine based on solution only if "
"featureFlagGetExecutorDeferredEngineChoice is "

View File

@ -602,7 +602,7 @@ static_assert(HasPreVisit<AndHashOrSortedRule, AndSortedNode>);
static_assert(HasPreVisit<AndHashOrSortedRule, AndHashNode>);
} // namespace
bool isPlanSbeEligible(const QuerySolution* solution) {
bool isPlanSbeCompatible(const QuerySolution* solution) {
return !treeMatchesAny(
solution->root(), DistinctScanRule(), HashedIndexScanPatternRule(), AndHashOrSortedRule());
}

View File

@ -38,7 +38,7 @@ namespace mongo {
/**
* Returns 'false' for query plans that can not be executed in SBE.
*/
bool isPlanSbeEligible(const QuerySolution* solution);
bool isPlanSbeCompatible(const QuerySolution* solution);
/**
* Returns the engine of choice for executing the specified query plan.

View File

@ -140,7 +140,7 @@ TEST_F(EngineSelectionPlanFixture, DistinctScanEligibility) {
BSONObj indexFields = fromjson("{a: 1}");
std::unique_ptr<QuerySolution> solution = makeDistinctScanPlan(indexFields);
ASSERT_FALSE(isPlanSbeEligible(solution.get()));
ASSERT_FALSE(isPlanSbeCompatible(solution.get()));
}
// Test eligibility of FETCH + IXSCAN plans with hashed indexes.
@ -149,14 +149,14 @@ TEST_F(EngineSelectionPlanFixture, HashedIndexIxScanEligibility) {
{
BSONObj indexFields = fromjson("{a: 1, m: 'hashed', 'm.m1': 1}");
std::unique_ptr<QuerySolution> solution = makeIndexScanFetchPlan(indexFields);
ASSERT_FALSE(isPlanSbeEligible(solution.get()));
ASSERT_FALSE(isPlanSbeCompatible(solution.get()));
}
// Single hashed index.
{
BSONObj indexFields = fromjson("{a: 'hashed'}");
std::unique_ptr<QuerySolution> solution = makeIndexScanFetchPlan(indexFields);
ASSERT_TRUE(isPlanSbeEligible(solution.get()));
ASSERT_TRUE(isPlanSbeCompatible(solution.get()));
}
}
@ -170,7 +170,7 @@ TEST_F(EngineSelectionPlanFixture, AndHashEligibility) {
auto solution = std::make_unique<QuerySolution>();
solution->setRoot(std::move(andHash));
ASSERT_FALSE(isPlanSbeEligible(solution.get()));
ASSERT_FALSE(isPlanSbeCompatible(solution.get()));
}
// Test eligibility of AND_SORTED plans.
@ -183,7 +183,7 @@ TEST_F(EngineSelectionPlanFixture, AndSortedEligibility) {
auto solution = std::make_unique<QuerySolution>();
solution->setRoot(std::move(andSorted));
ASSERT_FALSE(isPlanSbeEligible(solution.get()));
ASSERT_FALSE(isPlanSbeCompatible(solution.get()));
}
// Test selection of IXSCAN + FETCH plans.

View File

@ -1176,7 +1176,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorFind
// paths.
maybeUpgradeIdHackFlag(*canonicalQuery, collections.getMainCollection());
if (MONGO_unlikely(feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled())) {
if (canonicalQuery->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice)) {
return exec_deferred_engine_choice::getExecutorFindDeferredEngineChoice(
opCtx,
collections,

View File

@ -92,11 +92,7 @@ void inspectPlannerResult(
const std::unique_ptr<PlannerInterface>& result,
const boost::optional<QueryPlannerParams::ReplanningData>& replanningData) {
// These assertions relate to replanning, so bail if the query did not replan.
// Also, these assertions do not apply to the deferred get_executor. The solution hash is
// checked after the plan ranking result is created, to update
// `replannedPlanIsCachedPlanCounter`.
if (!replanningData.has_value() ||
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled()) {
if (!replanningData.has_value()) {
return;
}
@ -172,6 +168,9 @@ std::unique_ptr<PlannerInterface> retryMakePlanner(
CanonicalQuery* canonicalQuery,
std::size_t plannerOptions,
Pipeline* pipeline) {
const bool deferredExecutorEnabled =
canonicalQuery->getExpCtx()->getIfrContext()->getSavedFlagValue(
feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice);
// We create this once on replanning and then make a copy for each QueryPlannerParams to own for
// subsequent calls.
boost::optional<QueryPlannerParams::ReplanningData> replanningData = boost::none;
@ -182,14 +181,17 @@ std::unique_ptr<PlannerInterface> retryMakePlanner(
// First try the single collection query parameters, as these would have been
// generated with query settings if present.
auto result = makePlanner(std::move(plannerParams));
inspectPlannerResult(result, replanningData);
// The assertions in `inspectPlannerResult` do not apply when the deferred executor is
// enabled.
if (!deferredExecutorEnabled) {
inspectPlannerResult(result, replanningData);
}
return result;
} catch (const ExceptionFor<ErrorCodes::NoDistinctScansForDistinctEligibleQuery>&) {
// The planner failed to generate a DISTINCT_SCAN for a distinct-like query. Remove
// the distinct property and replan using SBE or subplanning as applicable.
canonicalQuery->resetDistinct();
if (canonicalQuery->isSbeCompatible() &&
!feature_flags::gFeatureFlagGetExecutorDeferredEngineChoice.isEnabled()) {
if (canonicalQuery->isSbeCompatible() && !deferredExecutorEnabled) {
// Stages still need to be finalized for SBE since classic was used previously. In
// the deferred get_executor, the stages are finalized during lowering.
finalizePipelineStages(pipeline, canonicalQuery);

View File

@ -445,6 +445,7 @@ feature_flags:
cpp_varname: gFeatureFlagGetExecutorDeferredEngineChoice
fcv_gated: false
default: false
incremental_rollout_phase: in_development
featureFlagImprovedDepsAnalysis:
description: "Feature flag to enable pipeline rewrites that require a dependency graph"