Repository navigation
Conversation
Add PipelinedShuffleWriterExec, PipelinedShuffleReaderExec and the per-executor PipelinedRegistry that connects them, with their protobuf messages, serde, the FetchPipelinedPartition Flight action and the ballista.execution.mode and ballista.pipelined.* settings. Nothing plans these operators yet. The scheduler rejects jobs with ballista.execution.mode = pipelined, and executors answer pipelined fetches with unimplemented, until later changes wire them in.
…pers - Lease pooled connections for the life of a remote pipelined stream, and build unpooled clients with new_ballista_client, as the shuffle reader does. Encode Flight tickets with encode_protobuf. - Compute the writer's routing once, drop unused builder and getter methods, and let JoinSet's drop abort the remaining inputs. - Track unclaimed queues with a counter instead of rescanning every partition on each open, share one bounded set for closed jobs and stages, and stop cloning per batch in the queue stream. - Keep a broadcast reader whole when restricting partitions, forget a consumer's waiting slot when it times out, and document the minimums of the pipelined settings.
andygrove
force-pushed
the
pipelined-exchange
branch
from
October 27, 2026 12:08
15e27d3 to
e6272fa
Compare
Member
Author
Each remote stream checked out its own connection, so a wide pipelined job opened one per producer and consumer task pair and overflowed the executors' listen queues. A reader partition now multiplexes its streams to an executor onto one connection, capped at 64 streams per connection.
tonic's `serve` binds through the standard library, which listens with a backlog of 128. A pipelined job connects from every reader task on every executor when it starts, so an executor can get thousands of connections within a second and overflow both its accept and SYN queues. Overflowed connections wait for a SYN retransmit, and once the kernel falls back to SYN cookies, a client whose first segments were dropped is reset as soon as it sends more. The reader then sees `ConnectionReset` on a stream it never opened, with nothing logged on either side. Bind the Flight server through `create_grpc_server_incoming`, which the executor's gRPC server already uses, and have that helper listen with `GrpcServerConfig::listen_backlog`, 4096 by default. The kernel caps the value at `net.core.somaxconn`, which is 4096 on Linux 5.4 and later.
… server answered A wide pipelined job opens its remote streams in one burst, and a connection the producer's executor could not accept in time can be reset by the kernel as the reader starts sending. The reader saw that as a `ConnectionReset` on a stream it had not opened, and since pipelined jobs do not retry tasks, one such reset failed the job. `fetch_pipelined_partition` now reports a failure of the connection itself, before the server answered, as `GrpcConnectionError`, and keeps `GrpcActionError` for anything the server answered. The reader retries the former on a new connection, `io_retries_times` times with `io_retry_wait_time_ms` between attempts, the settings the blocking shuffle reader already uses. A failed connection is dropped from its slot, so the other streams of the partition that share it connect again rather than fail on it. An error the server answered, including the producer's own failure, is still final: the producer may already have handed the partition to the failed stream. Tests cover a stream open that is reset on its first connection and served on its second, and a server-side failure that is not retried.
A pipelined reader opened a connection per reader partition to each producer executor, so a job opened executors x tasks x stage boundaries connections to every executor at once, enough to overflow its listen queue however large the queue is. The executor's client pool now keeps one shared connection per (host, port, config), which the streams of every task on the executor multiplex over, so an executor holds one connection to each other executor. A connection that fails before the server answers is discarded from the pool, and the open is retried on a new one. Sharing is safe because of how the windows are set. A stream nobody is reading holds up to its stream window of data on the connection, and if stalled streams could use up the connection window they would starve the streams something is waiting on. The shared connection therefore advertises the largest connection window HTTP/2 allows and a small stream window, `ballista.pipelined.stream_window_bytes` (512 KiB by default), so stalled streams hold at most one window each and a connection carries over 4000 streams before any could starve the rest. The executor's server is explicitly left without a limit on concurrent streams, since a stream the client had to queue behind a limit would leave its producer blocked on a full queue. `BallistaClientPool` gains a `shared` method with a default implementation, so existing pools keep compiling.
andygrove
marked this pull request as draft
October 27, 2026 19:39
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?
Part of #2652. This is the second of a series of smaller PRs split out of #2657 (pipelined execution). It is independent of #2669.
Rationale for this change
In pipelined execution, every task of a job runs at once and stages stream record batches to each other instead of exchanging shuffle files. This PR adds the exchange itself: the operators at either end of a pipelined stage boundary, and the per-executor registry that connects them. Nothing plans these operators yet, so this PR can't change how existing jobs run.
#2657 describes the whole feature, its design notes and SF1000 results.
What changes are included in this PR?
execution_plans/pipelined/PipelinedRegistryholds a bounded queue for each output partition of each running producer task, keyed by job, stage and task.PipelinedShuffleWriterExecis a stage root. It drives every input partition and routes batches into the queues: hash-partitioned, passthrough or broadcast.ballista.pipelined.registration_timeout_seconds.PipelinedShuffleReaderExecis a stage leaf. It drains all of its producers concurrently: directly from the registry when the producer is on the same executor, and over Flight otherwise. Reading concurrently is what keeps bounded queues free of deadlock for tree-shaped plans.tests.rsruns writers against readers in-process.PipelinedShuffleWriterExecNode,PipelinedShuffleReaderExecNode,PipelinedPartitionLocation, and theFetchPipelinedPartitionaction, plusBallistaClient::fetch_pipelined_partition. The Flight proxy inserving.rsforwards the new action.ballista.execution.mode(blockingorpipelined),ballista.pipelined.queue_capacityandballista.pipelined.registration_timeout_seconds. They are documented in a new section of the config reference.ballista.execution.mode = pipelined("not supported yet").FetchPipelinedPartitionwithunimplemented.How are these changes tested?
Are there any user-facing changes?
There are three new session settings, documented in the config reference. Setting
ballista.execution.mode = pipelinedcurrently makes the job fail with a "not supported yet" error. The default behavior is unchanged.