2020-07-19

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 Size501k10K100K500K
quarkus-resteasy600054603170522108
quarkus-vertx-web803075404680668137
spring-boot401036902520585134
spring-boot-webflux686061703470732162
micronaut561049802860660153
micronaut-reactive633052902840599130

POST Scenario

Mode \ Response Size501k10K100K500K
quarkus-resteasy49703850139018436
quarkus-vertx-web73906000143017434
spring-boot33402840122018537
spring-boot-webflux549040301460217160
micronaut40802910116016732
micronaut-reactive43603060116016431