Skip to main content
Home / Blog / Why Reactive Programming Could Save You Huge Infra Costs

Why Reactive Programming Could Save You Huge Infra Costs

Bhavesh Shah August 29, 2024 4 min
Cloud ComputingConcurrencyDistributed ComputingJava

Introduction

Modern services in cloud and edge environments must be resource-efficient to increase deployment density and reduce costs. This critical need for optimal cloud infrastructure usage explains why reactive programming is gaining popularity.

Consider this scenario: CPU usage of 80-90% is considered good performance. However, if your cloud-based VMs are averaging only 20-30% CPU utilization, your infrastructure is underutilized and costing you unnecessarily. The primary bottleneck causing this inefficiency is blocking I/O operations. Each remote call pauses the current thread from working until the result is returned, creating idle time that translates to wasted resources and higher costs.

The solution lies in reactive programming and non-blocking programming approaches. Organizations should evaluate these paradigms for both current and future projects to optimize infrastructure costs while improving performance.

What is Reactive Programming or Non-Blocking Programming?

Reactive programming is designed to achieve better scalability with minimal resources. Non-blocking I/O provides an efficient way to deal with concurrent I/O operations without creating excessive threads.

Key Benefits of Non-Blocking I/O

  • Efficient Concurrency: A minimal number of I/O threads can handle many concurrent I/O operations
  • Direct Processing: Requests are processed directly through I/O threads, reducing overhead
  • Resource Conservation: Eliminates the need to create worker threads to handle each request, saving memory and CPU
  • Improved Concurrency: Removes constraints on the number of threads available for processing
  • Reduced Latency: Improves response time by reducing the number of thread context switches

Why Quarkus as the Preferred Reactive Framework?

When evaluating reactive frameworks like RxJava, Reactor, Akka, and Quarkus, Quarkus stands out as particularly approachable. The Quarkus ecosystem is evolving nicely for writing modern backends that are highly performant, cloud-native, and reactive, making it suitable for containerized technologies like Kubernetes. Other frameworks often offer too many variations of reactive methods, leading to confusion and increased maintenance costs.

Key Features of Quarkus

  1. Inherently Reactive: Built from the ground up with reactive principles
  2. Cloud-Native and Containerized: Designed specifically for cloud environments and Kubernetes orchestration
  3. Fast Startup Times: Super fast startup times make it suitable for serverless architectures
  4. Efficient Resource Usage: Requires relatively low memory with effective CPU utilization
  5. Event-Driven Architecture: Event-driven and data-streams-driven notion at the core
  6. Simple Yet Robust API: Provides straightforward API support through Smallrye Mutiny for reactive programming
  7. Robust Extensions Framework: Extensive ecosystem of extensions for various integrations

Quarkus publishes performance benchmarks demonstrating the power of reactive programming compared to traditional approaches.

Some Well-Known Reactive Programming Patterns

Quarkus comes built-in with the Smallrye Mutiny reactive library. Events are at the core of Mutiny's design, making it easy to write readable processing pipelines. Important patterns for writing reactive code include:

Graceful Error Handling

Handle failures gracefully with fallback values or recovery strategies:

Uni<String> result = Uni.createFrom().failure(new RuntimeException("Something went wrong"))
  .onFailure().recoverWithItem("Fallback value");

Fault-Tolerance

Implement retry logic to handle transient failures:

Uni<String> result = Uni.createFrom().failure(new RuntimeException("Transient failure"))
  .onFailure().retry().atMost(3);

Chaining Operations

Compose transformations by chaining operations together:

Uni<String> result = Uni.createFrom().item("Hello")
  .onItem().transform(item -> item + " World")
  .onItem().transform(String::toUpperCase);

Fallback on Failure

Provide fallback values when critical operations fail:

Uni<String> result = Uni.createFrom().failure(new RuntimeException("Critical failure"))
  .onFailure().recoverWithItem("Fallback value");

Circuit Breaker

Protect against cascading failures:

Uni<String> result = Uni.createFrom().item("Important data")
  .onItem().transformToUni(data -> simulateRemoteServiceCall(data))
  .onFailure().recoverWithItem("Fallback after circuit breaker");

private Uni<String> simulateRemoteServiceCall(String data) {
  return Uni.createFrom().failure(new RuntimeException("Service failure"))
    .onFailure().invoke(failure -> System.out.println("Circuit breaker triggered"));
}

Stream Processing

Transform and filter streams of data:

Multi<Integer> multi = Multi.createFrom().items(1, 2, 3, 4, 5)
  .onItem().transform(i -> i * 2)
  .select().where(i -> i > 5);

Transformation: Asynchronous

Invoke remote services without blocking:

Uni<String> result = uni
  .onItem().transformToUni(item -> invokeRemoteService(item));

Transformation: Synchronous

Synchronously transform items (use cautiously as it blocks the thread):

Uni<String> someUni = Uni.createFrom().item("hello");
someUni.onItem().transform(i -> i.toUpperCase())
  .subscribe().with(item -> System.out.println(item));

Observing Events

Synchronous event observation (use only when necessary):

Uni<String> u = uni.onItem().invoke(i -> System.out.println("Received item: " + i));

Asynchronous event observation (preferred):

uni.onItem().call(i -> System.out.println("Received item: " + i));

Event Types in Reactive Streams

  • item: When the upstream sends an item
  • failure: When the upstream fails
  • completion: When the upstream completes
  • subscribe: A downstream subscriber expresses interest in the data
  • subscription: The upstream acknowledges the subscription
  • cancellation: A downstream subscriber requests to stop receiving events
  • overflow: The upstream emits more than the downstream can handle
  • request: The downstream indicates its capacity to handle specific items

What Else Requires Good Care?

When considering reactive programming adoption, assess the following areas:

  1. Quality: Align processes and coding standards to prevent anti-patterns from causing production issues
  2. Legacy Code: Develop a systematic migration plan if working with existing legacy systems
  3. Refactoring: Establish a solid testing strategy when lacking automated tests
  4. Current Skills: Evaluate the skill sets of current team members and their readiness for reactive paradigms
  5. Building Expertise: Decide whether to build expertise internally or learn from seasoned providers

Conclusion

Reactive programming and non-blocking I/O approaches offer significant infrastructure cost savings and improved resource utilization. Quarkus emerges as an excellent framework choice for organizations looking to adopt reactive programming with minimal complexity.

For more information or inquiries about reactive programming solutions, contact: info@brevitaz.com

BS

Bhavesh Shah

A member of the Brevitaz team sharing insights on software engineering, big data, and cloud technologies.

Back to all articles