Why I built this

At ByteDance we use MongoDB extensively for AI workloads, and slow queries are one of the most common problems customers report. Debugging them requires enough monitoring to see how a user’s query actually behaves. This contribution came out of that work: the slow query log did not report one thing the planner does, so an operator had to infer it. This is an open-source version of the observability we use internally.

What this feature does

When a MongoDB admin pins query settings — “for this query shape, use index X” — but the index can’t be used (say it was dropped), the planner falls back to multi-planning: it builds a plan against every available index and races them. That fallback is invisible today, even though its extra planning overhead can be exactly why a query showed up as slow.

Our change adds a boolean multiPlannerFallbackEngaged to the slow query log and the database profiler, so operators can see when the fallback fired. (PR #1639.)

The fallback path

It’s a catch block. Query settings say “use index X,” planning throws NoQueryExecutionPlans, and the catch removes the constraint (IGNORE_QUERY_SETTINGS) and retries with all indexes — succeeding, but with the overhead we now want on the record. The flag gets set at exactly that retry.

flowchart TD
    A["User sends: db.orders.find({a: 1, b: 1})"] --> B["find_cmd.cpp\nCommand handler"]
    B --> C["get_executor.cpp\nCreates query executor"]
    C --> D["retryMakePlanner()\nget_executor_helpers.cpp"]

    D --> E{"makePlanner(params)\nTry to build a plan"}
    E -->|Success| F["Plan Executor\nRuns the winning plan"]
    E -->|Throws NoQueryExecutionPlans| G{"Query settings\nexist?"}

    G -->|No| H["Re-throw\nReal error"]
    G -->|Yes, already ignored| H
    G -->|Yes, not yet ignored| I["SET multiPlannerFallbackEngaged = true\nSet IGNORE_QUERY_SETTINGS\nRetry planning"]

    I --> E

    F --> J["OpDebug\nStores all metrics"]
    J --> K["report()\nSlow query log"]
    J --> L["append()\nProfiler system.profile"]

    style I fill:#ff6b6b,color:#fff
    style K fill:#4ecdc4,color:#fff
    style L fill:#4ecdc4,color:#fff

Where the flag lives

OpDebug holds an operation’s diagnostics, and it has a nested AdditiveMetrics struct where similar flags like fromMultiPlanner already sit. That is the apparent place to add this one, and it does not work. The AdditiveMetrics instance can be replaced mid-operation, because setPlanSummaryMetrics() copies fresh stats in from PlanSummaryStats after execution. A flag set there during planning would be overwritten before anything read it.

So the flag lives on OpDebug directly: set in the catch block, read in report() and append(), never replaced.

flowchart TD
    subgraph CurOp["CurOp (one per operation, never replaced)"]
        subgraph OpDebug["OpDebug"]
            FLAG["multiPlannerFallbackEngaged ✅\nSet here, read here\nNever gets replaced"]

            subgraph AM["AdditiveMetrics (via getAdditiveMetrics())"]
                FMP["fromMultiPlanner\nkeysExamined\ndocsExamined"]
                BADFLAG["multiPlannerFallbackEngaged ❌\nGets LOST because\ninstance can be replaced"]
            end
        end
    end

    SET["retryMakePlanner()\nsets the flag"] --> FLAG
    READ["report() / append()\nreads the flag"] --> FLAG

    style FLAG fill:#2ecc71,color:#fff
    style BADFLAG fill:#e74c3c,color:#fff
    style SET fill:#3498db,color:#fff
    style READ fill:#3498db,color:#fff

Where the code lives

File What changed
src/mongo/db/op_debug.h:672-674 Declared bool multiPlannerFallbackEngaged{false}
src/mongo/db/op_debug.cpp:341 report() — emit to the slow query log
src/mongo/db/op_debug.cpp:624 append() — emit to the profiler
src/mongo/db/query/get_executor_helpers.cpp:124 retryMakePlanner() catch block — set the flag on fallback
jstests/noPassthrough/query/query_settings_fallback_profiler.js New integration test

The slow query log is text in mongod.log (written by report()); the profiler writes queryable BSON to system.profile (written by append()). Both read the same flag.

Locating the code

Two techniques covered most of the search. Searching for ticket keywords: IGNORE_QUERY_SETTINGS lands directly on the fallback in get_executor_helpers.cpp, and fromMultiPlanner surfaces an existing, near-identical flag. Tracing that similar feature end to end: fromMultiPlanner is declared in op_debug.h, set in setPlanSummaryMetrics(), emitted in report()/append(), and tested in profile_find.js. That is the same structure this change needed, apart from where the flag can safely be stored.