Ganesha S created SPARK-58611:
---------------------------------
Summary: Left anti stream-stream join
Key: SPARK-58611
URL: https://issues.apache.org/jira/browse/SPARK-58611
Project: Spark
Issue Type: Improvement
Components: Structured Streaming
Affects Versions: 4.3.0
Reporter: Ganesha S
Stream-stream join currently supports Inner, LeftOuter, RightOuter, FullOuter
and LeftSemi. LeftAnti is rejected at analysis time:
{noformat}
LeftAnti joins with a streaming DataFrame/Dataset on the right are not supported
{noformat}
This leaves the common "find left rows with no match on the right" pattern
without a native streaming implementation: impressions without clicks, orders
without shipments, sessions without conversion, alerts without acknowledgement.
Users work around it with NOT IN / NOT EXISTS rewrites or hand-written
transformWithState logic, both more expensive and easy to get subtly wrong.
LeftSemi was added under this umbrella in SPARK-32862; its complement was never
done. Note stream-static LEFT ANTI already works when only the left side is
streaming -- only a streaming right side is rejected -- so the gap is
specifically stream-stream.
h2. Approach
Unlike left semi, left anti cannot emit while joining. A semi match is
positively determined: the moment a left row matches, it can be emitted. "No
match exists" is only decidable once the watermark guarantees no future right
row can match. Left anti is therefore implemented on the eviction path, reusing
the existing left outer plumbing in \{{StreamingSymmetricHashJoinExec}}:
* nothing is emitted when a left row matches;
* at left-side state eviction, rows whose \{{matched}} flag is false are
emitted as bare left rows (left outer emits them joined with nulls instead);
* a matched left row stays in state carrying \{{matched = true}} so that it is
suppressed at eviction time, rather than being dropped from state early the way
left semi does;
* a left row that fails the pre-join filter can never match, so it is emitted
immediately without being added to state.
h2. Requirements
* *Watermark on the right side + time constraints are mandatory*, same contract
as left outer. Without them the engine can never conclude "no match".
* *Append output mode only.* Update mode would have to emit rows before the
watermark can rule out a future match, and such a row could be invalidated by a
later batch. This is the opposite of SPARK-56384, which allowed Update for
Inner/LeftSemi precisely because those have no early-firing concern.
* *No new state format version.* The \{{matched}} flag already persisted by
state format v2/v3/v4 is exactly the signal left anti needs, so existing
checkpoints need no migration. Format v1 is already rejected for non-inner
joins.
* *RightAnti is out of scope* -- it is not among the join types planned onto
this operator.
h2. Trade-off
Left anti buffers every left row until eviction, whereas left semi drops
matched left rows from state eagerly. This is inherent: a matched row must be
retained so that it can be _suppressed_ at eviction rather than emitted. State
size for left anti is therefore comparable to left outer, not to left semi.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]