• 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

      On the mixed plan-ranking path (CBRForNoMPResultsStrategy), a logical plan the cost-based ranker (CBR) costed can reach explain twice: once as the multi-planner's trial tree and once as CBR's never-ran costed clone. This is because CBR re-enumerates plans internally, so its QuerySolution objects differ from the multi-planner's even when the plans are structurally identical. The V3 explain format must display each logical plan as ONE plans[] entry whose statistics document carries both families (statistics.multiPlan from the trial, statistics.costBased from the clone).

      Today that merge lives in the explainer: PlanExplainerImpl::_getPlanEntriesV3() (src/mongo/db/query/plan_explainer_impl.cpp) matches never-ran clones against trial-carrying candidates (and the winner) by QuerySolution::hash(), keeps the trial tree as the displayed one, and remaps the clone's estimates onto it at serialization time (the estimateSourceRoot field of PlanInfoForExplainV3 plus a per-plan remapped estimate map in makeV3PlanEntry()). Review of that change (PR 10gen/mongo#58846) flagged that this makes explain aware of where and when the ranking machinery duplicates solutions, which will make future changes to that machinery more intrusive.

      Proposed fix

      Make the ranking strategies own the invariant "each solution handed to explain carries its own estimates in the estimate map", and hand explain an explicit duplicate link instead of hash matching:

      1. CBRForNoMPResultsStrategy already remaps the winner's estimates onto the multi-planner's QSN tree (addRemappedEstimates() in src/mongo/db/query/plan_ranking/cbr_for_no_mp_results.cpp). Extend the same remapping to every rejected trial plan that has a CBR-costed clone, matching by QuerySolution::hash() exactly as MultiPlanStage::abandonTrialsExceptHashes() already does for plan selection.
      2. Mark the clone records in the shared explain data with an explicit link - e.g. a duplicateOfSolutionHash (or index) field on SolutionWithPlanStage / PlanExplainerData (src/mongo/db/query/plan_ranker.h) - so the display layer can skip them without re-deriving the pairing.
      3. In _getPlanEntriesV3(), delete findMatchingClone(), the consumed[] bookkeeping with tasserts 13052908/13052909, and PlanInfoForExplainV3::estimateSourceRoot with the per-plan estimate-map remapping in makeV3PlanEntry(). Instead: skip candidates marked as duplicates, and read estimates directly off each displayed tree's QSNs.
      4. Move/keep the structural-identity tassert (currently in the explain-side remapping) in the strategy-side remapping so the invariant stays checked.

      Constraints

      • Legacy explain renders the duplicate records as-is, and PR 10gen/mongo#58846 keeps legacy output byte-identical. The clones must therefore stay in rejectedPlansWithStages untouched; they gain only an annotation.
      • The remapped estimates must be keyed by the displayed (trial) tree's QSNs, since that is the tree the serializer walks.

      Sequencing

      SERVER-131545 (refactor QuerySolution::score into a per-candidate scores record) touches the same per-candidate bookkeeping; implement these together or in sequence.

      A TODO SERVER-NNNNN comment referencing this ticket sits on the merge block in _getPlanEntriesV3().

            Assignee:
            Unassigned
            Reporter:
            Timour Katchaounov
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: