SERVER-112823 Make deferred get_executor flag an IFR flag (#54369)
GitOrigin-RevId: 978ae0b95e8902c4f48a849a2e80c15ceba969bd
This commit is contained in:
parent
a200e3f91a
commit
041aaf31f0
@ -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") {
|
||||
|
||||
@ -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") {
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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()}},
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 "
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user