Hermes Wiki

Scatter-Gather Pattern

Concept

Scatter-Gather (also called fan-out/fan-in, or "Broadcast-Aggregate" in Enterprise Integration Patterns terms) handles the case where a single incoming request needs data from multiple independent sources, and those sources can be queried in parallel rather than one after another. The request is scattered: a coordinator sends the same (or a source-specific) query to every recipient concurrently. Each recipient computes its own answer independently. The coordinator then gathers the responses and combines them into one reply to the original caller. Done well, total latency for the caller collapses from the sum of every source's response time (sequential) to roughly the slowest single source (parallel) — the entire value of the pattern is in that latency collapse.

Enterprise Integration Patterns names two variants of the scatter side: distribution, where the request goes to a known, fixed list of recipients (e.g., query five specific regional inventory services), and auction-style, where the request is broadcast on a shared channel and any interested party may respond (e.g., a request-for-quote pattern where an unknown number of vendor services might bid). The gather side needs an explicit completeness strategy deciding when to stop waiting and respond: wait for all recipients (Promise.all/Task.WhenAll-style), return as soon as the first N responses arrive, or return after a fixed timeout with however many responses have come back by then. Which strategy is correct depends entirely on whether a partial answer is acceptable to the caller.

Tradeoffs

Completeness strategy Benefit Cost
Wait for all Complete, deterministic result every time Total latency is bounded by the slowest recipient, not the average — one slow/stuck source drags down every request
First-N / first-best Fast, bounded latency regardless of stragglers Result quality/completeness depends on which sources happened to respond first, not which are most relevant or authoritative
Fixed timeout, partial results Predictable worst-case latency, graceful degradation Caller must handle a genuinely partial result (missing sources), which pushes complexity into every consumer of the response

Scatter-Gather is fan-out and fan-in combined for a single synchronous request/response cycle — see Fan-Out and Fan-In for the two halves treated independently outside a single request context (e.g., a fan-out that dispatches work asynchronously with no caller waiting on a combined response).

When to use / when not to

  • Use when one caller-facing response genuinely needs to combine data from multiple independent, parallelizable backends within a single request — federated search across data sources, an insurance/travel quote aggregator hitting multiple provider APIs, a fraud-detection check that queries several independent signal services before returning a verdict.
  • Especially valuable when the sources are roughly comparable in importance (no single source is authoritative enough to just call alone) and their query latencies are similar enough that "wait for all" doesn't get dominated by one outlier.
  • Don't use it when sources have wildly different latency profiles and a partial result would be unacceptable — a wait-for-all strategy against one consistently slow source just becomes a de facto synchronous call to that slow source, with wasted parallel calls to the fast ones.
  • Don't use it for a strict, ordered aggregation (e.g., an exact-count or an operation where every source's contribution must be reconciled deterministically) without also read MapReduce — scatter-gather's per-request fan-out doesn't give the same correctness guarantees as a batch aggregation model built for exactness.

Common pitfall

No per-source timeout, or a single global timeout applied only at the very end. Without a per-recipient timeout, one hung backend call can block the gather step indefinitely even if every other source responded in milliseconds — the coordinator has no way to know a specific recipient is stuck versus just slow. The fix is to bound every individual scattered call with its own timeout (and ideally a circuit breaker per recipient — see Circuit Breakers), so a single bad source degrades that source's contribution to the response rather than the whole request.

Engineering Lens

The scatter-gather review question is never "did we parallelize the calls" — that part is close to free with any modern async/concurrency primitive. It's "what does the caller get back when recipient 3 of 5 times out, and was that decided on purpose." A scatter-gather implementation that silently treats a timeout the same as "this source had nothing to contribute" is making a product decision (a missing signal is invisible) disguised as an engineering detail. The strong answer names the completeness strategy explicitly, states what a partial result looks like to the caller, and can point to where that tradeoff was decided rather than defaulted into by whatever the async library did when it was first wired up.

Sources

Hermes Wiki