Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 57

Backpressure: Preventing System Overload

Recommended resources

Introduction

Welcome to our next lesson on building resilient, high-performance services. In our previous session, we dissected flow control and connection management, focusing on how an individual service can protect itself from being overwhelmed using techniques like TCP's sliding window and Google's client-side adaptive throttling. You learned that a service can signal distress when its utilization exceeds a safe threshold.

This lesson builds directly on that idea, exploring how these individual distress signals are propagated through a system to create feedback loops. We will focus on backpressure, the set of patterns that allow a downstream service to signal an upstream service to slow down, thereby preventing a localized overload from escalating into a system-wide cascading failure. Understanding and applying these patterns is fundamental to engineering the kind of massively-scaled, reliable systems you would be responsible for as a Principal Engineer.


What is Backpressure?

At its core, backpressure is about communication. When a service (the consumer) is unable to process work as fast as it receives it from another service (the producer), a feedback mechanism is needed to tell the producer to reduce its rate. Without this feedback, the consumer's internal queues will grow, leading to increased memory usage, higher latency, and eventual failure—a condition we want to avoid at all costs.

This is a classic producer-consumer problem that appears in nearly all asynchronous systems. The video "Backpressure Explained" by Petabridge provides an excellent introduction to this problem, using a Kafka-based system as a clear example.

Backpressure Explained

This video will help you visualize what happens in a system without backpressure.

Please watch the first part of the video, from the beginning to the discussion of solutions. Pay close attention to how the non-blocking, asynchronous nature of the producer leads to the consumer being overwhelmed, resulting in high resource utilization. This sets the stage for why backpressure is so critical.

As the video illustrates, the absence of backpressure means the producer operates blindly, pushing data downstream as fast as possible. The consequence is not just high resource usage but the risk of a much more dangerous phenomenon: a cascading failure.


The Critical Role of Backpressure: Preventing Cascading Failures

A cascading failure is the domino effect that occurs when a failure in one part of a system triggers failures in other parts. An overloaded service is a common starting point. Its increased latency and error rate put pressure on its upstream callers. If those callers don't adapt, they may also become overloaded, propagating the failure further up the call stack until the entire user-facing service is degraded or unavailable.

Backpressure is the primary defense against this. It acts as a safety valve, ensuring that load is gracefully managed rather than allowing pressure to build to a breaking point. Google's SRE book chapter on "Addressing Cascading Failures" is the canonical text on this subject.

Cascading Failures: Reducing System Outage - Google SRE

This chapter from the Google SRE book details why cascading failures happen and what can be done to prevent and mitigate them.

Please read the following sections: Read the introduction for a clear definition. Focus on the section Causes of Cascading Failures. This explains how server overload and resource exhaustion are the root causes that backpressure is designed to prevent. Finally, look at the emergency measures under "Immediate Steps to Address Cascading Failures." Read the subsections on dropping traffic and entering degraded modes. These are reactive, "big hammer" forms of backpressure applied during an outage.


A Taxonomy of Backpressure Patterns

Now that we've established the "why," let's explore the "how." Backpressure isn't a single technique but a family of patterns, some of which you've already encountered. They range from simple, implicit mechanisms to sophisticated, adaptive algorithms.

Implicit Backpressure

These patterns create backpressure naturally through resource constraints, without explicit signaling.

  • Bounded Queues: This is the most fundamental form of backpressure. In a typical worker pool architecture, requests are placed in a queue before being processed by a worker thread. If this queue has a fixed size, it will eventually fill up when the producer is faster than the consumer. The attempt to add a new item to the full queue will block the producer, naturally forcing it to slow down to the rate at which the consumer is processing items. The Google SRE book discusses this in the context of queue management as a way to reject requests early.

Explicit Backpressure

These patterns involve active signaling between components to manage load.

  • Rate Limiting: As we saw last lesson with client-side adaptive throttling, a server can send explicit "I'm overloaded" signals (e.g., HTTP 429 "Too Many Requests," or gRPC RESOURCE_EXHAUSTED codes). The client then uses these signals to reduce its request rate. This is a direct, explicit feedback loop.

  • Circuit Breakers: This client-side pattern prevents a client from repeatedly calling a service that is known to be failing. The circuit breaker monitors calls to a service and, if the failure rate exceeds a threshold, it "opens" or "trips." While open, all subsequent calls fail immediately without being sent over the network. This protects the client from wasting resources and, crucially, gives the downstream service time to recover by relieving it of load. After a timeout, the breaker moves to a "half-open" state, allowing a few test requests through. If they succeed, the breaker closes; otherwise, it re-opens.

This diagram shows the three states of a circuit breaker: CLOSED (normal operation), OPEN (blocking requests after failures), and HALF-OPEN (testing for recovery). Backpressure signals, such as high latency or resource exhaustion metrics, can proactively trigger a transition to the half-open or open state before hard failures occur.
  • Load Shedding and Graceful Degradation: When a service detects it's nearing overload, it can decide to "shed" (drop) incoming requests to protect itself. As you read in the SRE book, this is most effective when done based on the criticality of the request, ensuring that essential work is preserved. Graceful degradation is a related concept where the service performs less work per request (e.g., returning fewer search results) to reduce CPU cost, thereby lowering its own internal pressure.

  • Adaptive Concurrency Limits: Rather than limiting the rate of requests (QPS), this advanced pattern limits the number of concurrent, in-flight requests. This approach can be more effective because it adapts to changes in latency. The core idea, used in algorithms like TCP Vegas and implemented in libraries like Netflix's Concurrency Limits, is to monitor the latency of requests. A sharp increase in latency (the "RTT gradient") implies that a queue is building up somewhere downstream. The system uses this signal to reduce its concurrency limit, effectively "draining the queue" and preventing overload before it gets severe.

This diagram illustrates an adaptive concurrency limit in practice. A load balancer distributes requests to service instances. Each instance measures the Round-Trip Time (RTT) of its requests and maintains a minimum observed RTT. By tracking the difference between the current RTT and the minimum RTT, it can infer queueing delay and adjust its in-flight request limit to keep latency low.

The Perils of Retries and the Power of Jitter

Backpressure mechanisms can be completely undermined by poorly implemented client behavior, especially retries. When a request fails—perhaps due to a backpressure signal like a rate limit—the natural instinct is to retry. However, if thousands of clients all retry immediately, they create a "thundering herd" that can overwhelm and knock over a recovering service.

Naive retries amplify load. The correct approach is to back off exponentially between retries. But even that isn't enough. If all clients use the same backoff interval, they will retry in synchronized waves. The solution is jitter—adding randomness to the backoff delay.

The talk from AWS re:Invent on "The Amazon Builders' Library" gives one of the best explanations of this concept.

AWS re:Invent 2019: Introducing The Amazon Builders’ Library (DOP328)

This segment explains why naive retries are dangerous and how exponential backoff with jitter prevents thundering herds without requiring complex coordination between clients.

Watch the section from Mark's talk on retries. The "cheesesteak truck" analogy is a famous and highly intuitive way to understand how synchronized retries create traffic spikes and how jitter smooths them out, improving both throughput and availability.

Exponential backoff with jitter is not just a good idea; it's a mandatory component of any robust client that interacts with a distributed system. It allows the client to respect the backpressure signals from the server while still ensuring eventual success for its requests.


Putting It All Together: A Practical Example

Let's return to the "Backpressure Explained" video. Now that we have a full vocabulary of patterns, the second half of the video, which demonstrates a solution, will be much more impactful. It shows how a streaming framework like Akka Streams provides tools to explicitly manage concurrency and create the feedback loop necessary for true backpressure.

Backpressure Explained

This part of the video demonstrates how to implement backpressure and quantifies its benefits.

Please watch the remainder of the video, starting from the discussion of solutions. Focus on: The strategies discussed, especially the core idea that the consumer must signal the producer to slow down. The implementation using selectAsyncUnordered to limit concurrency, which is a form of adaptive concurrency limit. The request-response ask pattern, which creates the explicit acknowledgment needed for the feedback loop. The dramatic reduction in CPU and memory usage once backpressure is enabled. This makes the abstract concept very concrete.


Conclusion

In this lesson, we've explored the critical role of backpressure in maintaining the stability of large-scale distributed systems. You've seen that backpressure is not a single feature but a collection of design patterns that create a feedback loop from overloaded consumers back to producers.

Here are the key takeaways:

  • Backpressure Prevents Cascading Failures: It's the primary mechanism for stopping a localized overload from spreading and taking down an entire system.
  • It's a System-Level Feedback Loop: Unlike transport-level flow control, backpressure propagates signals across service boundaries, forcing upstream producers to slow down.
  • A Rich Set of Patterns Exist: You can implement backpressure using bounded queues, explicit rate limits, circuit breakers, load shedding, and adaptive concurrency limits.
  • Client Behavior is Key: Proper client-side retry logic, using exponential backoff with jitter, is essential to cooperate with backpressure signals rather than fighting against them.

In our previous lessons, we've covered service meshes, flow control, and now backpressure. You now possess a powerful set of tools for designing and analyzing the reliability of communication between services. In our next lesson, you will apply this knowledge directly. You will be given a design scenario and asked to select and justify the communication patterns for key service interactions, weighing trade-offs between performance, consistency, and the resilience patterns we've discussed today.