gortiz opened a new pull request, #18692:
URL: https://github.com/apache/pinot/pull/18692

   ## Problem
   
   For `SELECT ... ORDER BY <col> [DESC] LIMIT n` on a table range-partitioned 
on `<col>`, `SelectionQuerySegmentPruner` prunes down to the handful of 
segments the LIMIT needs (an unfiltered "latest n" query touches a couple of 
segments). Adding **any** filter — even a trivially-true one on an unrelated 
column — defeated that pruning, and the query engaged every matching segment.
   
   The cause: `SelectionQuerySegmentPruner.isApplicableTo` only allowed the 
order-by/LIMIT pruning when there was **no** filter, because the pruner sizes 
its keep-set by accumulating the unfiltered `getTotalDocs()` until `LIMIT + 
OFFSET` — which, with a filter, is an *upper* bound on matching rows and would 
prune segments that still hold top-n matches (wrong results).
   
   See #18685 for the full reproduction and analysis.
   
   ## Change
   
   Extend `SelectionQuerySegmentPruner` to run with a filter by accumulating a 
**lower bound** on each segment's matching rows instead of `getTotalDocs()`:
   
   ```
   guaranteedMatchingDocs(S) = getTotalDocs(S)  if S provably FULLY satisfies 
the filter
                               0                 otherwise
   ```
   
   - "Provably full" is the dual of `ColumnValueSegmentPruner`'s no-match test, 
computed from the predicate column's min/max for `>`, `>=`, `<`, `<=`, `=`, 
`<>` (conjunction = all children full; OR = any child full; anything unprovable 
= `false`).
   - Because the accumulation only ever *under*-counts, the boundary is reached 
only once ≥ LIMIT matching rows provably exist, so a segment is never pruned 
when it might still hold a top-n row. Straddling/partially-matching segments 
count 0 but are still kept (they overlap the boundary). An `AND (...)` with a 
non-provable conjunct simply degrades to "no pruning" — never wrong.
   - The optimization is **skipped when null handling is active** for a column 
(nulls are stored as a default value that pollutes the min/max metadata and the 
doc count), and a `NaN` min/max is never treated as a full match.
   
   The execution-time `MinMaxValueBasedSelectionOrderByCombineOperator` skip 
already worked with a filter; this restores the *plan-time* pruning that avoids 
building/opening the pruned segments at all.
   
   ## Tests
   
   `SelectionQuerySegmentPrunerTest` adds coverage for: each comparison 
operator, ASC/DESC, the straddling-segment counterexample (the case the lower 
bound exists to protect), the `AND`-with-non-provable-conjunct degradation, the 
FLOAT/DOUBLE NaN guard, and the null-handling gate (incl. column-based null 
handling: non-nullable column still optimizes, nullable does not).
   
   ## Benchmark
   
   Adds `BenchmarkSelectionOrderByFilterPruning` (pinot-perf). It toggles 
`enableNullHandling` to compare the optimized path against the pre-fix path 
**within one build** (null handling gates the optimization off). On a 
100-segment table (20k rows/segment), `LIMIT 10`:
   
   | query | score |
   |---|---:|
   | filtered, optimized | **0.661 ± 0.051 ms/op** |
   | filtered, null handling on (pre-fix) | 0.824 ± 0.117 ms/op |
   | no filter (reference) | 0.661 ± 0.076 ms/op |
   
   With the fix the filtered query reaches parity with the unfiltered one; 
without it the same query is measurably slower. The gap widens with segment 
count and per-segment cost.
   
   Closes #18685
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to