-
Type:
Improvement
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: None
-
None
-
Query Optimization
-
None
-
None
-
None
-
None
-
None
-
None
-
None
Overview
Explain reads some of its output content back out of per-operation diagnostics state (OpDebug / CurOp) that the planning machinery parked there for logging and profiling. That state is owned by the logging subsystem - it may move or be reorganized - and it is per operation while the facts are per planning invocation: an operation that plans more than once (e.g. an inner executor) overwrites or accumulates the values, so explain can report another planning's value as this executor's.
The affected reads, all of the pattern "planner writes a fact into OpDebug, explain reads it back as content":
- src/mongo/db/query/explain.cpp (writeExplainOpsV3): CurOp::get(exec->getOpCtx())->debug().planRankerMethod -> the decidingPlanRanker argument of PlanExplainer::getPlanEntries(), which selects the kV3 ordering of the plans after the winner. Write sites: the plan ranking strategies (mp_plan_ranking.cpp, cbr_plan_ranking.cpp, cbr_for_no_mp_results.cpp, cost_based_plan_ranking.cpp, get_executor_deferred_engine_choice_planning.cpp).
- src/mongo/db/query/explain.cpp (appendQueryPlannerCommonInfo): debug().getAdditiveMetrics().planningTime -> optimizationTimeMillis/optimizationTimeMicros in the queryPlanner section. Additive metrics accumulate across the operation, so multi-planning operations report combined planning time.
- (weaker instance) src/mongo/db/query/explain_common.cpp (generateQueryShapeHash): debug().getQueryShapeHash() -> the top-level queryShapeHash field. Command-scoped rather than executor-scoped, so the mismatch hazard is smaller, but the authoritative source is the query itself, not the telemetry copy.
Not in scope - explain reads of live op-runtime measurements whose system of record genuinely is CurOp: elapsedTimeTotal() -> executionTimeMillis and getPeakTrackedMemoryBytes() -> peakTrackedMemBytes.
Proposed fix
Carry the planning-invocation facts on the planning-to-explain channel that already exists, PlanExplainerData (src/mongo/db/query/plan_explainer.h), which the ranking strategies already populate (estimates, rejected plans, trial snapshot, planStageQsnMap) and which flows strategy -> PlanRankingResult.maybeExplainData -> executor factory -> PlanExplainerImpl::_explainData:
- Add PlanRankerMethod decidingPlanRanker = kNone to PlanExplainerData. Mirror every existing OpDebug write in the ranking strategies into the adjacent explain data (when present; non-explain queries carry none). The OpDebug writes stay - they serve logging, which is their actual job. Merge semantics for operator<<: keep the lhs value unless it is kNone (the strategy that made the final decision owns the field).
- Drop the decidingPlanRanker parameter from PlanExplainer::getPlanEntries(); PlanExplainerImpl::_getPlanEntriesV3() reads it from _explainData. writeExplainOpsV3() then loses its CurOp read and OpCtx null-guard.
- Carry planningTime the same way (the planner knows it when it hands over the ranking result), deleting the second exec->getOpCtx() null-guard in explain.cpp.
- Optionally source queryShapeHash from the query rather than OpDebug.
The SBE runtime planner's OpDebug write (classic_runtime_planner_for_sbe/multi_planner.cpp) stays as is until SERVER-132033 routes SBE through the per-plan enumerator.
TODO SERVER-NNNNN comments referencing this ticket sit at the three read sites.
Origin: PR 10gen/mongo#58846 review round 2 (the OpCtx null-guard question) and author follow-up.
- is related to
-
SERVER-132377 Add reason to the rankerChoice object in explain V3
-
- Closed
-
-
SERVER-132033 Extend V3 explain with output from the SBE and Express explainers
-
- Open
-
- related to
-
SERVER-132012 Choose an explain-specialized PlanExplainer at the factory instead of the isExplain constructor flag
-
- Needs Scheduling
-