wirybeaver opened a new pull request, #2714:
URL: https://github.com/apache/datafusion-ballista/pull/2714

   ## Motivation
   
   A hot shuffle bucket can serialize most of a partitioned join on one task. 
This ports the skew-join AQE step: split its mapper outputs and replicate the 
matching bucket so the same join runs across smaller parallel tasks.
   
   ## Modification
   
   The opt-in rule runs after coalescing, computes each side's skew threshold 
once, and attaches paired mapper ranges to both input exchanges. Readers 
declare unknown partitioning, and stage completion uses the executable output 
width so expanded results are retained.
   
   Skew detection averages the middle pair for even partition counts, avoiding 
missed splits caused by using the upper middle value alone.
   
   ```text
   resolved shuffle statistics
     -> coalesce inputs when applicable
     -> detect skew and split mapper ranges
     -> pair shards / replicate the matching bucket
     -> aligned shuffle readers -> parallel join tasks
     -> repartition or collect every output partition
   ```
   
   The first version supports one partitioned join per stage. It excludes paths 
that need complete groups, fixed row limits, or ordered shuffle reads; partial 
aggregation before a reshuffle remains eligible. Dynamic-filter pushdown must 
be disabled. Configuration and its generated reference are included.
   
   ## Benchmarking Result
   
   Both are warm-cache experiments on one Apple M5 host (10 logical CPUs, 24 
GiB RAM), with two executors (4 slots and a 4 GiB memory pool each), 32 hash 
partitions, and one partition per task. In the split-only A/B comparisons, AQE 
is enabled; coalescing, dynamic-filter pushdown, and broadcast are disabled. 
The join algorithm is fixed within each comparison. **A disables split; B 
enables it.** Latency changes use `mean(B)/mean(A)-1` and 
`median(B)/median(A)-1`; negative values mean lower latency.
   
   ### Synthetic dataset
   
   **Workload:** 16M fact rows in 32 Parquet files; 90% share one hot key. A 
uniform control spreads 16M rows across 100,004 keys. The dimension has eight 
rows per key. The query joins on the key and groups counts and weighted sums by 
`key % 32`.
   
   ```sql
   SELECT d.bucket,
          COUNT(*) AS matched_rows,
          SUM(f.measure * d.weight) AS weighted_sum
   FROM fact f
   JOIN dim d ON f.join_key = d.join_key
   GROUP BY d.bucket
   ORDER BY d.bucket;
   ```
   
   **Parameters/process:** skew factor **5**, threshold **32 MiB**, advisory 
size **16 MiB**. After one warmup per arm, run **ABBAAB**, with three queries 
per visit. Every answer matches an independent row-ID oracle. Only the skewed B 
cases split, increasing join tasks **32 → 47**. The sort-merge baseline's hot 
sort spills; splitting avoids that spill.
   
   #### Per-visit results
   
   | Visit | Arm | Skew SMJ (s / M rows/s) | Skew hash (s / M rows/s) | Uniform 
SMJ (s / M rows/s) |
   | --- | --- | ---: | ---: | ---: |
   | 1 | A | 1.726 / 9.268 | 1.049 / 15.253 | 0.502 / 31.876 |
   | 2 | B | 0.426 / 37.601 | 0.333 / 48.195 | 0.509 / 31.471 |
   | 3 | B | 0.426 / 37.568 | 0.325 / 49.308 | 0.521 / 30.696 |
   | 4 | A | 1.754 / 9.122 | 1.038 / 15.410 | 0.506 / 31.624 |
   | 5 | A | 1.756 / 9.114 | 1.034 / 15.474 | 0.517 / 30.989 |
   | 6 | B | 0.446 / 35.934 | 0.318 / 50.370 | 0.515 / 31.080 |
   
   | Workload | Mean latency A → B (s) | Mean latency change | Median latency 
change |
   | --- | ---: | ---: | ---: |
   | Skew, sort-merge | 1.745 → 0.432 | -75.22% | -75.71% |
   | Skew, hash | 1.040 → 0.325 | -68.74% | -68.73% |
   | Uniform, sort-merge | 0.508 → 0.515 | +1.36% | +1.84% |
   
   Skewed-join latency improves **4.04× for sort-merge** and **3.20× for 
hash**; the uniform control has a small slowdown. These synthetic measurements 
predate the even-partition median correction; recorded revisions, scripts, and 
evidence are in the [synthetic benchmark 
gist](https://gist.github.com/wirybeaver/17d83998b6d10df852f15b637a196c86).
   
   ### TPC-DS SF100 — custom NULL-hotkey query
   
   **Workload:** unmodified reference-generated SF100 data (24 tables, 35.7 GB 
Parquet). A custom NULL-safe join matches `store_sales` to return groups from 
`store_returns`, then aggregates counts and weighted units by date bucket. The 
27.7M-row build side and NULL hot key produce 10.67B logical matches. [Full SQL 
and 
reproduction](https://gist.github.com/wirybeaver/8a8848c997d8e7f6b2f8dafa5ac135ad#file-query-sql).
   
   ```sql
   WITH return_buckets AS (
       SELECT sr_customer_sk, sr_return_amt, COUNT(*) AS return_events
       FROM store_returns
       WHERE sr_customer_sk IS NOT NULL
          OR (sr_customer_sk IS NULL AND sr_return_amt BETWEEN 0 AND 5.11)
       GROUP BY sr_customer_sk, sr_return_amt
   )
   SELECT s.ss_sold_date_sk % 8 AS bucket,
          COUNT(*) AS matched_rows,
          SUM(CAST(s.ss_quantity AS BIGINT) * r.return_events) AS weighted_units
   FROM store_sales s
   JOIN return_buckets r
     ON s.ss_customer_sk IS NOT DISTINCT FROM r.sr_customer_sk
   GROUP BY s.ss_sold_date_sk % 8
   ORDER BY bucket NULLS FIRST;
   ```
   
   **Parameters/process:** partitioned hash join with the ordinary **64 MiB** 
hash-build limit; skew factor **1.5**, threshold **8 MiB**, advisory size **8 
MiB**, small-partition factor **0.2**. Freeze the SQL/parameters, warm each arm 
once, then run **ABBA**. All answers exactly match an independent factored 
DuckDB oracle. Split expands the join stage **32 → 62** logical tasks.
   
   #### Per-visit results
   
   | Visit | Arm | Query latency (s) | Join tasks | Longest join task (s) |
   | --- | --- | ---: | ---: | ---: |
   | 1 | A | 82.368 | 32 | 79.924 |
   | 2 | B | 28.051 | 62 | 4.637 |
   | 3 | B | 28.622 | 62 | 7.213 |
   | 4 | A | 81.940 | 32 | 79.440 |
   
   #### Join-strategy comparison
   
   | Configuration | Measurement | Query latency (s) |
   | --- | --- | ---: |
   | Fixed partitioned hash, split disabled | Mean of 2 ABBA visits | 82.154 |
   | Automatic join selection, split disabled (broadcast selected) | Single 
reference run | 40.199 |
   | Fixed partitioned hash, split enabled | Mean of 2 ABBA visits | **28.337** 
|
   
   Split reduces mean latency by **65.51% (2.90× speedup)** against the fixed 
partitioned baseline; mean and median latency changes are both **-65.51%**. It 
also beats the automatic-join reference by **29.51% (1.42× speedup)**. The 
automatic-join result is a single measurement, separate from the repeated 
split-only ABBA comparison. These are warm, single-host custom-query results.
   
   Scripts and recorded results are in the [SF100 reproduction 
gist](https://gist.github.com/wirybeaver/8a8848c997d8e7f6b2f8dafa5ac135ad). 
`benchmarks/` remains unchanged against the PR base.
   


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