xumingming opened a new pull request, #57162:
URL: https://github.com/apache/spark/pull/57162

   
   ### What changes were proposed in this pull request?
   
   For join types that preserve streamed rows (LeftAnti, LeftOuter, RightOuter 
and ExistenceJoin), ON-clause conjuncts that reference only the streamed side 
were previously evaluated once per matched (streamed, buffered) pair inside the 
hash-bucket or merge walk. When such predicates contain expensive UDFs and are 
false for most rows, this wastes significant CPU.
   
   The optimization is based on the observation that, for these join types, if 
a streamed-only predicate S is FALSE/NULL for a streamed row, the full join 
condition S AND other(S,B) is FALSE/NULL for ANY buffered row B. Therefore the 
streamed row outcome is already determined before any probe: it is emitted for 
outer/existence joins and emitted for left anti joins when no match exists.
   
   This change splits the join condition into streamed-only and mixed-side 
parts. The streamed-only part is evaluated once per streamed row before 
entering the match loop; only the mixed-side remainder is evaluated inside the 
loop. When the residual condition is entirely streamed-side-only, the inner 
loop degenerates to a pure existence check.
   
   Guarded by the new SQL config
   spark.sql.join.splitStreamedSideJoinCondition (default false).
   
   ### Why are the changes needed?
   
   For hash and sort-merge joins that preserve streamed-side rows (LeftAnti, 
LeftOuter, RightOuter and ExistenceJoin), ON-clause conjuncts that reference 
only the streamed side are currently evaluated repeatedly inside the match 
loop, once per candidate buffered row. When such predicates involve expensive 
UDFs or computations and are false for most streamed rows, this wastes CPU 
because the join result for those streamed rows is already determined before 
any buffered row is inspected.
   
   Scenario 1: Expensive streamed-side filter in a left outer join.
   
   ```
   SELECT /*+ BROADCAST(t2) */ t1.*
   FROM t1 LEFT JOIN t2
     ON t1.id = t2.id AND expensive_udf(t1.a) = 'ok'
   ```
   
   If expensive_udf(t1.a) returns a value other than 'ok' for most rows of t1, 
the join condition can never be satisfied for those rows, yet the UDF is 
invoked for every matching row in t2.
   
   Scenario 2: Left anti join with a streamed-side predicate.
   
   ```
   SELECT *
   FROM t1 LEFT ANTI JOIN t2
     ON t1.id = t2.id AND t1.category IN (1, 3, 5)
   ```
   Rows from t1 whose category is not in the allowed set are guaranteed to be 
emitted (they have no match by definition), but the current implementation 
still probes t2 for each of them.
   
   
   ### Does this PR introduce _any_ user-facing change?
   
   No.
   
   
   ### How was this patch tested?
   
   Unit test.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   No.


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