ExportXMLWordPrintableJSON

    • Type: Task
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: None
    • Component/s: Build
    • None
    • Query Execution
    • QE 2026-08-17, QE 2026-08-31, QE 2026-09-15, QE 2026-09-28
    • 200
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      `$geoNear`'s query predicate is merged into the find filter by `DocumentSourceGeoNear::asNearQuery()`. When it names a field the geo index doesn't cover, the planner cannot assign it to the index scan, so `buildIndexedAnd()` leaves it on a FETCH stage placed above GEO_NEAR.

      That means every document the near stage returns pays for distance computation, BSON ownership and a round trip through the BoundedSorter, and only then gets discarded by a predicate that could have run first. On the `Aggregation.GeoNear2d` mongo-perf workload roughly half the buffered documents were thrown away this way: the stage buffered 988 documents to return 224.

      This patch adds a planner pass, `pushResidualFilterIntoGeoNear()`, that collapses a `FETCH

      {filter}

      then GEO_NEAR` pair into the geo node and evaluates the predicate in the stage's per-interval fetch, before any of that downstream work. It is result-equivalent — the same MatchExpression, against the same documents, evaluated lower in the tree. Removing the FETCH is legal because `GeoNear*Node::fetched()` is already true and the node reports all fields as provided. For the 2D path the annulus check is kept first in the AND, since it is cheap and geo-selective.

      The predicate goes into a new `docFilter` field rather than the existing `filter`. GeoNear2DStage routes `GeoNearParams::filter` to its covered IndexScan as a key-level filter, whose IndexKeyMatchableDocument throws massert 17409 for any field absent from the key pattern. Reusing `filter` would turn this into a user-visible query failure on 2D.

      `removeImpreciseInternalExprFilters()` now also learns about `docFilter`: it strips imprecise `$_internalExpr*` conjuncts from `fetched()` nodes, and without this the pushdown would have smuggled a redundant conjunct past it and evaluated it per document.

      Nothing here crosses a process boundary: QuerySolution is never serialized, mongos ships commands rather than plans, and the plan cache stores index tags and rebuilds solutions through `analyzeDataAccess()` on every hit. Mixed-version clusters need no gating. Explain output does change shape — the FETCH above GEO_NEAR disappears and a `docFilter` appears on the geo node. This is likely a lesser concern.

      Measurements (local LTO+PGO build, no BOLT; mongo-perf, 1 thread, n=9–11 paired reps):
      ```
      ┌─────────────────────────────┬──────────┬─────────────┐
      │ test │ instr/op │ ops/sec │
      ├─────────────────────────────┼──────────┼─────────────┤
      │ Aggregation.GeoNear2d │ −17.6% │ +24% … +34% │
      ├─────────────────────────────┼──────────┼─────────────┤
      │ Aggregation.GeoNear2dSphere │ −21.6% │ +31% … +45% │
      └─────────────────────────────┴──────────┴─────────────┘
      ```

            Assignee:
            Jan Steemann
            Reporter:
            Jan Steemann
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: