Why reactive programming could save you huge infra costs?

Why Reactive Programming Helps Save Huge Infra Cost

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

For instance, CPU usage of 80-90% is good. However, if your cloud-based VMs are averaging only 20-30% then your infra is underutilized and costing you more. Mostly the blocking IO operations in a request is the bottleneck. Each remote call pauses your current thread from working until the result is returned. In a nutshell, this pause is the cause of all inefficiency.

How can a server handle more requests despite optimized code? The answer lies in reactive programming or non-blocking programming. You should consider evaluating reactive programming for both current and future projects.

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. A minimal amount of I/O threads can handle many concurrent I/O. Requests are processed through I/O threads directly hence lower overheads.  It saves memory and CPU, as there is no need to create worker threads to handle the requests. It also improves the concurrency as it removes the constraint on the number of threads. Finally, it also improves response time as it reduces the number of thread context switches.

blocking vs non-blocking threads

How to decide among so many reactive frameworks like RxJava, Reactor, Akka, Quarkus, etc.? We found Quarkus fairly simple.  The ecosystem of quarkus is evolving nicely for writing modern backends that are highly performant, cloud-native, reactive, and suitable for containerized technologies like Kubernetes. Other frameworks offer too many variations of reactive methods and that leads to more confusion and costs more on maintenance.

Why Quarkus as the preferred reactive framework?

Below are some noteworthy features that make quarkus stand out overall :

  • Inherently reactive framework. 
  • Designed to go well with cloud-native and containerized environments like Kubernetes
  • Super fast start-up times suitable for serverless architectures
  • Requires relatively low memory and effective use of CPU
  • Event-driven/data-streams-driven notion at the core of it
  • Simple yet robust API support through Smallrye Mutiny for reactive programming.
  • Robust extensions framework

Take a look at the performance benchmark published by Quarkus showing power of reactive programming here

Some well-known reactive programming patterns

Quarkus comes inbuilt with the Smallrye Mutiny reactive library. Events are at the core of Mutiny’s design. This event-driven model makes it easy to write readable processing pipelines. The following are important patterns for writing reactive code:

Graceful Error Handling

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

Fault-Tolerance

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

Chaining Operations

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

Fallback on Failure

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

Circuit Breaker

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

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

Transformation

// Asynchronous

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

// Synchronous

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

Observing Events

Synchronous (Caution: use this only if you have to as it will be blocking the thread): 

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

Asynchronous:

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

There are other events also which are also useful while writing business logic

  • 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
  • subscriptionupon receiving a subscribe event, 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 a specific number of items
 

What else requires good care?

Assess below as well while you consider reactive programming in your projects.

  • Quality: Align processes. So the code anti-patterns do not backfire in production.
  • Legacy Code: Legacy code requires a systematic migration plan
  • Refactoring: Lack of automated tests requires a solid strategy
  • Current Skills: Skill sets of current team members
  • Building Expertise: Should you build expertise on your own or learn from seasoned providers?

Comment or reach out to us at info@brevitaz.com  for any queries or questions on the topic or software engineering in general. Read more here about Brevitaz’s service offerings.

Write a comment