Lavkesh/network shuffle experiments - #16
Draft
ologlogn wants to merge 6 commits into
Draft
Conversation
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.
optimize_shuffle_partitioning_ratio
Workload Guide
What the ratio controls
At each shuffle boundary, the planner applies a clamp formula to determine how many tasks
the downstream stage should have:
clamp(cardinality, p, ceil(p * ratio))p(enabling optimized 1:1 mapping)p * ratio(limiting N×M fanout)ptasks → fully optimized every boundary2ptasks → moderate parallelism with controlled fanoutcardinality.min(ceil(p * ratio))p— useful for reducing parallelism on light workloadsWhere
pis theoutput_partitionsof the NetworkShuffleExec at the boundary (determinedby
target_partitionssession config), andcardinalityis the inherited task count fromupstream stages, adjusted by cardinality-effect factors from reducing operations.
The
use_optimizedflag onNetworkShuffleExecis set per-boundary whentask_count == p.When optimized, each consumer task reads exactly one partition stream (1:1 mapping) instead
of reading all N upstream streams and filtering by hash.
Ratio selection by workload type
ratio=1 (default, fully optimized everywhere)
Best for:
target_partitionspper boundary,preventing compounding fanout across many stages
What it does: every shuffle boundary becomes optimized (1:1 task-to-partition mapping).
Stream counts stay constant at
pacross all stages regardless of source cardinality.ratio=2–3 (moderate, recommended general-purpose default)
Best for:
are cheaper because aggregation has already reduced the data volume
What it does: source and early stages can run at up to 2–3× the partition count, preserving
parallelism for heavy compute. Once a cardinality-reducing operation appears (partial
aggregate, filter with high selectivity), subsequent stages naturally inherit smaller
task counts, making the high ratio less impactful downstream.
ratio=4+ (high parallelism)
Best for:
Avoid when:
partitions, each boundary has 60 streams. Across 5 stages this means each worker
simultaneously maintains many gRPC connections, increasing memory pressure proportional
to
record_batch_buffer_size × connections.When ratio does not help: skewed data
The ratio controls how many consumers drain upstream tasks. It does not control how evenly
data is distributed across those upstream tasks.
If one upstream task processes 167× more data than another (as seen in EventStore queries
over 6-month time ranges, where certain months have 27M events and others have near-zero),
every consumer stage will exhibit HOL (head-of-line) blocking — consumers finish their light
tasks quickly but then sit idle waiting for the single heavy upstream task to complete.
This manifests in plans as:
network_latency_sumon NetworkShuffleExec consumers (e.g., 180s, 69s, 236s)send_timeon the heavy source task orders of magnitude above the othersfetch_timeuniformly high across all downstream tasks (all waiting for the same bottleneck)Ratio cannot fix this. More consumers just means more workers waiting on the same slow
upstream task. The solution is a bytes-based or rows-based task estimator at the source stage
that splits heavy time-range partitions into multiple tasks before the first shuffle boundary.
Multi-stage cardinality propagation
In a pipeline with multiple stages, the ratio affects early stages most significantly.
Later stages self-correct because:
task count via the
cardinality_task_count_factorconfiguration.p, ratio=1 behavior kicks in anyway (lifting back top).LIMIT 10000, top-N window) end up withptasks regardless of what happened upstream.This means ratio matters most for the first 1–2 heavy stages. Tuning ratio for the
whole query is really tuning for the source-side parallelism. The rest of the pipeline
adjusts naturally.
Typical multi-stage behavior with ratio=2 (p=4, source cardinality=15):
With ratio=4:
Quick reference
Relationship to N×M fanout
Original behavior without
optimize_shuffle_partitioning: every boundary is N×M, whereN is the upstream task count and M is
output_partitions. Each consumer reads N streams.With optimization enabled:
The ratio is therefore a continuous knob between fully optimized (low fanout, low overhead,
requires uniform data) and fully N×M (maximum parallelism, maximum stream count, tolerates
any distribution).
For most production workloads, ratio=2 represents a reasonable tradeoff: it preserves
optimized behavior for light-to-medium stages while allowing heavy stages to utilize
more parallelism than a strict 1:1 mapping would permit.