Stackbook Logo
scalingestablished · low operational burden

Request Coalescing

Also known as: single-flight, request-collapsing

Intent

Merge identical concurrent requests into one upstream call and share the result among all waiters.

Problem

Many callers simultaneously ask for the same expensive resource, so the backend repeats identical work and is overloaded by duplicates. Caches do not help while the first computation is still in flight.

Forces

  • •Bursts of identical requests on popular resources
  • •Backend work is expensive and idempotent
  • •Waiters tolerate sharing a single result
  • •Latency of the shared call affects every waiter

Solution

Overview

Request coalescing, also called single-flight or request collapsing, ensures that only one execution of an identical operation runs at a time. It targets the window in which a cache cannot help yet: the moment between the first miss and the first stored result.

How it works

inflight = {}

func get(key):
  if key in inflight: return inflight[key].wait()
  call = start(loader, key)
  inflight[key] = call
  try:    return call.wait()
  finally: delete inflight[key]

Go's singleflight package implements this idea directly. CDNs and reverse proxies do the same with request collapsing: when 500 clients request an uncached object, one request goes to the origin and the other 499 wait for its response.

Choose the key carefully: normalize parameters and exclude per-request noise such as trace ids. Run the shared call with its own timeout, detached from any individual caller's context, so one impatient caller cannot cancel the work for the rest.

Coalescing works best with a cache. The shared result is stored, so later arrivals hit the cache instead of starting a new flight.

Pitfalls

  • Sharing a call across users with different authorization. Include the identity in the key when results differ.
  • Propagating a caller's cancellation into the shared call.
  • Forgetting to remove the entry on error, which poisons the key.
  • Assuming it works across instances. In-process coalescing reduces calls by instance count, not to one. Use a shield tier for global collapse.
  • Unbounded waiter lists on very slow calls. Time out waiters separately.

Key takeaways

  • One in-flight call per key, shared by all concurrent callers.
  • Use it for idempotent reads, never for writes.
  • Pair with caching and a decoupled timeout.
  • Keep the key tight and authorization-aware.

✓ When to Use

  • •Cache misses on popular keys
  • •Idempotent reads to slow or costly dependencies
  • •CDN or proxy layers fronting an origin

✗ When Not to Use

  • •Writes or non-idempotent operations
  • •Per-user requests that cannot be shared
  • •Calls where one caller timeout should not affect the others

Pros

  • +Cuts duplicate backend load dramatically
  • +Complements caching during misses
  • +Small amount of code, often a library call

Cons

  • −One slow or failing call affects all waiters
  • −Only collapses within a single process unless done at a shared tier
  • −Cancellation semantics are tricky

Cost Profile

Infrastructure

None

Operational

Low: add metrics for coalesced counts

Cognitive

Medium: concurrency and cancellation

Failure Modes

  • Shared call hangs and every waiter hangs with it

  • Key that includes irrelevant fields prevents any coalescing

  • First caller cancels and aborts the call for everyone

Real-World Examples

Alternatives

    Related Patterns

      Competency Domains

      reliability opsdistribution communication