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.