Boost throughput with RESTEasy Reactive in Quarkus 2.2
Quarkus has, from its beginning, provided core features for coding Java programs in both imperative and reactive styles. With the new 2.2 release, Quarkus continues to improve in terms of network-related features, reactive programming, and integration with the Eclipse Vert.x event bus. For example, RESTEasy Reactive in Quarkus is a new JAX-RS implementation based on the Vert.x layer that achieves much higher throughput by handling reactive events on the non-blocking I/O thread.
The Red Hat build of Quarkus 2.2 provides production support for the RestEasy Reactive extensions, with additional features:
- Multipart/form-data request handling
- Executing a non-blocking method by default
- Determining the dispatch strategy by method signatures
- Providing a performance score dashboard for reactive endpoints
These new features deliver improved performance improvements when developing reactive applications.
This article demonstrates how you can improve the performance of a Quarkus reactive application by using RESTEasy Reactive features and the Quarkus Dev UI. Download the repository from GitHub to follow along with this tutorial.
Check REST reactive endpoints in the Quarkus Dev UI
First things first: Run Quarkus Dev Mode using the following Maven command in your project directory where you cloned the example:
$ ./mvnw quarkus:dev
Press d
after the example application starts and displays the following messages:
--
Tests paused
Press [r] to resume testing, [o] Toggle test output, [h] for more options>
A new web browser will open to access the Quarkus Dev UI, shown in Figure 1. In the RESTEasy Reactive box, click Endpoint scores.
You will then be taken to the Quarkus REST scores console, which shows the behavior of four REST reactive endpoints with scores and colors (see Figure 2).
Note that two endpoints have a score of 100 with the color green, but the /hello()
endpoint has a score of only 33 in red and the /hello/stream/{count}/{name}
endpoint has a score of 66 in yellow. Before we investigate why two endpoints are not performing well, let's do a local test to see whether they work properly.
Execute the following curl
commands to invoke the two REST reactive endpoints. For the /hello
endpoint, enter:
$ curl localhost:8080/hello
hello
For the /hello/stream/{count}/{name}
endpoint, enter:
$ curl localhost:8080/hello/stream/3/daniel
data:hello daniel - 0
data:hello daniel - 1
data:hello daniel - 2
These commands should be complete without errors. Go back to the scores dashboard in the Quarkus Dev UI and click GET /hello. The detail scores will appear as shown in Figure 3.
Three measurements show whether a REST reactive application can be optimized further. For example, the hello
endpoint is optimized only for resource measurement. This means the endpoint is accessible and returns a successful output, which you already tested locally. In contrast, the Writer and Execution measurements show failures with 0%
scores.
Tune the slow endpoints to optimize performance
Open the ReactiveGreetingResource.java
file in the src/main/java/org/acme/getting/started
directory and take a look at the hello()
method:
@GET
@Produces(MediaType.TEXT_PLAIN)
public Object hello() {
return "hello";
}
The return type of the method is currently defined with a type of Object
. It needs to be specified as a String
to avoid unnecessary object conversion during reactive application execution. Therefore, update the return type as shown in the following snippet (the change is highlighted in bold):
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
Save the ReactiveGreetingResource.java
file, then reload the scoreboard page in the Dev UI. You should find that the performance has improved because the Writer measurement has been fixed and the score has increased to 66 as shown in Figure 4.
Non-blocking I/O
Let's fix the remaining performance issue in the hello()
method, shown by the Execution measurement. RESTEasy Reactive, in the Red Hat build of Quarkus 2.2, automatically determines whether a particular method runs on a non-blocking I/O thread asynchronously or on a worker thread synchronously, based on the method's signature. The hello()
method returns a String
signature that dispatches a blocking worker thread. The blocking thread reduces reactive processing performance compared to the non-blocking I/O thread.
Therefore, you need to override the default dispatch strategy to make it use a non-blocking I/O thread, using the @NonBlocking
annotation. Go back to the ReactiveGreetingResource.java
file and add an @NonBlocking
annotation as follows:
@GET
@Produces(MediaType.TEXT_PLAIN)
@NonBlocking
public String hello() {
return "hello";
}
Save the ReactiveGreetingResource.java
file, then reload the scoreboard page in the Dev UI. You should now have 100 scores everywhere, thanks to optimizing the hello()
method's performance (Figure 5).
Now let's fix the other endpoint, /hello/stream/{count}/{name}
. Move to the greetingsAsStream()
method in the ReactiveGreetingResource.java
file:
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
@RestSseElementType(MediaType.TEXT_PLAIN)
@Path("/stream/{count}/{name}")
@Blocking
public Multi<String> greetingsAsStream(int count, String name) {
return service.greetings(count, name);
}
The method signature is already specified Multi
to dispatch a non-blocking I/O thread. However, an unnecessary @Blocking
annotation appears in the method, overriding the default dispatch strategy. Therefore, you need to remove that annotation or comment it out. Update the greetingsAsStream
method as shown in the following example:
@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
@RestSseElementType(MediaType.TEXT_PLAIN)
@Path("/stream/{count}/{name}")
// @Blocking
public Multi<String> greetingsAsStream(int count, String name) {
return service.greetings(count, name);
}
Save the file and reload the scores page in the Dev UI. You should now have 100%
scores everywhere because the greetingsAsStream()
method has been optimized, as shown in Figure 6.
Figure 7 shows that you have optimized the performance of all endpoints.
How to get started with Quarkus
This article has shown how developers optimize reactive applications with Quarkus in the inner-loop development phase. Release 2.2 also provides awesome features to improve developers' productivity, including continuous testing, improvements in the Quarkus command-line interface (CLI) and the Dev UI, and Dev Services.
Subscribe to bit.ly/danielohtv for learning cloud-native application development with Kubernetes.