-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: None
-
Component/s: None
-
None
-
Query Optimization
-
Fully Compatible
-
ALL
-
200
-
None
-
None
-
None
-
None
-
None
-
None
-
None
The QuerySolutionNode-based explain serializer (statsToBSON() in src/mongo/db/query/plan_explainer_sbe.cpp, used to produce queryPlanner.winningPlan.queryPlan) guards against exceeding the explain size threshold but has no guard against exceeding the maximum BSON nesting depth. For sufficiently deep plan trees the server builds and sends an explain reply nested deeper than the max allowable BSON depth (~200 levels). BSONObjBuilder does not enforce depth at build time, so the reply is sent
successfully — but clients cannot parse it. The shell's message validation rejects it with:
BSONObj exceeds maximum nested object depth in element with field name 'stages.0.$cursor.queryPlanner.winningPlan.queryPlan.inputStages.0.inputStages.0. ...'
This error is thrown client-side (bson_validate.cpp, ErrorCodes::Overflow) with no server error code attached, so callers cannot handle it like the existing server-side BSONObjectTooLarge case. Any driver would be equally unable to parse the reply.
Note that the sibling serializer for sbe::PlanStageStats (statsToBSONHelper() in the same file) already has a depth guard; the QSN-based serializer was missed.
How it manifests. The product_limits task fails on the enterprise-amazon-linux2023-arm64 variant: jstests/product_limits/dataset_many_collections.js, workload WorkloadManyCollectionsLookupBushy (an aggregate with 499 chained $lookup stages), aborts when running explain("allPlansExecution").
Timeline of why this latent bug surfaced now:
SERVER-118014(March 2026) refactored EqLookupNode to plan the foreign collection as an explicit second child. With >1 child, the serializer emits an inputStages array, costing 2 BSON nesting levels per $lookup — ~1000 levels for 499 lookups.SERVER-130747(July 2026) added the product_limits suite to plain (default-knob) variants, where trySbeRestricted pushes the $lookup chain down as EQ_LOOKUP nodes, exercising this serializer for the first time with such a deep tree.
Fix. Add a depth guard to the QSN-based statsToBSON() mirroring the existing one in statsToBSONHelper(): track the current depth during recursion (+1 for a single-child inputStage, +2 for inputStages arrays) and, at BSONDepth::getMaxDepthForUserStorage() - 2, emit "warning": "stats tree exceeded BSON depth limit; omitting the rest of the tree" and stop recursing, so the reply always stays parseable.
- is related to
-
SERVER-130747 Add performance testing covering combinations of newly supported SBE operators
-
- Closed
-
-
SERVER-118014 Refactor EqLookupNode to use two QuerySolutionNode as sources
-
- Closed
-