alexch2000 commented on issue #19339:
URL: https://github.com/apache/pinot/issues/19339#issuecomment-5407558574

   We hit the same cliff, from a different direction. Instead of a subquery 
inside the OR, the OR is on two time ranges (day-over-day comparison) and the 
scan appears only on segments that lack the configured range index.
   
   **Query shape** (v1 engine, realtime table, ~134K segments):
   
   ```sql
   SELECT <10-min bucket>, FUNNELCOUNT(...)
   FROM events
   WHERE (
       (name = 'A' AND flow_id = 'F' AND action_id = 'X')
       OR (name = 'B' AND flow_id = 'F')
     )
     AND device_os IN ('android', 'ios')
     AND (
       (ts >= :a0 AND ts < :a1)
       OR (ts >= :b0 AND ts < :b1)   -- b = a - 24h
     )
   GROUP BY 1
   ```
   
   `name`, `flow_id`, `action_id`, `device_os` have inverted indexes. `ts` is 
the time column and has `rangeIndexColumns` configured, but ~3% of segments are 
missing the index on disk (separate issue, being fixed by reload).
   
   **Numbers**
   
   | | window A only | A OR B |
   |---|---|---|
   | `numEntriesScannedInFilter` | 101,187 | **441,757,659** |
   | `numDocsScanned` | 66,800 | 154,975 |
   | `numSegmentsProcessed` | 1,384 | 4,541 |
   | `realtimeThreadMemAllocatedBytes` | 684 MB | 1.83 GB |
   
   ~4,400× more filter work for ~2.3× more matched rows. Window B alone behaves 
like window A alone.
   
   **Verbose EXPLAIN on the OR query** (all cohorts, 4,588 segments after 
pruning):
   
   | plan on the time predicate | segments |
   |---|---|
   | single `FILTER_RANGE_INDEX` under `FILTER_AND` | 3,449 |
   | `FILTER_OR` → 2× `FILTER_RANGE_INDEX` | 1,004 |
   | single `FILTER_FULL_SCAN` under `FILTER_AND` | 44 |
   | **`FILTER_OR` → 2× `FILTER_FULL_SCAN`** | **83** |
   | `FILTER_EMPTY` | 8 |
   
   The 83 segments account for essentially all of the 441M: ~2.7M docs × 2 
predicates each, i.e. a full pass over the column per range predicate. The 44 
unindexed segments whose scan sits directly under the AND cost almost nothing, 
because `AndDocIdSet` hands them the intersected inverted-index bitmap via 
`ScanBasedDocIdIterator.applyAnd`. The 83 get no candidate set because 
`OrDocIdSet` has no equivalent, and `AndDocIdSet#iterator()` calls `iterator()` 
on the OR child before it has computed anything.
   
   Two details that made this particularly sharp for us:
   
   - Per segment, a range predicate that can't match the segment's min/max 
becomes `EmptyFilterOperator` and is dropped from the OR, so the OR only 
survives on segments that overlap *both* windows.
   - Each window on its own never exposes the missing index (~100K entries), so 
this was invisible until both windows were combined in one query.
   
   **On the two proposed fixes**
   
   +1 to option 1 (push `applyAnd` down into composite doc-id sets). For our 
shape, #19350 as currently scoped would help only marginally: the only conjunct 
it can distribute is `device__os IN ('android','ios')`, which matches most 
rows. The selective part of the outer AND is the `OR(AND(name, flow_id, 
action_id), AND(name, flow_id))` block, which isn't a single-column EQ/IN and 
so wouldn't be pushed into the time-range branches.
   


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