Skip to main content

Backpressure

Backpressure is the ability of a Consumer/Subscriber to signal the Producer that the rate of emission is higher than what it can handle. So using this mechanism, the Consumer/Subscriber gets control over the speed at which data is emitted.

Reactive Streams provides four building blocks:

  • A Publisher that emits elements.
  • A Subscriber that reacts when elements are received.
  • A Subscription that binds a Publisher and a Subscriber.
  • And a Processor.

Here's the class diagram:

backpressure-diagram-01.png
  • Using Backpressure, the Subscriber controls the data flow from the Publisher.
  • The Subscriber makes use of request(n) to request n number of elements at a time.

The Subscription is at the root of backpressure via its request() method.

A Subscriber must signal demand via Subscription.request(long n) to receive onNext signals. The intent of this rule is to establish that it is the responsibility of the Subscriber to decide when and how many elements it is able and willing to receive. See: reactive-streams-jvm

But it falls outside the specifications' scope to define how to manage items emitted by the producer that cannot be handled downstream.

Backpressure in Project Reactor

1. Buffering

Ideal for applications that can tolerate delayed processing, such as batch jobs.

Flux<String> bufferedFlux = Flux.just("A", "B", "C")
.onBackpressureBuffer(); // Buffers all items if the downstream can't keep up
bufferedFlux.subscribe(item -> {
// Simulate slower consumption
Thread.sleep(1000);
System.out.println(item);
});

2. Dropping

Suited for real-time applications where only the latest data is relevant, such as live user interfaces.

Flux<String> droppingFlux = Flux.just("A", "B", "C", "D", "E")
.onBackpressureDrop(); // Drops items that exceed downstream capacity
droppingFlux.subscribe(System.out::println);

3. Throttling

Best for rate-limited applications, ensuring smooth data flow without overloading the system, like API data ingestion.

Flux<String> throttledFlux = Flux.just("A", "B", "C", "D", "E")
.delayElements(Duration.ofMillis(100)) // Simulate data emission rate
.sample(Duration.ofMillis(300)); // Sample the flux every 300ms
throttledFlux.subscribe(System.out::println);
  1. Batching Efficient for operations where processing data in groups significantly reduces overhead, such as data import processes.
Flux.range(1, 100)
.buffer(10) // Group into lists of 10
.subscribe(batch -> System.out.println("Processing batch: " + batch));

Backpressure in Spring Reactor

Main strategies and techniques:

1. buffer()

The buffer() operator in Spring Reactor can be used to buffer elements emitted by a Flux or Mono. Example:

Flux<Integer> numbersFlux = Flux.range(1, 1000).buffer(100);

2. onBackpressureDrop()

The onBackpressureDrop() operator drops elements when the consumer is unable to keep up with the rate of production.

Flux<Integer> numbersFlux = Flux.range(1, 1000).onBackpressureDrop();

3. filter()

The filter() operator can be used to filter elements based on certain conditions.

Flux<Integer> numbersFlux = Flux.range(1, 1000).filter(num -> num % 2 == 0);