xiangfu0 commented on code in PR #19539:
URL: https://github.com/apache/pinot/pull/19539#discussion_r4201480263


##########
pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java:
##########
@@ -152,4 +223,106 @@ private boolean isPartitionMatch(Expression 
filterExpression, SegmentPartitionIn
         return true;
     }
   }
+
+  /// Lazily prepares only visited expressions and values. Instances belong to 
one prune call, never shared by queries.
+  private final class PreparedPredicate {
+    private final Expression _expression;
+    private final PartitionFunction _partitionFunction;
+    private FilterKind _kind;
+    private List<Expression> _operands;
+    private PreparedPredicate[] _children;
+    private boolean _isPartitionPredicate;
+    private Integer _singlePartitionId;
+    private IntSet _partitionIds;
+    private int _numEvaluatedValues;
+
+    private PreparedPredicate(Expression expression, PartitionFunction 
partitionFunction) {
+      _expression = expression;
+      _partitionFunction = partitionFunction;
+    }
+
+    private boolean matches(Set<Integer> partitions) {

Review Comment:
   Agreed; `850bad9f22` uses whole-predicate eager preparation, not the 
previous lazy/OR-on-miss variant. This supersedes my earlier replies that kept 
preparation lazy. The candidate IDs are prepared once per equivalent partition 
function per prune call, then reused for segment overlap checks. AND 
intersects, OR unions, and unsupported predicates are unrestricted. Partition 
columns are validated as single-valued, so the intersection is safe even when a 
segment contains multiple partitions. The cached singleton-ID fast path is 
retained.
   
   Invalid-input handling is separate: if eager preparation throws, that 
function group uses the original per-segment evaluator for the rest of the 
call. We do not silently discard invalid literals; original short-circuit/error 
behavior is preserved. Below the preparation threshold, and for functions that 
opt out, the original evaluator still applies.
   
   I reran the published implementation against `abbba34a77` (previous cached 
implementation), including 256, 512, 1024, 4096, and 16384 segments. The 
preparation-disabled control is the original per-segment evaluator at the 
current revision, not a separately built master. Same harness/dependencies, 
freshly compiled pruner revisions, JDK 25.0.4.1, JMH 1.37, one thread, two 
forks, 3 × 1-second warmups / 5 × 1-second measurements per fork, average time, 
`-prof gc`. Independently constructed equal 64-way Modulo functions, one 
partition per segment. Every fixture checks selected segments before timing.
   
   Nested filter: `(id = 7 OR id IN (0, ..., 15)) AND otherColumn = 1`; the 
unrelated column acts as a wildcard. First-hit IN(128) uses partition 0 for 
every segment. Scores below are µs per full prune call, mean ± JMH-reported 
99.9% error. The interpretation column uses a conservative twice-relative-error 
heuristic, not a formal paired significance test.
   
   <details>
   <summary>Full main-run latency matrix</summary>
   
   | Filter | Segments | Preparation disabled (µs/op) | Before cached (µs/op) | 
Eager (µs/op) | Eager vs before | Interpretation |
   |---|---:|---:|---:|---:|---:|---|
   | EQUALS | 256 | 3.81 ± 1.15 | 1.76 ± 0.10 | 1.63 ± 0.03 | -7.3% | 
Inconclusive |
   | EQUALS | 512 | 6.91 ± 0.20 | 3.71 ± 0.26 | 3.22 ± 0.29 | -13.2% | 
Inconclusive |
   | EQUALS | 1,024 | 16.26 ± 0.86 | 9.35 ± 1.94 | 7.55 ± 0.24 | -19.3% | 
Inconclusive |
   | EQUALS | 4,096 | 63.43 ± 1.34 | 36.93 ± 5.26 | 29.97 ± 1.62 | -18.8% | 
Inconclusive |
   | EQUALS | 16,384 | 323.47 ± 4.93 | 152.53 ± 2.13 | 140.52 ± 15.72 | -7.9% | 
Inconclusive |
   | IN(16) | 256 | 26.17 ± 0.27 | 2.58 ± 0.03 | 3.61 ± 0.34 | +39.7% | Likely 
regression |
   | IN(16) | 512 | 76.10 ± 0.73 | 5.83 ± 0.18 | 5.50 ± 0.28 | -5.6% | 
Inconclusive |
   | IN(16) | 1,024 | 147.08 ± 9.87 | 12.69 ± 0.30 | 12.02 ± 1.09 | -5.2% | 
Inconclusive |
   | IN(16) | 4,096 | 614.52 ± 42.12 | 50.59 ± 1.83 | 55.23 ± 33.58 | +9.2% | 
Inconclusive |
   | IN(16) | 16,384 | 1954.26 ± 29.93 | 233.58 ± 19.48 | 211.82 ± 21.19 | 
-9.3% | Inconclusive |
   | Nested AND/OR | 256 | 32.72 ± 1.54 | 3.69 ± 0.12 | 3.76 ± 0.56 | +1.9% | 
Inconclusive |
   | Nested AND/OR | 512 | 92.21 ± 2.34 | 7.95 ± 0.79 | 5.33 ± 0.15 | -33.0% | 
Likely improvement |
   | Nested AND/OR | 1,024 | 187.26 ± 11.98 | 15.51 ± 0.34 | 11.58 ± 0.71 | 
-25.3% | Likely improvement |
   | Nested AND/OR | 4,096 | 713.18 ± 7.91 | 69.60 ± 3.87 | 45.00 ± 2.10 | 
-35.3% | Likely improvement |
   | Nested AND/OR | 16,384 | 2454.42 ± 160.26 | 307.59 ± 7.72 | 204.75 ± 9.17 
| -33.4% | Likely improvement |
   | IN(128), first hit | 256 | 5.50 ± 0.31 | 10.09 ± 26.19 | 5.27 ± 1.21 | 
-47.8% | Inconclusive |
   | IN(128), first hit | 512 | 11.82 ± 0.36 | 8.50 ± 0.74 | 9.36 ± 1.26 | 
+10.1% | Inconclusive |
   | IN(128), first hit | 1,024 | 24.80 ± 1.20 | 17.72 ± 0.69 | 17.40 ± 0.41 | 
-1.8% | Inconclusive |
   | IN(128), first hit | 4,096 | 95.30 ± 1.02 | 78.98 ± 10.39 | 63.89 ± 1.28 | 
-19.1% | Inconclusive |
   | IN(128), first hit | 16,384 | 419.06 ± 8.90 | 371.91 ± 41.33 | 282.40 ± 
11.20 | -24.1% | Likely improvement |
   
   </details>
   
   Two noisy controls and the small-case cost were checked with complete 
before/eager reruns at both 256 and 4096 segments, using fresh forks and the 
same settings. All four paired repeats are included; the original matrix is 
retained above:
   
   | Filter | Segments | Before cached (µs/op) | Eager (µs/op) | Change |
   |---|---:|---:|---:|---:|
   | IN(16) | 256 | 2.60 ± 0.04 | 3.23 ± 0.04 | +23.9% |
   | IN(16) | 4,096 | 50.24 ± 2.53 | 45.00 ± 2.36 | -10.4% |
   | IN(128), first hit | 256 | 3.90 ± 0.29 | 4.63 ± 0.07 | +18.6% |
   | IN(128), first hit | 4,096 | 71.22 ± 6.94 | 66.90 ± 5.30 | -6.1% |
   
   Nested pruning improves about 33%, 25%, 35%, and 33% at 512/1024/4096/16384 
segments versus the previous cached implementation. The 256 nested cell is 
inconclusive in this rerun. The stable 256 repeats show a small eager cost: 
+0.62 µs for IN(16), +0.73 µs for first-hit IN(128). Several cheap/large 
controls still have wide intervals, so I am not claiming universal 
no-regression.
   
   Where the savings come from: preparation disabled recomputes literal IDs and 
visits the predicate for each segment. The previous cache already avoids most 
repeated ID computation; eager additionally collapses the full predicate once 
and removes per-segment tree traversal. At 4096 segments, nested pruning is 
713.18 µs without preparation, 69.60 µs before cached, and 45.00 µs eager. 
Allocation falls by about 1192 B/prune for nested at 1024+ segments versus 
before cached; first-hit IN adds about 3464 B/prune because every literal is 
prepared.
   
   The PR description is being updated to these results and tradeoffs. Apache 
CI is green on `850bad9f22` (13 checks); this rerun changes no production code 
or tests, and no benchmark source is committed. These are synthetic 
broker-pruner measurements, not end-to-end customer QPS.
   
   



-- 
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