Recent Posts
Archives

Posts Tagged ‘ReactiveStreams’

PostHeaderIcon [DevoxxPL2019] Constructing Custom Reactive Publishers: Insights into Project Reactor Internals

Lecturer

Oleh Dokuka contributes to Project Reactor as a committer, authoring books on reactive programming with Spring and serving as a software engineer at Superhuman. Based in Kyiv, he actively participates in conferences and communities focused on asynchronous systems.

Abstract

This inquiry delves into the intricacies of building a reactive publisher compliant with Reactive Streams specifications, drawing from Project Reactor’s design. It covers the rationale behind the spec, naive implementations, concurrency patterns like work-in-progress, and verification via TCK. Through iterative coding, it analyzes challenges in non-blocking data flows, backpressure, and thread safety, pondering effects on debugging, customization, and library extension.

Demystifying Reactive Streams: Specification and Purpose

Reactive Streams standardize asynchronous, non-blocking data processing with backpressure, addressing overflow in producer-consumer scenarios. Oleh commences by recalling the spec’s origins, crafted to unify libraries like RxJava and Akka Streams, ensuring interoperability.

Core interfaces—Publisher, Subscriber, Subscription, Processor—define interactions: publishers emit items, subscribers consume, subscriptions mediate requests and cancellations. The spec mandates rules for thread safety and signal ordering, preventing races.

Contextually, adoption surged with Java 9’s Flow API, embedding reactivity natively. Analytically, backpressure—subscribers requesting items—prevents buffering overloads, crucial in unbounded sources like networks.

Implications: enables composable, resilient pipelines, but demands adherence to 50+ rules, tested via TCK. For developers, understanding facilitates debugging; for extenders, it unlocks optimizations.

Naive Publisher Construction: Initial Steps and Pitfalls

Commencing with a basic array publisher, Oleh demonstrates emitting elements on subscription. Yet, naivety ignores concurrency: parallel subscriptions risk duplicates or misses.

Methodologically, extend TCK’s PublisherVerification for rule checks. Initial failures highlight needs for atomic operations and request tracking.

A subscription class manages emissions:

class ArraySubscription<T> implements Subscription {
    private final Subscriber<? super T> subscriber;
    private final T[] array;
    private int index = 0;
    private boolean canceled = false;

    public ArraySubscription(Subscriber<? super T> subscriber, T[] array) {
        this.subscriber = subscriber;
        this.array = array;
    }

    @Override
    public void request(long n) {
        if (n <= 0 && !canceled) {
            subscriber.onError(new IllegalArgumentException("Non-positive request"));
            canceled = true;
            return;
        }
        for (long i = 0; i < n && !canceled; i++) {
            if (index < array.length) {
                subscriber.onNext(array[index++]);
            } else {
                subscriber.onComplete();
                canceled = true;
                break;
            }
        }
    }

    @Override
    public void cancel() {
        canceled = true;
    }
}

This handles basics but falters under concurrency, necessitating refinements.

Incorporating Concurrency Safeguards: Work-in-Progress and Atomicity

To thread-safely accumulate requests, introduce work-in-progress (WIP)—an atomic counter tracking processing state. Oleh explains: increment WIP to claim emission exclusivity; if non-zero, another thread processes, so defer.

Requests add to a requested counter atomically. On WIP decrement to zero, check if more requests pend, resuming if so.

This pattern, akin to semaphores, ensures single-threaded emission despite multi-threaded requests, averting races.

Analytically, it balances responsiveness and safety, though overflows (Long.MAX_VALUE) signal unbounded requests, potentially overwhelming subscribers.

Implications: facilitates non-blocking I/O, vital for high-throughput, but debugging requires tracing atomics.

Verification and Iterative Refinement: Ensuring Spec Compliance

Leverage TCK for exhaustive testing: extend PublisherVerification, supplying working and failing publishers. Tests validate signals, backpressure, and edge cases like negative requests.

Oleh iterates: failures prompt guards, like canceling on invalid requests. Post-fixes, all pass, confirming robustness.

Methodologically, TCK simulates parallelism, exposing flaws early. For custom operators, similar suites verify.

Consequences: empowers library creation or tweaks, as in optimizing for known guarantees, enhancing performance in specific flows.

Extending to Operators and Libraries: Building Beyond Basics

With a compliant publisher, assemble operators chaining transformations. Oleh hints at flux wrappers, where sources like arrays feed pipelines.

Analytically, operators preserve backpressure, propagating requests upstream. This composability yields expressive, efficient streams.

Implications: demystifies internals, aiding contributions to Reactor or custom variants for niches like low-latency trading.

In conclusion, mastering publishers unlocks reactive potential, transforming complex async into manageable flows.

Links:

PostHeaderIcon [ScalaDaysNewYork2016] Connecting Reactive Applications with Fast Data Using Reactive Streams

The rapid evolution of data processing demands systems that can handle real-time information efficiently. At Scala Days New York 2016, Luc Bourlier, a software engineer at Lightbend, delivered an insightful presentation on integrating reactive applications with fast data architectures using Apache Spark and Reactive Streams. Luc demonstrated how Spark Streaming, enhanced with backpressure support in Spark 1.5, enables seamless connectivity between reactive systems and real-time data processing, ensuring responsiveness under varying workloads.

Understanding Fast Data

Luc began by defining fast data as the application of big data tools and algorithms to streaming data, enabling near-instantaneous insights. Unlike traditional big data, which processes stored datasets, fast data focuses on analyzing data as it arrives. Luc illustrated this with a scenario where a business initially runs batch jobs to analyze historical data but soon requires daily, hourly, or even real-time updates to stay competitive. This shift from batch to streaming processing underscores the need for systems that can adapt to dynamic data inflows, a core principle of fast data architectures.

Spark Streaming and Backpressure

Central to Luc’s presentation was Spark Streaming, an extension of Apache Spark designed for real-time data processing. Spark Streaming processes data in mini-batches, allowing it to leverage Spark’s in-memory computation capabilities, a significant advancement over Hadoop’s disk-based MapReduce model. Luc highlighted the introduction of backpressure in Spark 1.5, a feature developed by his team at Lightbend. Backpressure dynamically adjusts the data ingestion rate based on processing capacity, preventing system overload. By analyzing the number of records processed and the time taken in each mini-batch, Spark computes an optimal ingestion rate, ensuring stability even under high data volumes.

Reactive Streams Integration

To connect reactive applications with Spark Streaming, Luc introduced Reactive Streams, a set of Java interfaces designed to facilitate communication between systems with backpressure support. These interfaces allow a reactive application, such as one generating random numbers for a Pi computation demo, to feed data into Spark Streaming without overwhelming the system. Luc demonstrated this integration using a Raspberry Pi cluster, showcasing how backpressure ensures the system remains stable by throttling the data producer when processing lags. This approach maintains responsiveness, a key tenet of reactive systems, by aligning data production with consumption capabilities.

Practical Demonstration and Challenges

Luc’s live demo vividly illustrated the integration process. He presented a dashboard displaying a reactive application computing Pi approximations, with Spark analyzing the generated data in real time. Initially, the system handled 1,000 elements per second efficiently, but as the rate increased to 4,000, processing delays emerged without backpressure, causing data to accumulate in memory. By enabling backpressure, Luc showed how Spark adjusted the ingestion rate, maintaining processing times around one second and preventing system failure. He noted challenges, such as the need to handle variable-sized records, but emphasized that backpressure significantly enhances system reliability.

Future Enhancements

Looking forward, Luc discussed ongoing improvements to Spark’s backpressure mechanism, including better handling of aggregated records and potential integration with Reactive Streams for enhanced pluggability. He encouraged developers to explore Reactive Streams at reactivestreams.org, noting its inclusion in Java 9’s concurrent package. These advancements aim to further streamline the connection between reactive applications and fast data systems, making real-time processing more accessible and robust.

Links: