Scatter Gather
Purpose
For all-of-N, root latency includes the maximum of the required leaf completion times plus
scatter/gather overhead. First-success and k-of-N use order statistics and can return earlier;
quorum/partial semantics decide whether that answer is correct. At N = 100 with independent,
identically distributed leaves, roughly 63% of root requests contain
at least one leaf beyond its own p99. The arithmetic is tail-latency-analysis; the
consequence is this skill's whole subject.
The failure this prevents is the optimisation that reverses: work is split across more leaves
so each leaf does less, every leaf's p99 improves, and the user-facing p99 gets worse. No
leaf dashboard shows it, because no leaf is at fault. The second failure is the fan-out that
costs N units of work for one answer — the gather is satisfied, the root replies, and the
losing leaves keep running with their connections held.
Compatibility and evidence
Inspect deployed Java/client versions, existing execution model and the caller's consistency
contract before changing fan-out. The Java sketch targets Java 21+ without preview; preserve
older supported executors/frameworks when appropriate rather than upgrading implicitly.
Missing joint traces, ownership epochs or cancellation evidence leave the corresponding
latency/correctness claim unverified. Report the completion rule, bounded work/bytes, deadline
allocation, actual evidence and the validation still needed.
Workflow
- State the completion rule before writing any code. All-of-N (the answer needs every
leaf), first-success (first acceptable equivalent answer), or k acceptable distinct owners
by deadline. Define insufficient-k behavior separately; an error completing first is not
a successful winner. Every other decision below follows from this one.
- Choose N, and record why. N is a trade between per-leaf work and tail exposure, not a
free parameter. "One leaf per shard" is a default inherited from the data layout, not a
decision — see
references/tail-amplification-and-hedging.md.
- Derive every leaf cutoff from one root deadline, reserving time for cancellation
bookkeeping, merge/serialization/return.
Start a leaf only when its probability/value of completing within remaining budget justifies
the work; p50 is not a universal cutoff (
timeouts-and-deadlines).
- Bound fan-out across concurrent roots as well as within each request. Limit admission,
waiting tasks, response bytes and active leaf work. Virtual threads still retain memory;
a semaphore caps holders, not its waiter population
(
concurrency-limiting-and-bulkheads).
- Signal cancellation to losers when the gather is satisfied, and verify root tasks plus
remote work release resources. Cancellation may be advisory and cannot undo a committed
effect; budget residual work even after reply.
- Decide the partial/quorum-result contract with the caller. Include expected/responded/
missing owners, errors, data/version watermark and whether aggregation is exact, lower/
upper-bounded or stale (
rpc-and-api-contracts).
- Only then consider hedging, with operation safety, replica independence/consistency,
rate/capacity budget and cancellability measured.
Decision block
Use scatter/gather when:
- the answer genuinely requires data from several owners, each holding a disjoint slice
(sharding-and-partitioning), and it must be current as of this request
- N and total work are bounded by an admission budget; hierarchical/dynamic fan-out has a
global descendant cap rather than recursively multiplying unchecked
- root latency is derived from measured joint/order-statistic behavior, not one leaf percentile
- the caller's contract can express a partial answer, or all-of-N genuinely fits the budget
Avoid scatter/gather when:
- the leaves share a saturated resource — one database, one pool, one node — so the fan-out
is concurrency against itself rather than parallelism
- N grows with data volume: latency then degrades as the workload succeeds
- the request writes and requires atomic all-or-nothing visibility without a commit protocol.
Fan-out writes can be valid for replicated/quorum or idempotent broadcast semantics, but
scatter/gather alone is not a transaction (`distributed-transactions-and-sagas`)
- the measured all-of-N tail plus dispatch/merge cost does not fit the root target;
a leaf p99 alone cannot prove a breach for every request or for first-success
Prefer instead:
- an index or denormalised view keyed by the query, so one owner answers it
(sharding-and-partitioning) — this is the fix for a query that always fans out
- caching the gathered result when the inputs change more slowly than they are read
(caching-strategies)
- a precomputed aggregate maintained out of band when the answer need not be per-request
(distributed-aggregation-and-barriers)
Rules
- Never quote a leaf's p99 as the root's SLO. Derive each leaf's budget backwards from the
root's, using the fan-out amplification in
tail-latency-analysis.
- Raising N may reduce divisible data work, but fixed setup, skew, duplicate work and shared
bottlenecks prevent linear scaling. For all-of-N it increases exposure to any slow leaf;
correlation determines how much. Find the crossover at the root with realistic placement.
- The coordinator/root is a failure and capacity domain unless replicated/stateless. Under
all-of-N, independent required leaves with aligned success definitions multiply to about
98% at N=20 and 99.9% each; real correlation requires joint measurement. k-of-N availability
follows a binomial model only for independent identical leaves, while quorum correctness has
separate consistency assumptions.
- A partial result must carry an explicit completeness field naming the missing owners. A
list of 8 elements is indistinguishable from 8 shards that had no data, and a caller that
cannot tell will cache the wrong answer or show it as authoritative.
- Each leaf timeout is bounded by the remaining leaf cutoff; a smaller dependency-specific
timeout may also apply. Include queue/admission time. An early dependency timeout is valid,
but a fresh timeout must not extend the root deadline.
- Java
Future.cancel(true) attempts interruption; CompletableFuture.cancel does not
guarantee interrupting supplier execution, and remote cancellation depends on protocol/
client. A cancelled handle proves only local state. Assert remote in-flight/resource release.
- Hedging requires all of these: equivalent/read-only or downstream-idempotent operation;
an independent eligible replica with acceptable consistency; remaining deadline; global
hedge/concurrency budget; and cheap cancellation/residual-work accounting. An uncapped
hedge fires most often exactly when the dependency is already slow, which makes it a load
multiplier at the worst moment.
- Prefer a different failure domain than the original after a conditional latency trigger.
A different replica sharing the same shard/database may add only load, and a stale replica
may not be semantically equivalent. Trigger placement is
tail-latency-analysis.
- A retry inside a leaf multiplies the whole fan-out: N leaves at 3 attempts is 3N calls
inside one root budget, and the budget arithmetic must include it
(
retries-and-backoff).
- Scatter/gather scales worse than it looks. Adding leaves adds tail exposure and root
fan-out cost; it converts a capacity problem into a latency-variance problem. Say that in
the design review rather than discovering it at N = 50.
- Java: the production shape is
Executors.newVirtualThreadPerTaskExecutor() in
a lifecycle-managed virtual-thread executor (Java 21+) plus completion/cancellation tracking,
or a framework client with equivalent lifecycle. A per-call try-with-resources executor can
block in close() until uncooperative tasks terminate, defeating the response deadline.
StructuredTaskScope expresses ownership better but remains preview through JDK 26 (JEP 525),
requiring preview flags/recompilation; structured-concurrency owns the version matrix.
References
- Tail amplification and hedging — the
max-of-N consequence with the N table, how to choose N and where the crossover sits, the
hedging eligibility conditions and admission caps, backup-request placement, and the
series that show whether hedging is helping or adding load. Read before changing N,
and before enabling any hedge or backup request.
- Fan-out in Java — a virtual-thread executor fan-out under a
propagated deadline, per-leaf timeouts derived from the remaining budget, cancellation of
outstanding leaves and the
close() trap behind it, partial-result assembly with
completeness/watermark fields, and tests distinguishing local cancellation from residual
remote work. Read when implementing or reviewing a fan-out.
1---2name: scatter-gather3description: Fanning one request out to N workers and combining answers: order-statistic latency, choosing N against tail exposure, all-of-N/first-of-N/k-of-N completion, safe hedging, partial-result completeness and watermarks, deadline propagation, and cancelling losers. Use when a keyless query fans out to every shard, when leaf dashboards are green but user-facing p99 is not, when more leaves made the request slower, when a fan-out gives no way to tell no-data from no-answer, when a hedge is proposed, or when an in-flight gauge stays high after the caller gave up. Not StructuredTaskScope (structured-concurrency), bounding in-flight work (concurrency-limiting-and-bulkheads), tail arithmetic (tail-latency-analysis), percentiles (latency-statistics), retry policy (retries-and-backoff), offline fan-out (distributed-aggregation-and-barriers), deadlines (timeouts-and-deadlines), or shard keys (sharding-and-partitioning).4---56# Scatter Gather78## Purpose910For **all-of-N**, root latency includes the maximum of the required leaf completion times plus11scatter/gather overhead. First-success and k-of-N use order statistics and can return earlier;12quorum/partial semantics decide whether that answer is correct. At N = 100 with independent,13identically distributed leaves, roughly 63% of root requests contain14at least one leaf beyond its own p99. The arithmetic is `tail-latency-analysis`; the15consequence is this skill's whole subject.1617The failure this prevents is the optimisation that reverses: work is split across more leaves18so each leaf does less, every leaf's p99 improves, and the user-facing p99 gets worse. No19leaf dashboard shows it, because no leaf is at fault. The second failure is the fan-out that20costs N units of work for one answer — the gather is satisfied, the root replies, and the21losing leaves keep running with their connections held.2223## Compatibility and evidence2425Inspect deployed Java/client versions, existing execution model and the caller's consistency26contract before changing fan-out. The Java sketch targets Java 21+ without preview; preserve27older supported executors/frameworks when appropriate rather than upgrading implicitly.28Missing joint traces, ownership epochs or cancellation evidence leave the corresponding29latency/correctness claim unverified. Report the completion rule, bounded work/bytes, deadline30allocation, actual evidence and the validation still needed.3132## Workflow33341. **State the completion rule before writing any code.** All-of-N (the answer needs every35 leaf), first-success (first acceptable equivalent answer), or k acceptable distinct owners36 by deadline. Define insufficient-k behavior separately; an error completing first is not37 a successful winner. Every other decision below follows from this one.382. **Choose N, and record why.** N is a trade between per-leaf work and tail exposure, not a39 free parameter. "One leaf per shard" is a _default inherited from the data layout_, not a40 decision — see `references/tail-amplification-and-hedging.md`.413. **Derive every leaf cutoff from one root deadline**, reserving time for cancellation42 bookkeeping, merge/serialization/return.43 Start a leaf only when its probability/value of completing within remaining budget justifies44 the work; p50 is not a universal cutoff (`timeouts-and-deadlines`).454. **Bound fan-out across concurrent roots as well as within each request.** Limit admission,46 waiting tasks, response bytes and active leaf work. Virtual threads still retain memory;47 a semaphore caps holders, not its waiter population48 (`concurrency-limiting-and-bulkheads`).495. **Signal cancellation to losers when the gather is satisfied**, and verify root tasks plus50 remote work release resources. Cancellation may be advisory and cannot undo a committed51 effect; budget residual work even after reply.526. **Decide the partial/quorum-result contract with the caller.** Include expected/responded/53 missing owners, errors, data/version watermark and whether aggregation is exact, lower/54 upper-bounded or stale (`rpc-and-api-contracts`).557. **Only then consider hedging**, with operation safety, replica independence/consistency,56 rate/capacity budget and cancellability measured.5758## Decision block5960```text61Use scatter/gather when:62- the answer genuinely requires data from several owners, each holding a disjoint slice63 (sharding-and-partitioning), and it must be current as of this request64- N and total work are bounded by an admission budget; hierarchical/dynamic fan-out has a65 global descendant cap rather than recursively multiplying unchecked66- root latency is derived from measured joint/order-statistic behavior, not one leaf percentile67- the caller's contract can express a partial answer, or all-of-N genuinely fits the budget68Avoid scatter/gather when:69- the leaves share a saturated resource — one database, one pool, one node — so the fan-out70 is concurrency against itself rather than parallelism71- N grows with data volume: latency then degrades as the workload succeeds72- the request writes and requires atomic all-or-nothing visibility without a commit protocol.73 Fan-out writes can be valid for replicated/quorum or idempotent broadcast semantics, but74 scatter/gather alone is not a transaction (`distributed-transactions-and-sagas`)75- the measured all-of-N tail plus dispatch/merge cost does not fit the root target;76 a leaf p99 alone cannot prove a breach for every request or for first-success77Prefer instead:78- an index or denormalised view keyed by the query, so one owner answers it79 (sharding-and-partitioning) — this is the fix for a query that always fans out80- caching the gathered result when the inputs change more slowly than they are read81 (caching-strategies)82- a precomputed aggregate maintained out of band when the answer need not be per-request83 (distributed-aggregation-and-barriers)84```8586## Rules8788- Never quote a leaf's p99 as the root's SLO. Derive each leaf's budget backwards from the89 root's, using the fan-out amplification in `tail-latency-analysis`.90- Raising N may reduce divisible data work, but fixed setup, skew, duplicate work and shared91 bottlenecks prevent linear scaling. For all-of-N it increases exposure to any slow leaf;92 correlation determines how much. Find the crossover at the root with realistic placement.93- The coordinator/root is a failure and capacity domain unless replicated/stateless. Under94 all-of-N, independent required leaves with aligned success definitions multiply to about95 98% at N=20 and 99.9% each; real correlation requires joint measurement. k-of-N availability96 follows a binomial model only for independent identical leaves, while quorum correctness has97 separate consistency assumptions.98- A partial result must carry an explicit completeness field naming the missing owners. A99 list of 8 elements is indistinguishable from 8 shards that had no data, and a caller that100 cannot tell will cache the wrong answer or show it as authoritative.101- Each leaf timeout is bounded by the remaining leaf cutoff; a smaller dependency-specific102 timeout may also apply. Include queue/admission time. An early dependency timeout is valid,103 but a fresh timeout must not extend the root deadline.104- Java `Future.cancel(true)` attempts interruption; `CompletableFuture.cancel` does not105 guarantee interrupting supplier execution, and remote cancellation depends on protocol/106 client. A cancelled handle proves only local state. Assert remote in-flight/resource release.107- **Hedging requires all of these**: equivalent/read-only or downstream-idempotent operation;108 an independent eligible replica with acceptable consistency; remaining deadline; global109 hedge/concurrency budget; and cheap cancellation/residual-work accounting. An uncapped110 hedge fires most often exactly when the dependency is already slow, which makes it a load111 multiplier at the worst moment.112- Prefer a different failure domain than the original after a conditional latency trigger.113 A different replica sharing the same shard/database may add only load, and a stale replica114 may not be semantically equivalent. Trigger placement is `tail-latency-analysis`.115- A retry _inside_ a leaf multiplies the whole fan-out: N leaves at 3 attempts is 3N calls116 inside one root budget, and the budget arithmetic must include it117 (`retries-and-backoff`).118- **Scatter/gather scales worse than it looks.** Adding leaves adds tail exposure and root119 fan-out cost; it converts a capacity problem into a latency-variance problem. Say that in120 the design review rather than discovering it at N = 50.121- Java: the production shape is `Executors.newVirtualThreadPerTaskExecutor()` in122 a lifecycle-managed virtual-thread executor (Java 21+) plus completion/cancellation tracking,123 or a framework client with equivalent lifecycle. A per-call try-with-resources executor can124 block in `close()` until uncooperative tasks terminate, defeating the response deadline.125 `StructuredTaskScope` expresses ownership better but remains preview through JDK 26 (JEP 525),126 requiring preview flags/recompilation; `structured-concurrency` owns the version matrix.127128## References129130- [Tail amplification and hedging](references/tail-amplification-and-hedging.md) — the131 max-of-N consequence with the N table, how to choose N and where the crossover sits, the132 hedging eligibility conditions and admission caps, backup-request placement, and the133 series that show whether hedging is helping or adding load. Read before changing N,134 and before enabling any hedge or backup request.135- [Fan-out in Java](references/java-fan-out.md) — a virtual-thread executor fan-out under a136 propagated deadline, per-leaf timeouts derived from the remaining budget, cancellation of137 outstanding leaves and the `close()` trap behind it, partial-result assembly with138 completeness/watermark fields, and tests distinguishing local cancellation from residual139 remote work. Read when implementing or reviewing a fan-out.