DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Building a Reactive Event-Driven App With Dead Letter Queue
  • The Long Road to Java Virtual Threads
  • Embracing Reactive Programming With Spring WebFlux
  • Tracking Changes in MongoDB With Scala and Akka

Trending

  • Event-Driven Microservices: How Kafka and RabbitMQ Power Scalable Systems
  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  • Caching 101: Theory, Algorithms, Tools, and Best Practices
  • AI Agents: A New Era for Integration Professionals

Backpressure in Reactive Systems

Considering a fast data producer and a slow data consumer, backpressure is the mechanism that 'pushes back' on the producer not to be overwhelmed by data.

By 
Nicolas Fränkel user avatar
Nicolas Fränkel
DZone Core CORE ·
Mar. 18, 21 · Analysis
Likes (7)
Comment
Save
Tweet
Share
6.1K Views

Join the DZone community and get the full member experience.

Join For Free

Mid-January, I held a talk at Kotlin.amsterdam based on my post Migrating from Imperative to Reactive (a Spring Boot application). Because it was a Kotlin meetup, I demoed Kotlin code, and I added a step by migrating the codebase to coroutines. During Q&A, somebody asked whether coroutines implemented backpressure. I admit I was not sure of the answer, so I did a bit of research.

This post provides information on backpressure in general and how RxJava (v3), Project Reactor, and Kotlin's Coroutines handle it.

What Is Backpressure?

Back pressure (or backpressure) is a resistance or force opposing the desired flow of fluid through pipes, leading to friction loss and pressure drop.

The term back pressure is a misnomer, as pressure is a scalar quantity, so it has a magnitude but no direction.

-- Wikipedia

In software, backpressure has a slightly related but still different meaning: considering a fast data producer and a slow data consumer, backpressure is the mechanism that 'pushes back' on the producer not to be overwhelmed by data.

Whether based on reactivestreams.org or Java's java.util.concurrent.Flow, Reactive Streams provides four building blocks:

  1. A Publisher that emits elements.
  2. A Subscriber that reacts when elements are received.
  3. a Subscription that binds a Publisher and a Subscriber.
  4. And a Processor.

Here's the class diagram:

Processor Class Diagram

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

The specifications are pretty straightforward:

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. To avoid signal reordering caused by reentrant Subscription methods, it is strongly RECOMMENDED for synchronous Subscriber implementations to invoke Subscription methods at the very end of any signal processing. It is RECOMMENDED that Subscribers request the upper limit of what they are able to process, as requesting only one element at a time results in an inherently inefficient "stop-and-wait" protocol.

-- Reactive Streams specifications for the JVM

Reactive Streams' specifications are pretty solid. They also come with a Java-based TCK (Test Compatibility Kit).

But it falls outside the specifications' scope to define how to manage items emitted by the producer that cannot be handled downstream. While the problem is pretty simple, different solutions are possible. Each Reactive framework provides some options, so let's see them in turn.

Backpressure in RxJava 3

RxJava v3 provides several base classes:

Class Description
Flowable A flow of 0..N items. It supports Reactive-Streams and backpressure.
Observable A flow of 0..N items. It doesn't support backpressure.
Single A flow of exactly:
  • 1 item,
  • or an error.
Maybe A flow with either:
  • no items,
  • exactly one item,
  • or an error.
Completable A flow with no item but either:
  • a completion,
  • or an error signal.

Among these classes, Flowable is the only class that implements Reactive Streams — and backpressure. Yet, providing backpressure is not the only issue. As RxJava's wiki states:

Backpressure doesn’t make the problem of an overproducing Observable or an underconsuming Subscriber go away. It just moves the problem up the chain of operators to a point where it can be handled better.

Reactive pull backpressure isn’t magic.

To cope with that, RxJava offers two main strategies to handle 'overproduced' items:

  1. Store items in a buffer:

    Storing Items in a Buffer

    Note that if you set no upper bound to the buffer, it might cause OutOfMemoryError.

  2. Drop items:

    Dropping Items

The following diagram summarizes the different methods that implement those strategies:

Implementation Strategy Flowchart

Note that the onBackPressureLatest operator is similar to using onBackpressureBuffer(1):

Alternate Operator Flow

Note that I took the above Marble diagrams from RxJava's wiki.

Compared to other frameworks, RxJava offers methods to send an overflow exception signal after sending all items. These allow the consumer to receive items and still be notified that the producer has dropped items.

Backpressure in Project Reactor

Strategies offered by Project Reactor are similar to those of RxJava's.

The APIs have some slight differences, though. For example, Project Reactor offers a convenient method to throw an exception if the producer overflows:

Java
 




x


 
1
var stream = Stream.generate(Math::random);
2

          
3
// RxJava
4
Flowable.fromStream(stream)        // 1
5
        .onBackpressureBuffer(0);  // 2
6

          
7
// Project Reactor
8
Flux.fromStream(stream)            // 1
9
    .onBackpressureError();        // 2



  1. Create the Reactive Stream.
  2. Throw if the producer overflows

Here's the Flux class diagram that highlights backpressure capabilities:

Flux Class Diagram

Compared to other frameworks, Project Reactor offers methods to set a TTL for buffered items to prevent overflowing it.

Backpressure in Coroutines

Coroutines do offer the same buffering and dropping capabilities. The base class in coroutines is Flow.

kotlinx.coroutines.flow Diagram

You can use the classes like this:

Kotlin
 




xxxxxxxxxx
1


 
1
flow {                              // 1
2
  while (true) emit(Math.random())  // 2
3
}.buffer(10)                        // 3



  1. Create a Flow which content is defined by the next block.
  2. Define the Flow content.
  3. Set the buffer's capacity to 10.

Conclusion

All in all, RxJava, Project Reactor, and Kotlin coroutines all provide backpressure capabilities. All cope with a producer that is faster than its subscriber by offering two strategies: either buffer items or drop them.

Thanks to my friend Oleh Dokuka for his kind review.

To Go Further:

  • Reactive Streams JVM specifications
  • How (not) to use Reactive Streams in Java 9+
  • RxJava Backpressure

Originally published at A Java Geek on March 14th 2021.

Reactive Streams

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Building a Reactive Event-Driven App With Dead Letter Queue
  • The Long Road to Java Virtual Threads
  • Embracing Reactive Programming With Spring WebFlux
  • Tracking Changes in MongoDB With Scala and Akka

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!