comphead commented on PR #2434:
URL: 
https://github.com/apache/datafusion-ballista/pull/2434#issuecomment-5641130802

   Reviewed this against the PR head (`1340cc97`). Baseline is green for me: 
416/416 in `cargo test -p ballista-scheduler --lib`.
   
   First, the part you asked people to push on — **the one-sided shuffle safety 
argument holds.** I traced it rather than taking it on faith: 
`find_runnable_exchanges` only pushes `ExchangeExec` nodes as stages and stops 
descending at a resolved one, so in the staged state the probe subtree creates 
no stage, and the join sits above the exchange rather than inside its subtree. 
`Repartitioned` is reachable only via `to_partitioned()` or 
`inputs_already_partitioned()`, and staging sets neither. `to_broadcast` 
sharing `stage_id`/`shuffle_partitions` checks out too.
   
   Two things I'd want changed before merge.
   
   ### 1. Staging fires for join types that can never broadcast
   
   The `StageBuildSide` guard in `to_actual_join` checks the config, 
`!null_aware`, the exchange guard and `should_stage_build_side` — but not 
`collect_left_broadcast_safe(build_side_join_type)`, which is already computed 
~80 lines above.
   
   `partition_mode` is `CollectLeft` only when `under_threshold && 
collect_left_broadcast_safe(..)`. That second term is a pure function of the 
join type, so no measurement can change it. For a non-broadcastable type the 
second pass can't reach `CollectLeft`, hits the (now failing) exchange guard, 
and falls to `Repartition` — one serialised stage boundary that never had a 
chance of changing the decision.
   
   I confirmed it with a throwaway test on the q8 shape:
   
   ```
   join_type=Full     collect_left_broadcast_safe=false  action=StageBuildSide
   join_type=Left     collect_left_broadcast_safe=false  action=StageBuildSide
   join_type=LeftSemi collect_left_broadcast_safe=false  action=StageBuildSide
   join_type=LeftAnti collect_left_broadcast_safe=false  action=StageBuildSide
   ```
   
   `Full` is affected unconditionally (`Full.swap() == Full`); 
`Left`/`LeftSemi`/`LeftAnti`/`LeftMark` whenever the build side is already the 
left input.
   
   One line fixes it:
   
   ```rust
    (JoinInputState::Unknown, PartitionMode::Partitioned)
        if bc.stage_build_side_enabled()
            && !self.null_aware
   +        // Measuring the build side can only change the outcome if
   +        // CollectLeft is reachable at all, which is a property of the
   +        // (post-swap) join type alone.
   +        && collect_left_broadcast_safe(build_side_join_type)
            && !self.left.is::<ExchangeExec>()
   ```
   
   Applied locally: **415/415 pass, no changes needed to any test in this PR** 
— they all use `Inner`.
   
   **On the +3.1s band:** this is a candidate partial explanation, not a 
diagnosis. q16 (`NOT IN`, which `not_in_subquery_rewrite` turns into a plain 
anti join) and q22 (`NOT EXISTS`) are both anti joins and both sit in the band. 
It doesn't explain q5/q7/q12, which are plain inner joins — those look more 
like the serialisation cost you already acknowledge. Re-running the six with 
`stage_build_side=false` per query would separate the two.
   
   ### 2. Nothing tests the multi-pass path
   
   All the new coverage is single-pass: `should_stage_build_side` in isolation, 
one `to_actual_join` call, `exchange_on`. The mechanism this PR is actually 
about — stage → resolve with measured stats → re-decide → `CollectLeft` — isn't 
exercised, and neither is the `EnsureRequirements`/`CoalescePartitionsExec` 
behaviour you say you verified by hand.
   
   The harness for exactly this already exists in `aqe/test/join_selection.rs` 
(`AdaptivePlanner` + `mock_partitions_with_statistics` + `assert_plan!`). Two 
`assert_plan!` tests would cover it: the q8 shape resolving to a broadcast 
`CollectLeft`, and the fallthrough where the measured size is still too large 
and `exchange_on` reuse yields one exchange per side rather than a nested pair.
   
   Worth noting `test_left_join_not_collected_left` (the #1055 guard) passes 
today only because its MemTable stats don't match the staging shape — nothing 
currently pins "a Left join must not stage."
   
   ### Smaller
   
   - I'd split the `broadcast_join_threshold_rows` semantics change out. It's 
well argued and clearly flagged, but by your own account it isn't what fixes 
q8, and it replaces a test that asserted the old behaviour deliberately. 
Separate PRs means either half can be reverted alone if the SF1000 numbers move.
   - The exchange guard only inspects immediate children, so a build side 
shaped like `FilterExec(ExchangeExec(..))` gets a second boundary stacked on 
the first. Edge case.
   - `CoalescePartitionsRule`'s docs assume both join legs read upstream 
shuffle output, and attach the same `CoalescePlan` to every leaf to keep 
buckets aligned. In the staged state only one leg is an exchange. It's off by 
default so the risk is low, but does the leaf grouping handle a one-exchange 
join?
   
   ### On defaulting to `true`
   
   Fix #1 first, then yes. As it stands, default-on ships a path that's 
provably unprofitable for `Full` and left-sided semi/anti joins, which is a bad 
trade for queries that get nothing back. After the guard I'd have no objection.
   
   Benchmark methodology is good — real cluster, row counts cross-checked, 
regressions disclosed rather than smoothed. Two caveats worth stating inline: 
one iteration per query, and the current totals include wasted stages on any 
anti/outer join matching the shape, so the post-fix number should come in a 
little under 696.09s.
   
   I did not check what happens to a resolved exchange's `shuffle_partitions` 
when the executor holding that shuffle is lost. Looks pre-existing for all 
exchanges rather than something this PR introduces, but staging makes one more 
stage's output load-bearing.
   


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