[GLUTEN-12921][VL] Support full outer BroadcastNestedLoopJoin with guarded rewrite - #12922
[GLUTEN-12921][VL] Support full outer BroadcastNestedLoopJoin with guarded rewrite#12922WangGuangxin wants to merge 6 commits into
Conversation
|
Run Gluten Clickhouse CI on x86 |
2 similar comments
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
8 similar comments
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
2 similar comments
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
@zhztheplayer @zml1206 @philo-he please take a look at this when you are convenient |
There was a problem hiding this comment.
Could we consider using ExistenceJoin for the second branch instead of an outer join with a synthetic marker?
The second branch only needs to determine whether each row from the original broadcast side has any match. The current outer-join approach may emit every matching pair and then discard all of them through IsNull(markerAttr). For predicates with a high match rate or many-to-many matches, this can introduce substantial redundant processing.
ExistenceJoin is already supported by the Velox BNLJ path with BuildRight and maps to Velox’s kLeftSemiProject. It emits at most one row per streamed row and can stop searching after the first match. The rewrite could therefore be:
LeftOuter(L, R)
UNION ALL
Filter(!exists, ExistenceJoin(R, L))
followed by projecting NULL for the missing-side columns. This would also eliminate the synthetic marker while preserving the same full-outer-join semantics. Could we use this approach, or add a high-match-cardinality benchmark to compare the two implementations?
|
Run Gluten Clickhouse CI on x86 |
1 similar comment
|
Run Gluten Clickhouse CI on x86 |
|
Run Gluten Clickhouse CI on x86 |
|
@zhztheplayer @zml1206 @philo-he comments resolved. Please take another look |
What changes are proposed in this pull request?
This PR adds Velox support for
BroadcastNestedLoopJoinExecinFULL OUTER JOINcases by rewriting the Spark physical plan into a composition that Velox already supports, instead of falling back the originalfull outer BNLJ.
The rewrite transforms:
BroadcastNestedLoopJoinExec(..., FullOuter, ...)into:
Unionto combine both branchesThis avoids introducing native Velox full outer nested-loop join support while preserving full outer join semantics, including null-sensitive cases.
In addition, this PR adds a conservative size-based guard for the rewrite:
spark.gluten.sql.columnar.broadcastNestedLoopJoin.fullOuterRewriteThresholdOtherwise, Gluten keeps the original behavior and lets the plan fall back instead of forcing the rewrite on larger joins.
Why are the changes needed?
Velox does not support Spark's
FULL OUTERbroadcast nested loop join end-to-end today, so Gluten falls back for these queries.A direct native implementation is not a good short-term option because:
A rewrite-based solution is lower risk because it reuses operators already supported by Velox.
However, rewriting every full outer BNLJ into two joins can be more expensive than a single Spark row-based fallback join. To keep the feature safe, this PR only enables the rewrite for small, stats-known joins
and preserves fallback for larger or unknown-size cases.
How was this patch tested?
More UT
Config
A new config is introduced:
spark.gluten.sql.columnar.backend.velox.broadcastNLJ.fullOuterRewriteThresholdIt controls the maximum per-side logical plan size for enabling the full outer BNLJ rewrite.
Was this patch authored or co-authored using generative AI tooling?
Yes. Generated-by: GPT-5
Related issue: #12921