
Wrk-ing µ-Frameworks’ Flavors
There are many options when it comes to which framework to use for developing JVM-based microservices these days.
In this post, I’m going to summarize how they perform on two simple day-to-day scenarios (with some variations) from wrk‘s point of view.
Wrk Tool
While searching a simple cli tool for measuring performance of REST services, I found plenty of references to wrk – a HTTP benchmarking tool able to generate a significant load while running on a single multi-core CPU of an usual dev workstation.
Its usage is pretty straightforward: set the test duration, the number of threads and connections those threads are going to use during the test and optionally add a custom Lua script for making more than a simple GET request.
Test Scenarios
The two test scenarios consist of GETting and POSTing Articles resources, as defined in the gothinkster/realworld “mother of all demo apps” project.
Both scenarios include:
- Reading the data from memory
- Using POJOs (requests) responses, (de)serialized with Jackson
- Varying the resource size, from 50B to 500K
- Using 10 threads and 100 connections (which will put a decent load on the tested applications)
- Starting, warming-up the application for 60s, run the test for another 60s, then stopping it
Spring Boot – the “Classic Flavour”
A Spring @RestController
class Controller {
@GetMapping
public ResponseEntity<?> getArticles(
@PathVariable("path") String path
)
@PostMapping
public ResponseEntity<?> saveArticles(
@PathVariable("path") String path,
@RequestBody Articles articles
)
}
Spring Boot Web Flux
Functional-style routes
class Configuration {
public RouterFunction<ServerResponse> routerFunction() {
route(GET("/spring-boot-webflux/{path}"), req -> { /* ... */ })
.and(route(POST("/spring-boot-webflux/{path}"), req -> { /* */}))
}
}
Quarkus w/ Vertx
A declarative vertx route
class Controller {
@Route(path = "quarkus-vertx-web/:path",
methods = HttpMethod.GET)
void getArticles(RoutingContext rc)
@Route(path = "quarkus-vertx-web/:path",
methods = HttpMethod.POST)
void saveArticles(RoutingContext rc)
}
Micronaut
A Micronaut @Controller
class Controller {
@Get(uri = "/micronaut/{path}")
public HttpResponse<?> getArticles(
@PathVariable("path") String path
)
@Post(uri = "/micronaut/{path}")
public HttpResponse<?> saveArticles(
@PathVariable("path") String path,
@Body Single<Articles> articles
)
}
Micronaut Reactive
A Micronaut @Controller with RxJava requests and responses
class Controller {
@Get
public Maybe<Articles> getArticles(
@PathVariable("path") String path
)
@Post
public Maybe<Articles> saveArticles(
@PathVariable("path") String path,
@Body Single<Articles> articles
)
}
The code and test scripts can be found here
Results
The smaller the payload is, the bigger the differeces between frameworks (and the same framework flavors) are, while when using an extreme payload size it doesn’t seem to matter any longer.
GET Scenario
| Mode \ Response Size | 50 | 1k | 10K | 100K | 500K |
|---|---|---|---|---|---|
| quarkus-resteasy | 6000 | 5460 | 3170 | 522 | 108 |
| quarkus-vertx-web | 8030 | 7540 | 4680 | 668 | 137 |
| spring-boot | 4010 | 3690 | 2520 | 585 | 134 |
| spring-boot-webflux | 6860 | 6170 | 3470 | 732 | 162 |
| micronaut | 5610 | 4980 | 2860 | 660 | 153 |
| micronaut-reactive | 6330 | 5290 | 2840 | 599 | 130 |
POST Scenario
| Mode \ Response Size | 50 | 1k | 10K | 100K | 500K |
|---|---|---|---|---|---|
| quarkus-resteasy | 4970 | 3850 | 1390 | 184 | 36 |
| quarkus-vertx-web | 7390 | 6000 | 1430 | 174 | 34 |
| spring-boot | 3340 | 2840 | 1220 | 185 | 37 |
| spring-boot-webflux | 5490 | 4030 | 1460 | 217 | 160 |
| micronaut | 4080 | 2910 | 1160 | 167 | 32 |
| micronaut-reactive | 4360 | 3060 | 1160 | 164 | 31 |