# Backpressure begins where your queue fills up

> Choose bounded buffers, flow control, admission limits, load shedding, and dead-letter behavior for an overloaded pipeline.

Canonical URL: https://www.devobs.io/articles/backpressure-at-async-boundaries/
By: Ines Costa
Published: 2026-01-13T21:06:02.434Z
Updated: 2026-09-05
Section: Architecture

Backpressure is a contract for what producers do when consumers cannot keep up. It starts at the first growing buffer. If that buffer is unbounded, the system has delayed the decision until latency, memory, storage, or recovery time becomes unacceptable. Bound every queue, measure its age, and choose deliberately between waiting, rejecting, shedding, or degrading.

## Follow one overloaded pipeline

Imagine an upload API that writes jobs to a broker, workers call an analysis service, and results stream to clients. Under a traffic spike, workers slow because the analyzer saturates. Broker depth rises, completion latency rises, clients retry, and the API adds work faster. Scaling workers may worsen the analyzer. The useful signal is not queue length alone but oldest job age relative to the product deadline.

At the network stream, use flow control so a receiver governs how much data arrives. [gRPC flow-control guidance](https://grpc.io/docs/guides/flow-control/) describes mechanisms that prevent a sender from overwhelming a receiver and warns about deadlock when both sides perform blocking writes without reads. Flow control protects a connection or stream; it does not decide whether the overall service should admit another upload.

At the application boundary, admission control checks capacity before accepting durable work. Use a bounded concurrency semaphore, queue-age limit, tenant quota, or token bucket. Reject excess requests with a clear retry contract, or accept only if the system can preserve its completion promise. A `202 Accepted` response is misleading when the backlog already guarantees expiry.

## Propagate pressure instead of hiding it

The [Reactive Streams initiative](https://www.reactive-streams.org/) defines an asynchronous stream model with non-blocking backpressure, where demand is signaled rather than assumed. The same idea applies beyond one library: consumers advertise capacity; producers stop, buffer within a bound, or receive a refusal. Adapters must preserve the signal. Reading an entire bounded stream into an unbounded array breaks the contract.

Across a broker, producers may not block on consumer demand. Use broker quotas, partition limits, delayed acceptance, and queue-age policies. Avoid automatic producer retries that ignore admission responses. If the work is optional, shed it early. If it is critical, reserve capacity or isolate it in a separate queue so bulk traffic cannot consume every worker and storage byte.

## Pick the failure mode per workload

Interactive requests usually prefer fast rejection over minutes of hidden queueing. Batch work may wait within a published deadline. Telemetry can sample or drop low-value events while retaining security signals. For replaceable state updates, coalesce multiple pending jobs by key. For irreversible commands, preserve each accepted item and stop admitting before durability is threatened.

Dead-letter queues are quarantine, not disposal. Include original ID, attempt count, schema version, safe error classification, and first/last failure time. Alert an owner and provide an idempotent replay path. Poison messages must not block an ordered partition indefinitely; choose whether to pause that key, skip with an audit record, or route for repair.

## Control the feedback loop

Track admitted, rejected, completed, failed, dropped, and retried work; queue depth and oldest age; consumer utilization; downstream latency; and time-to-drain at current throughput. Autoscaling should include downstream capacity and backlog age, not react to depth alone. Cap retries under overload and use jitter so recovery does not cause a synchronized surge.

Review each boundary with six questions: Is the buffer bounded? What is the unit of demand? Who can refuse work? Which priority gets reserved capacity? What does the caller observe? How is quarantined work repaired?

Choose the queue with the highest age today. Set a product deadline, derive an admission threshold before that deadline becomes impossible, and load-test beyond it. A healthy overloaded system should reject or degrade predictably while accepted work continues to complete.

## Source references

- <https://www.reactive-streams.org/>
- <https://grpc.io/docs/guides/flow-control/>
