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

   <!--
   Thanks for sending a pull request!  Here are some tips for you:
     1. If this is your first time, please read our contributor guidelines: 
https://spark.apache.org/contributing.html
     2. Ensure you have added or run the appropriate tests for your PR: 
https://spark.apache.org/developer-tools.html
     3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., 
'[WIP][SPARK-XXXX] Your PR title ...'.
     4. Be sure to keep the PR description updated to reflect all changes.
     5. Please write your PR title to summarize what this PR proposes.
     6. If possible, provide a concise example to reproduce the issue for a 
faster review.
     7. If you want to add a new configuration, please read the guideline first 
for naming configurations in
        
'core/src/main/scala/org/apache/spark/internal/config/ConfigEntry.scala'.
     8. If you want to add or modify an error type or message, please read the 
guideline first in
        'common/utils/src/main/resources/error/README.md'.
   -->
   
   ### What changes were proposed in this pull request?
   <!--
   Please clarify what changes you are proposing. The purpose of this section 
is to outline the changes and how this PR fixes the issue.
   If possible, please consider writing useful notes for better and faster 
reviews in your PR. See the examples below.
     1. If you refactor some codes with changing classes, showing the class 
hierarchy will help reviewers.
     2. If you fix some SQL features, you can provide some references of other 
DBMSes.
     3. If there is design documentation, please add the link.
     4. If there is a discussion in the mailing list, please add the link.
   -->
   
   This is **part 4** of a multi-PR effort to add *streaming shuffle* to Spark 
— a push-based shuffle used by Real-Time Mode (RTM) structured streaming, where 
writer tasks push records directly to reader tasks over the network instead of 
writing map output to disk for readers to pull.
   
   This PR adds the **writer (push) side**:
   
   - **`StreamingShuffleWriter`** — a `ShuffleWriter` that pushes serialized 
records to the downstream readers over Netty instead of writing shuffle files 
to disk.
   - **`StreamingShuffleServerHandler`** — the writer-side Netty `RpcHandler` 
that accepts reader connections and processes the messages readers send back.
   - **`StreamingShuffleManager.getWriter`** — now returns a 
`StreamingShuffleWriter` (it was a stub that threw 
`UnsupportedOperationException` in part 3).
   - **`TransportServer.getPooledByteBufAllocator`** — a small accessor so the 
writer can allocate pooled send buffers from the server's allocator.
   - Three writer configs and two structured-logging keys (see below).
   
   #### How the writer and reader talk to each other
   
   Each writer task starts its own Netty server. Readers connect to it, and the 
two exchange four message types (all defined in part 1). The exchange for one 
writer <-> one reader looks like this:
   
   ```
     Reader                                   Writer (this PR)
       |                                          |
       |  --- CreditControlMessage ----------->   |   reader connects; writer 
now knows
       |                                          |   which network channel 
maps to this reader
       |                                          |
       |  <-------------- DataMessage (seq=0) ---  |   writer pushes batched 
rows
       |  <-------------- DataMessage (seq=1) ---  |   (each message carries a 
sequence
       |  <-------------- DataMessage (seq=2) ---  |    number and an optional 
CRC32C checksum)
       |                    ...                    |
       |  <----- TerminationControlMessage ------  |   writer has sent 
everything (seq=N)
       |                                          |
       |  --- TerminationAckMessage (seq=N) --->   |   reader confirms it 
received up to seq=N
       |                                          |
   ```
   
   Key points of the protocol, from the writer's side:
   
   1. **Connection setup.** A reader opens a connection and sends a 
`CreditControlMessage`. Until that arrives the writer does not know which 
channel belongs to which reader, so it queues outgoing messages for that reader 
and flushes them once the reader is known.
   2. **Data push.** The writer partitions each record with the shuffle 
dependency's partitioner, serializes rows into a pooled buffer, and sends the 
buffer as a `DataMessage` once it fills up 
(`spark.shuffle.streaming.networkBufferSize`, default 32 KB) or after a maximum 
buffering interval (`spark.shuffle.streaming.networkBufferMaxWaitTimeMs`, 
default 50 ms). Every message this writer sends to a given reader carries a 
**monotonically increasing sequence number**, so the reader can detect a lost 
or out-of-order message.
   3. **Integrity check (optional).** When 
`spark.shuffle.streaming.checksum.enabled` is on, the writer embeds a CRC32C 
over the buffer that the reader re-computes and compares.
   4. **Back-pressure.** In-flight buffer memory is bounded 
(`spark.shuffle.streaming.writerMaxMemory`, default 32 MB) by an off-heap 
`MemoryConsumer` plus a byte semaphore; when the limit is reached the writer 
blocks the upstream iterator until buffers are freed by send completions.
   5. **Termination.** After the last record, the writer sends each reader a 
`TerminationControlMessage` carrying the final sequence number, then waits for 
every reader to reply with a `TerminationAckMessage`. On each ack the writer 
checks that the reader's last-seen sequence number matches what it sent (a 
mismatch fails the task), and only returns from `write()` once all readers have 
acknowledged.
   
   Errors that occur on Netty threads (e.g. a failed write) are recorded via 
`ErrorNotifier` (part 3.5) and re-thrown on the task thread at a safe point. 
All resources (the server, channels, pooled buffers, reserved memory) are 
released from a `TaskContext` completion listener, so they are cleaned up on 
both success and failure.
   
   New configs (all `internal`, default on the safe side):
   
   | Config | Default | Purpose |
   |---|---|---|
   | `spark.shuffle.streaming.networkBufferSize` | 32 KB | target size of each 
pushed buffer |
   | `spark.shuffle.streaming.networkBufferMaxWaitTimeMs` | 50 ms | max time a 
partial buffer is held before flushing |
   | `spark.shuffle.streaming.writerMaxMemory` | 32 MB | best-effort cap on the 
writer's in-flight buffer memory |
   
   New log keys: `NUM_SHUFFLE_READERS`, `SHUFFLE_READER_ID`.
   
   The full PR stack:
   
   - **Part 1** (SPARK-56674, *merged*) - streaming shuffle wire protocol (the 
four Netty message types above).
   - **Part 2** (SPARK-56962, *merged*) - `StreamingShuffleOutputTracker` 
(driver-side writer-location coordination).
   - **Part 3** (SPARK-57141, *merged*) - shuffle-manager layer 
(`StreamingShuffleManager` + `MultiShuffleManager`).
   - **Part 3.5** (SPARK-57337, *merged*) - shared transport + error plumbing 
(`ErrorNotifier`, `TransportClient.send(ByteBuf)`, checksum config).
   - **Part 4** (*this PR*) - `StreamingShuffleWriter` + server-side Netty 
handler (push path).
   - **Part 5** - `StreamingShuffleReader` + client-side Netty handler (pull 
path).
   - **Part 6** - register streaming shuffles with the tracker in 
`DAGScheduler` (activation).
   - **Part 7** - end-to-end `StreamingShuffleSuite`.
   - **Part 8** - documentation.
   
   This PR depends only on the merged parts (1 through 3.5) and is independent 
of the reader PR (part 5); the two can be reviewed in parallel and merged in 
either order.
   
   ### Why are the changes needed?
   <!--
   Please clarify why the changes are needed. For instance,
     1. If you propose a new API, clarify the use case for a new API.
     2. If you fix a bug, you can clarify why it is a bug.
   -->
   
   Real-Time Mode / low-latency continuous queries need shuffle data to flow 
continuously between stages. The default sort shuffle (write map output to 
disk, then have reducers pull it) adds latency that is unacceptable for these 
workloads. Streaming shuffle instead pushes records directly from writer tasks 
to reader tasks. This PR implements the push (writer) half — the 
previously-stubbed `StreamingShuffleManager.getWriter` — including the per-task 
transport server, the credit-based connection setup, sequence-numbered data 
push, memory back-pressure, and the optional end-to-end checksum.
   
   ### Does this PR introduce _any_ user-facing change?
   <!--
   Note that it means *any* user-facing change including all aspects such as 
new features, bug fixes, or other behavior changes. Documentation-only updates 
are not considered user-facing changes.
   
   If yes, please clarify the previous behavior and the change this PR proposes 
- provide the console output, description and/or an example to show the 
behavior difference if possible.
   If possible, please also clarify if this is a user-facing change compared to 
the released Spark versions or within the unreleased branches such as master.
   If no, write 'No'.
   -->
   
   No. The streaming shuffle managers are opt-in via `spark.shuffle.manager` 
and are not the default, and the feature is not usable end-to-end until the 
reader (part 5) and activation (part 6) PRs land; the new configs therefore 
have no effect on the default sort-shuffle path. The added configs 
(`spark.shuffle.streaming.networkBufferSize`, 
`spark.shuffle.streaming.networkBufferMaxWaitTimeMs`, 
`spark.shuffle.streaming.writerMaxMemory`) take effect only once a streaming 
shuffle is in use.
   
   ### How was this patch tested?
   <!--
   If tests were added, say they were added here. Please make sure to add some 
test cases that check the changes thoroughly including negative and positive 
cases if possible.
   If it was tested in a way different from regular unit tests, please clarify 
how you tested step by step, ideally copy and paste-able, so that other 
reviewers can test and check, and descendants can verify in the future.
   If tests were not added, please describe why they were not added and/or why 
it was difficult to add.
   If benchmark tests were added, please run the benchmarks in GitHub Actions 
for the consistent environment, and the instructions could accord to: 
https://spark.apache.org/developer-tools.html#github-workflow-benchmarks.
   -->
   
   The writer is exercised end-to-end by `StreamingShuffleSuite` in the tests 
PR of this stack (part 7): writer<->reader data transfer, the credit-control / 
termination handshake, sequence-number validation, checksum verification, 
memory back-pressure, and background-thread error propagation.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   <!--
   If generative AI tooling has been used in the process of authoring this 
patch, please include the
   phrase: 'Generated-by: ' followed by the name of the tool and its version.
   If no, write 'No'.
   Please refer to the [ASF Generative Tooling 
Guidance](https://www.apache.org/legal/generative-tooling.html) for details.
   -->
   
   Co-authored with Claude Code (Claude Opus 4.8)


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