Stackbook Logo
messagingestablished · medium operational burden

Fan-Out Fan-In

Also known as: scatter-gather, split and aggregate

Intent

Split a task into parallel subtasks distributed across workers, then collect and combine their results.

Problem

A large job, such as querying many shards or processing many files, takes too long sequentially. Running pieces in parallel needs a way to track completion and merge outputs, including partial failures.

Forces

  • •Parallelism reduces latency
  • •Results must be correlated and aggregated
  • •Some subtasks fail or are slow
  • •Aggregation state needs durability

Solution

Overview

Fan-out fan-in, also called scatter-gather, trades coordination complexity for speed. The job is split, processed concurrently and joined.

The hard part is not the fan-out; it is knowing when the fan-in is done and what to do when it never completes.

The aggregator needs thought. Counting arrivals is fragile under duplicate delivery, so record results by part index in durable storage and complete when all indices are present. Workflow engines can express this directly as parallel steps with a join, saving you from hand-building the tracking logic.

How it works

The coordinator publishes N subtasks with a job id. The aggregator persists a record per job and marks each part as it arrives.

job = new_id(); parts = split(input)
for i, p in enumerate(parts):
    publish("work", {job, i, n: len(parts), p})

# aggregator
state[job].parts[i] = result        # idempotent by index
if len(state[job].parts) == n or past_deadline(job):
    emit(merge(state[job]))

Tail latency matters because the job finishes when the slowest part does. Mitigate with timeouts, hedged retries for stragglers and smaller chunks. Decide the partial-failure contract up front: fail the whole job, return partial results with a flag, or retry only missing parts. Cap concurrency so the fan-out does not overwhelm shared downstream resources.

Pitfalls

  • Counting results instead of keying by part index, so duplicates complete the job early.
  • No deadline, so a single lost part blocks forever.
  • Keeping aggregator progress only in memory.
  • Splitting into thousands of tiny tasks where overhead dominates.

Key takeaways

  • Key results by part index to make aggregation idempotent.
  • Always define a deadline and partial-result policy.
  • Persist aggregator state.
  • Size subtasks so work outweighs overhead.

✓ When to Use

  • •Work divides into independent chunks
  • •Latency matters and workers can run concurrently
  • •Results can be merged by a commutative or well-defined reduction

✗ When Not to Use

  • •Subtasks depend on each other sequentially
  • •Splitting overhead exceeds the work itself
  • •Strong atomicity across all subtasks is required

Pros

  • +Lower end-to-end latency
  • +Scales with worker count
  • +Isolates failures to subtasks

Cons

  • −Aggregation state and completion tracking add complexity
  • −Tail latency set by slowest subtask
  • −Partial failure policy must be defined

Cost Profile

Infrastructure

Medium - queue, workers, aggregator state

Operational

Medium - job tracking

Cognitive

Medium - join semantics

Failure Modes

  • One lost subtask leaves the job waiting forever

  • Duplicate result double counts in the aggregate

  • Aggregator crash loses in-memory progress

Real-World Examples

Alternatives

    Related Patterns

    • competing-consumers
    • publish-subscribe
    • dead-letter-queue

    Competency Domains

    distribution communicationreliability ops