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

   ### What changes were proposed in this pull request?
   `SortMergeAsOfJoinExec` gets a cursor scan. For a backward or forward join, 
it walks the right side once and keeps only the best right row so far. It does 
not buffer the right-side group.
   
   The operator checks once per query whether the join can use it. It needs all 
of these:
   - A backward or forward join, not nearest.
   - No residual join condition left after pushdown.
   - An as-of condition that compares the two sort keys, one on each side, of 
the same type.
   - A scalar key type, not STRUCT or ARRAY.
   
   Every other join keeps the buffered scan. The new internal config 
`spark.sql.join.sortMergeAsOfJoin.cursorScan.enabled` (default `true`) turns 
the cursor scan off. The new metric `number of cursor scan tasks` counts the 
tasks that use it.
   
   ### Why are the changes needed?
   The buffered scan saves each right-side group, then rereads it from the 
start for every left row. So a group with N left rows and M right rows can cost 
up to N x M condition checks, and a large group spills to disk. Both sides are 
already sorted by the as-of key, so the match only moves forward as the left 
key grows. The cursor scan costs about N + M checks.
   
   ```sql
   SELECT * FROM trades t LEFT ASOF JOIN quotes q
     MATCH_CONDITION (t.ts >= q.ts) ON t.sym = q.sym
   ```
   
   `AsOfJoinBenchmark`, 10,000 rows on each side, best time on my machine:
   - 100 groups: 40 ms -> 34 ms
   - 10 groups: 121 ms -> 30 ms
   - No equi-key: 885 ms -> 18 ms
   
   ### Does this PR introduce _any_ user-facing change?
   No. Query results stay the same. The join node in the SQL UI shows one new 
metric.
   
   ### How was this patch tested?
   New `SortMergeAsOfJoinScannerSuite` tests check that the cursor scan checks 
the as-of condition only twice per left row, and which conditions 
`supportsCursorScan` accepts and rejects.
   
   New `AsOfJoinSortMergeSQLSuite` tests run each query with the cursor scan 
off and on, and check both against the same expected rows. With a spill 
threshold of one row, the cursor run must spill nothing. They cover ties, NULL 
keys, scalar types, NaN and -0.0, expression sides, STRING against INT, mixed 
TIMESTAMP_LTZ and TIMESTAMP_NTZ across a DST gap, and the joins that must keep 
the buffered scan.
   
   The existing DataFrame `joinAsOf` tests in `SortMergeAsOfJoinSuite` now run 
with the cursor scan on. Its spill tests turn the cursor scan off, since it 
never buffers.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   Generated-by: Claude Code
   


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