Repository navigation
Conversation
A multi-column or correlated `NOT IN` plans as a null-aware LeftAnti hash join that JoinSelection never swapped, so the outer side was always the build side and held in memory even when the subquery was small. Let JoinSelection swap every null-aware LeftAnti into a RightAnti. A correlated null-aware RightAnti (more than one key or a join filter) now decides each outer (probe) row while streaming: it reuses the NULL candidate pairing of the correlated LeftAnti (scope maps, the element-wise mismatch check and the join filter), but marks the UNKNOWN probe rows of the current batch instead of build rows in the shared bitmap, and drops them from the anti join output. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #26213 +/- ##
==========================================
- Coverage 82.80% 82.80% -0.01%
==========================================
Files 1149 1149
Lines 452213 452325 +112
Branches 452213 452325 +112
==========================================
+ Hits 374473 374560 +87
- Misses 54945 54954 +9
- Partials 22795 22811 +16 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
Rationale for this change
A multi-column
(a, b) NOT IN (SELECT x, y ...), and a correlatedNOT INwith scope keys or a join filter, plans as a null-awareLeftAntihash join thatJoinSelectionnever swaps. The outer side is always the build side and is held in memory even when the subquery is tiny. With @comphead's repro (2M-row outer table, 100-row subquery,--memory-limit 64M), the scalarNOT INruns but the multi-column one fails withResources exhausted ... HashJoinInput.What changes are included in this PR?
JoinSelectionmay swap every null-awareLeftAntiinto aRightAntithat builds on the subquery side, not only the single-key, unfiltered one. The swap keepsnull_aware_value_keysand the key order, and the join filter is swapped with the inputs. A null-awareLeftMarkis not swapped.NullAwareMode::RightAntinow carries{ correlated, value_keys }, andNullAwareMode::try_newaccepts a multi-key or filtered null-awareRightAnti(stillCollectLeftonly).LeftAnti: NULL-valued build rows against all probe rows, and NULL-valued probe rows against all build rows, narrowed by the scope maps, then the element-wise "no definite mismatch" check (retain_value_mismatch_free) and the join filter. The UNKNOWN rows are dropped from the output.stream.rstake anUnknownMarksthat says which side of a candidate pair is marked: build rows in the shared null-indices bitmap (LeftAnti/LeftMark, unchanged) or rows of the current probe batch (RightAnti). A correlatedRightAntibuilds the scope maps but not the per-build-row bitmap, and has no state shared across probe partitions.The build side is not deduplicated, since that does not hold with a join filter. The NULL pairing still costs (NULL-valued rows) x (rows on the other side) without scope keys; #26088 narrows that.
What is the testing strategy for this PR?
exec.rsunit tests:test_null_aware_multi_column_right_antiruns a two-columnNOT INasLeftAntiand as the swappedRightAntiacross batch sizes, including an all-NULL-valued subquery (empty full-key map) and an empty subquery.test_null_aware_left_anti_multi_column_scopealso checks the swappedRightAnti.RightAnti.join_selection.rs: a filtered null-awareLeftAntiswaps, and the filter's sides are swapped too. A multi-key null-awareLeftAntiswaps withvalue_keys1 and 2 kept.null_aware_multi_column.sltaddsdatafusion.optimizer.join_reordering=true,falseto itsconfigMatrix, so every result is checked with theRightAntiand theLeftAntiplan. Plans innull_aware_anti_join.sltandsubquery_projection.sltare updated; no query result changed.null_aware_join_fuzz.rs: half of the seeds use a much smaller subquery table, and the test counts the null-aware plans it ran and asserts it reachedLeftAnti,RightAntiand a correlated (multi-key or filtered)RightAnti. A run had 228 / 1692 / 1602 of them. Disabling the mismatch check on theRightAntipath makes the fuzz fail.memory_limit/null_aware_not_in.rs: a small-scale version of the repro above. A 200k-row outer table against a 101-row subquery (one NULL-valued row) under a 2 MB limit gives the right count with join reordering on, and runs out of memory inHashJoinInputwith it off. The test fails without theJoinSelectionchange.null_aware_joinbenchmark, Q10 / Q11 / Q12 (multi-column, which now plan asRightAnti), mean of 3 interleaved runs, in ms:They take about the same time because the NULL pairs are the same. The gain is that the build side is now the subquery, which the memory-limit test covers. The other queries of the suite stay within run-to-run noise.
Are there any user-facing changes?
Multi-column and correlated
NOT INsubqueries can now plan as a null-awareRightAntihash join built on the subquery side, soEXPLAINoutput changes and memory use drops when the subquery is the smaller side. No public API changes.