-
Type:
Task
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: None
-
Component/s: None
-
None
-
Query Execution
-
Fully Compatible
-
None
-
None
-
None
-
None
-
None
-
None
-
None
Overview
The query.maxEstimatedScanBytes.rejected, rejectedAndOverridden, and dryRunWouldReject (SERVER-130231) FTDC counters can be incremented more than once for what is logically a single rejected user query, when the query has a rooted $or that goes through classic subplanning.
Mechanism
SubplanStage::pickBestPlan (src/mongo/db/exec/classic/subplan.cpp) calls QueryPlanner::planSubqueries, which calls QueryPlanner::plan() once per $or branch (query_planner.cpp, around line 2439). The branch loop returns immediately (with a non-OK status) on the first branch that fails to plan – e.g. because maxEstimatedScanBytes rejects it. This makes subplanningStatus non-OK, which then triggers a fallback call to SubplanStage::choosePlanWholeQuery, which calls QueryPlanner::plan() again, this time on the whole (un-split) query. If the collection also exceeds the threshold for the whole-query plan (which it typically will, since it's the same collection), this second call also triggers rejection and increments the counter again.
Reproduction
Verified empirically against a build from the kyle.burgess/SERVER-130231 branch:
db.heavy.insertMany(...); // 200 docs, no indexes, collection larger than threshold db.adminCommand({setParameter: 1, maxEstimatedScanBytes: heavySize - 1}); const before = db.adminCommand({serverStatus: 1}).metrics.query.maxEstimatedScanBytes.rejected; db.runCommand({find: "heavy", filter: {$or: [{a: 5}, {b: 7}]}}); // rejected, no index on a or b const after = db.adminCommand({serverStatus: 1}).metrics.query.maxEstimatedScanBytes.rejected; // before=0, after=2 -- one rejected $or query incremented the counter by 2, not 1.
Impact
This is an observability/metrics-accuracy issue, not a correctness issue – the query is still correctly rejected (or, for SERVER-130231's dry-run mode, correctly allowed through with a log line). But operators using these counters to gauge production impact (e.g. before enabling maxEstimatedScanBytes enforcement via dry-run mode) will see inflated counts for any workload with rooted $or queries lacking indexes on all branches, which could distort capacity-planning decisions.
Suggested fix direction
Consider de-duplicating the increment per top-level CanonicalQuery (e.g. via a flag/counter check higher up the call stack, before subplanning branches out), rather than incrementing at each low-level QueryPlanner::plan() call site. This would need to account for: the branch-loop-then-whole-query-fallback pattern described above, and the already-known per-outer-document replanning under classic NLJ for $lookup (a separate, arguably-intentional multiplicity noted in SERVER-127688/SERVER-130231 test comments).
Related
SERVER-127688(introduced maxEstimatedScanBytes and these counters)SERVER-130231(dry-run mode; inherits the same overcounting for dryRunWouldReject)
- is related to
-
SERVER-127688 Reject queries that will perform a full COLLSCAN on a large collection
-
- Closed
-
-
SERVER-130231 Add dryrun mode to maxEstimatedScanBytes
-
- Closed
-