jiangxt2 commented on PR #57336:
URL: https://github.com/apache/spark/pull/57336#issuecomment-5011071932

   ## Context
   
   We run a telecom data platform where event tables reach tens-of-billions of
   rows. A common pattern is filtering events by pre-defined user segments
   (stored as `(group_id, user_id)` pairs with millions of members). At this
   scale, broadcast hash join is infeasible and every query triggers a
   SortMergeJoin.
   
   `bitmap_contains` pre-computes segments as bitmap tables in ETL, then
   replaces the shuffle join with a constant-time bit check:
   
   ```sql
   -- ETL: once
   CREATE TABLE seg_bitmaps AS
   SELECT group_id, bitmap_bucket_number(user_id) AS bucket,
          bitmap_construct_agg(bitmap_bit_position(user_id)) AS bm
   FROM user_segments GROUP BY group_id, bitmap_bucket_number(user_id);
   
   -- Query: bit check instead of shuffle join
   SELECT e.* FROM events e
   JOIN seg_bitmaps s
     ON bitmap_bucket_number(e.user_id) = s.bucket AND s.group_id = 100
   WHERE bitmap_contains(s.bm, bitmap_bit_position(e.user_id));
   ```
   
   ## Bloom Filter vs bitmap_contains
   
   Both are join pre-filtering techniques, but they differ fundamentally:
   
   | | Runtime Bloom Filter | bitmap_contains |
   |---|---|---|
   | **Mechanism** | Probabilistic: xxhash64 × k hashes → 
`BloomFilterImpl.probe`. Has false positives | Deterministic: single `bm[pos/8] 
& (1 << pos%8)`. Zero false positives |
   | **Trigger** | Automatic via `InjectRuntimeFilter`. Requires ALL of: (1) 
`isLikelySelective` filter on creation side with no Aggregate in the 
Filter→Scan chain; (2) probe side scan ≥ `applicationSideScanSizeThreshold` 
(default 10GB); (3) creation side ≤ `creationSideThreshold` (default 10MB — 
almost never met for million-row segments) | Manual: (1) ETL 
`bitmap_construct_agg`; (2) `bitmap_contains` in WHERE. No trigger restrictions 
|
   | **Scaling** | Probe cost grows with creation-side size (larger bloom 
filter → more memory pressure, higher false-positive rate → more rows pass 
through to join) | O(1) per check. Bitmap table fixed at ~6MB regardless of 
segment cardinality |
   | **Best for** | Small creation side (≤1M rows), one-off queries | Large 
segments (≥5M rows), repeated queries, ETL-precomputed bitmaps |
   | **Build cost** | Hidden in every query (cannot be separated) | Paid once 
in ETL, zero at query time |
   
   The key difference: bloom filter degrades as the creation side grows —
   more rows to hash, larger filter, higher false-positive rate. Bitmap
   query time is determined by domain size, not segment size. For large
   segments (typical in production cohort filtering), this structural
   difference matters.
   
   Currently `bitmap_contains` uses `StaticInvoke` for evaluation (similar to
   other bitmap functions), which adds a minor per-row overhead. A follow-up
   could add `doGenCode()` for inline bitwise codegen — analogous to how
   `BloomFilterMightContain` generates direct `mightContainLong()` calls inside
   `WholeStageCodegen`. This is a small optimization (~30 lines) that would
   give `bitmap_contains` the same codegen treatment bloom filter already has.
   
   Cross-engine: ClickHouse, Doris, and StarRocks all provide this function.
   Hive community has requested it (HIVE-27372). Uses Spark's existing
   flat-bitmap convention without external dependencies.
   
   Feedback welcome.
   


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