Home >> Blog >> Why I use Quarkus rather than Spring-Boot for my CNA apps

Why I use Quarkus rather than Spring-Boot for my CNA apps

13 janvier 2021

By Pascal Libenzi.

This post demonstrates the advantages of compiling and running a Java application using Quarkus.

As an introduction about what is Quarkus you can read the previous blog post written by Yann Albou.

Photo by Shiro Hatori on Unsplash

To get the most out of your application in the Cloud and especially in containers and Kubernetes you need to design your application for this new world. How can frameworks help to reach high levels of productivity and efficiency ?

For sure Java frameworks can help in the area, either Spring Boot or Quarkus, that we compare in this article using the same sample application.

The code used is available on GitHub, you can find three subdirectories:

  • employee-sb – Spring Boot flavor
  • employee-quarkus – Quarkus version
  • comparisons – tools for comparing the application on both platforms

The sample application is a simple CRUD of a root entity « Persons », connected to a Postgres database. We use the Hibernate ORM for both applications, and additionally the Panache framework in case of Quarkus.

There are two ways for compiling a Java application using the Quarkus framework:

  • standard / old-fashioned JVM
  • GraalVM (for generation of native code)

To get a minimalistic exposed surface in terms of security, we will use scratch as the starting point of our Docker image, to get only our application binary in it.

Environment Setup

To avoid complex setup with GraalVM, you can use a multistage Docker build, which helps in building the GraalVM binary without installing it locally.

The multistage Docker build is composed of three stages (see Dockerfile.scratch.native):

  • collect jar dependencies in the first step, to avoid losing time in the following steps
  • build the native binary (some tricks are necessary when using a scratch or an alpine image, detailed in the Dockerfile comments)
  • finally, copy the binary into our scratch image

In both subdirectories employee-sb and employee-quarkus you can find a build-mvn-docker script that builds images for you, and then tag the docker image with the name that will be used by the comparison tools.

The hardware used for comparisons is a MacBook Pro laptop with Docker Desktop installed on it to run the containers.

Dedicated resources for Docker Virtual Machine are:

  • 8Gb of RAM + 1Gb for swap.
  • 8 CPUs.

Benchmark

In order to compare various approaches, we have a look at 3 key metrics:

  • startup time
  • size of generated artifacts and docker image
  • memory usage

Startup time

Spring Boot – Startup time: ~10 seconds

  • Old-fashioned
  • Less efficient than Quarkus

Quarkus JVM – Startup time: ~2-3 seconds

  • Simple build without -Dnative (or -Pnative, the choice is yours)
  • Less efficient than a native build
  • More efficient than a classic stack

Quarkus native – Startup time < 0.1 second as shown above

  • Very efficient
  • Compilation time is slow since you use GraalVM, but it’s the price to pay to have great performances at runtime. Notice that if you use multistage build, your build time will be slower than in the « standard GraalVM way ».
Spring Boot Quarkus JVM Quarkus native
Startup time ~10s ~3s <0.1s
Build time ~18s ~20s ~4m by local GraalVM ~6m using multistaging

Size of JAR files

Spring Boot Quarkus
58.9M 30Mo

If you analyze the generated jar file from Quarkus, you see there’s no lib (JAR files) included, every class needed at runtime is extracted from its original library and included in the generated jar directly. Then, Quarkus only keep the strict necessary classes, which results in a so lightweight package.

For instance I used io.quarkus.hibernate.orm.panache.PanacheEntity from Quarkus-Panache maven dependency but not io.quarkus.panache.common.Page, and you will not find this Page class into the final package.

Size of Docker images

Spring Boot Quarkus JVM Quarkus native
237M 213M 68.2M

As you need to embed JVM in the quarkus-jvm Docker image, size is three times heavier than the native docker image.

As the native way doesn’t need to have an embedded JVM in the docker image, it is much lighter. And size matters, since a lighter image will affect you in terms of:

  • storage – you will need less storage in your repositories and on your local « dev » machine
  • bandwidth – the lighter the image, the faster it will be to pull the image
  • security – the surface prone to attacks is smaller, considering the number of packages

 Runtime

You can use a simple docker-compose file to run the three modes in parallel (remember that my dedicated resources for Docker is 8Gb of RAM and 8 CPUs, you will have some gaps in results for comparisons depending on your configuration).

RAM:

Spring Boot Quarkus JVM Quarkus native
386.5M 205.7M 15.96M

CPU:

Spring Boot Quarkus JVM Quarkus native
0.38% 0.32% 0.02%

We can see that while the application is in « quiet mode », Quarkus consumes less memory and CPU in JVM mode than Spring Boot, and it’s much better with the native mode (~10 times less CPU usage, and almost 20 times less Memory usage)

Scaling multi instances with load

In a container environment, with multi-tenant purposes, you need to be able to scale (up or down) quickly a service, if load is too important for the service to be able to respond fast. If the application becomes unresponsive or unavailable because of the number of connections to it, this definitely impacts the end-users…

Let’s imagine that our application often crashes due to memory leak or anything related, the Orchestrator (Swarm, Kubernetes, etc.) will restart the replica.

As a demonstration, we simply do it using watch and docker-compose restart:

watch -n 60 "docker-compose restart employee-qk-native employee-qk-jvm employee-sb"

Now we call each container every second during 100 seconds, to check if it is up and running:

  for i in {1..100}; do wget -t 1 localhost:8092/hello -a sb.log ;sleep 1; done
  for i in {1..100}; do wget -t 1 localhost:8082/hello -a qk_native.log ;sleep 1; done
  for i in {1..100}; do wget -t 1 localhost:8081/hello -a qk_jvm.log ;sleep 1; done

Count of failures:

Spring Boot Quarkus JVM Quarkus native
19 7 0

Let’s increase the rhythm, by restarting every 10 seconds. This is just for the demonstration purpose (I hope this will never happen to you in real life).

Count of failures:

Spring Boot Quarkus JVM Quarkus native
99 37 1

Using docker stats, we can see that:

  • Spring Boot used sometimes more than 400% (4 cores) of CPU for restart
  • Quarkus with JVM max CPU usage is approximately 200% (2 cores)
  • Quarkus native never goes above 1%

As you can see, the probability for your users to be impacted tends to 0 when using Quarkus in the native way. And if you are not comfortable with native compilation, using Quarkus in the JVM mode is still better than using Spring Boot.

Max concurrent containers

For this section, you can use subfolders in docker-compose, one per application mode.

What we should expect with 8GB of RAM for docker:

Spring Boot Quarkus JVM Quarkus native
One instance ~390M ~210M ~20M
#containers for 8Gb RAM 8000/390 =~ 20 8000/210 =~ 38 8000/20 = 400

For the initialization of Swarm we use the following command, to get more IP addresses (by default swarm provides you a network with only « 256 » available addresses):

docker swarm init --default-addr-pool=10.0.0.0/8 --default-addr-pool-mask-length 16

Spring boot

cd docker-compose-only-quarkus-jvm && docker stack deploy quarkus-jvm -c docker-compose.yaml

Using swarm, with 10 replicas, spring boot app take 30 secondes to launch.

cd docker-compose-only-quarkus-jvm && docker stack deploy quarkus-jvm -c docker-compose.yaml
squale@MacBookPro docker-compose-only-sb % docker service logs employee-sb_employee-sb
  | grep "Started EmployeeApplication" | sed 's/.*(Started EmployeeApplication.*).JVM.*/1/'
  Started EmployeeApplication in 29.753 seconds
  Started EmployeeApplication in 29.307 seconds
  Started EmployeeApplication in 30.299 seconds
  Started EmployeeApplication in 30.078 seconds
  Started EmployeeApplication in 30.516 seconds
  Started EmployeeApplication in 29.641 seconds
  Started EmployeeApplication in 29.327 seconds
  Started EmployeeApplication in 29.259 seconds
  Started EmployeeApplication in 29.304 seconds
  Started EmployeeApplication in 28.946 seconds

Now we try with 20 replicas, using:

docker service scale employee-sb_employee-sb=0 && docker service scale employee-sb_employee-sb=20

Sometimes it exceeds the 400Mb of RAM when starting, and so swapping occurs.

With only 20 replicas, the startup time dramatically increases for most of them – something you do not expect in a production environment…

Started EmployeeApplication in 47.961 seconds 
Started EmployeeApplication in 118.761 seconds 
Started EmployeeApplication in 39.776 seconds 
Started EmployeeApplication in 119.987 seconds 
Started EmployeeApplication in 47.934 seconds 
Started EmployeeApplication in 80.795 seconds 
Started EmployeeApplication in 118.837 seconds 
Started EmployeeApplication in 47.437 seconds 
Started EmployeeApplication in 115.916 seconds 
Started EmployeeApplication in 22.231 seconds 
Started EmployeeApplication in 114.372 seconds 
Started EmployeeApplication in 123.038 seconds 
Started EmployeeApplication in 123.981 seconds 
Started EmployeeApplication in 41.729 seconds 
Started EmployeeApplication in 57.515 seconds 
Started EmployeeApplication in 48.277 seconds 
Started EmployeeApplication in 13.333 seconds 
Started EmployeeApplication in 121.364 seconds 

Quarkus-JVM

10 replicas:

squale@MacBookPro docker-compose-only-quarkus-jvm % docker service logs employee-quarkus-jvm_employee-qk-jvm --since=7m | grep 
  started | sed 's/(.* INFO).*(started in .*)Listening.*/1 2/'
employee-quarkus-jvm_employee-qk-jvm.4.c2u7wtsja1qb@docker-desktop     | 2020-12-12 15:27:01,502 INFO started in 7.241s. 
employee-quarkus-jvm_employee-qk-jvm.6.h2recpnyzlvu@docker-desktop     | 2020-12-12 15:27:01,412 INFO started in 7.247s. 
employee-quarkus-jvm_employee-qk-jvm.5.ymnsww2n022p@docker-desktop     | 2020-12-12 15:26:59,485 INFO started in 6.489s. 
employee-quarkus-jvm_employee-qk-jvm.7.hf6awiitarjc@docker-desktop     | 2020-12-12 15:27:00,797 INFO started in 7.289s. 
employee-quarkus-jvm_employee-qk-jvm.10.o3vh4d1nftgu@docker-desktop    | 2020-12-12 15:27:01,530 INFO started in 7.286s. 
employee-quarkus-jvm_employee-qk-jvm.1.z0ww963hunpx@docker-desktop     | 2020-12-12 15:27:01,339 INFO started in 7.122s. 
employee-quarkus-jvm_employee-qk-jvm.9.0j0h3yrvsvgg@docker-desktop     | 2020-12-12 15:27:01,466 INFO started in 7.176s. 
employee-quarkus-jvm_employee-qk-jvm.3.jyz20e437j45@docker-desktop     | 2020-12-12 15:27:01,379 INFO started in 7.326s. 
employee-quarkus-jvm_employee-qk-jvm.2.aly4vnfo11wx@docker-desktop     | 2020-12-12 15:27:01,321 INFO started in 7.008s. 
employee-quarkus-jvm_employee-qk-jvm.8.m562xpxqxqtt@docker-desktop     | 2020-12-12 15:27:01,072 INFO started in 7.343s.

This is really better (30 seconds in Spring Boot mode vs only 7 seconds here).

Scaling to 35 replicas, we have a max startup time at 25 seconds (vs 120 for Spring Boot for 20 replicas):

squale@MacBookPro docker-compose-only-quarkus-jvm % docker service logs quarkus-jvm_employee-qk-jvm --since=7m 
  | grep started 
  | sed 's/(.* INFO).*(started in .*)Listening.*/1 2/'
quarkus-jvm_employee-qk-jvm.1.semdukuhnvu6@docker-desktop     | 2020-12-14 18:33:10,695 INFO started in 25.324s. 
quarkus-jvm_employee-qk-jvm.15.p95ubjzlx8vi@docker-desktop    | 2020-12-14 18:33:10,787 INFO started in 24.647s. 
quarkus-jvm_employee-qk-jvm.2.0htc1q0jxj9u@docker-desktop     | 2020-12-14 18:33:10,718 INFO started in 24.730s. 
quarkus-jvm_employee-qk-jvm.3.pzlmns267b5f@docker-desktop     | 2020-12-14 18:32:56,025 INFO started in 17.017s. 
quarkus-jvm_employee-qk-jvm.28.a4zziyewdf8p@docker-desktop    | 2020-12-14 18:33:10,775 INFO started in 24.381s. 
quarkus-jvm_employee-qk-jvm.18.unxeyk19uhwg@docker-desktop    | 2020-12-14 18:33:10,373 INFO started in 24.689s. 
quarkus-jvm_employee-qk-jvm.11.njexuc0ayi83@docker-desktop    | 2020-12-14 18:33:10,741 INFO started in 24.647s. 
quarkus-jvm_employee-qk-jvm.31.eu171iagolpm@docker-desktop    | 2020-12-14 18:33:09,157 INFO started in 24.411s. 
quarkus-jvm_employee-qk-jvm.25.n0yasczo1cq1@docker-desktop    | 2020-12-14 18:33:10,780 INFO started in 25.022s. 
quarkus-jvm_employee-qk-jvm.30.nertg4g44o34@docker-desktop    | 2020-12-14 18:33:02,995 INFO started in 22.477s. 
quarkus-jvm_employee-qk-jvm.16.aonlyw9qr3nu@docker-desktop    | 2020-12-14 18:33:05,927 INFO started in 23.977s. 
quarkus-jvm_employee-qk-jvm.6.9l69o023p2wc@docker-desktop     | 2020-12-14 18:33:10,667 INFO started in 24.485s. 
quarkus-jvm_employee-qk-jvm.12.tfn5xpxafnsd@docker-desktop    | 2020-12-14 18:32:44,853 INFO started in 7.269s. 
quarkus-jvm_employee-qk-jvm.5.8ersi5p4ff9x@docker-desktop     | 2020-12-14 18:33:06,275 INFO started in 23.666s. 
quarkus-jvm_employee-qk-jvm.26.ool02pyq151r@docker-desktop    | 2020-12-14 18:33:07,140 INFO started in 24.131s. 
quarkus-jvm_employee-qk-jvm.27.5gtmbaoib1m1@docker-desktop    | 2020-12-14 18:33:10,587 INFO started in 24.580s. 
quarkus-jvm_employee-qk-jvm.10.simx2umzdwin@docker-desktop    | 2020-12-14 18:33:10,772 INFO started in 25.046s. 
quarkus-jvm_employee-qk-jvm.4.xkfaq80a0h92@docker-desktop     | 2020-12-14 18:33:10,697 INFO started in 25.105s. 
quarkus-jvm_employee-qk-jvm.32.r0h1pm75lfrk@docker-desktop    | 2020-12-14 18:33:10,253 INFO started in 24.399s. 
quarkus-jvm_employee-qk-jvm.22.v4axe5ceexzt@docker-desktop    | 2020-12-14 18:32:53,473 INFO started in 14.764s. 
quarkus-jvm_employee-qk-jvm.29.mbzx60idg59u@docker-desktop    | 2020-12-14 18:33:06,211 INFO started in 23.889s. 
quarkus-jvm_employee-qk-jvm.7.q7qlvt0f4big@docker-desktop     | 2020-12-14 18:32:56,848 INFO started in 17.181s. 
quarkus-jvm_employee-qk-jvm.8.qmkyu94fascp@docker-desktop     | 2020-12-14 18:33:10,739 INFO started in 24.828s. 
quarkus-jvm_employee-qk-jvm.23.1qaf0z9751gp@docker-desktop    | 2020-12-14 18:32:50,664 INFO started in 12.271s. 
quarkus-jvm_employee-qk-jvm.19.ljbf0zmeyfnc@docker-desktop    | 2020-12-14 18:33:10,466 INFO started in 24.283s. 
quarkus-jvm_employee-qk-jvm.20.w3h66vb0rb0v@docker-desktop    | 2020-12-14 18:33:04,855 INFO started in 23.851s. 
quarkus-jvm_employee-qk-jvm.9.mfhbv2ebb9rn@docker-desktop     | 2020-12-14 18:33:09,142 INFO started in 25.281s. 
quarkus-jvm_employee-qk-jvm.34.5zvaark9wtab@docker-desktop    | 2020-12-14 18:33:10,673 INFO started in 24.607s. 
quarkus-jvm_employee-qk-jvm.17.tfhjnjo4cmva@docker-desktop    | 2020-12-14 18:33:06,129 INFO started in 22.958s. 
quarkus-jvm_employee-qk-jvm.14.0aq8y160osfz@docker-desktop    | 2020-12-14 18:33:09,203 INFO started in 23.630s. 
quarkus-jvm_employee-qk-jvm.13.67atlnv9bujo@docker-desktop    | 2020-12-14 18:32:44,632 INFO started in 7.028s. 
quarkus-jvm_employee-qk-jvm.21.sxrmf38njw35@docker-desktop    | 2020-12-14 18:32:48,207 INFO started in 9.868s. 
quarkus-jvm_employee-qk-jvm.33.mc9lu3cc3por@docker-desktop    | 2020-12-14 18:33:10,105 INFO started in 23.876s. 
quarkus-jvm_employee-qk-jvm.35.ewxoosq9xmi8@docker-desktop    | 2020-12-14 18:33:08,267 INFO started in 23.841s. 
quarkus-jvm_employee-qk-jvm.24.rtr487q4qvvr@docker-desktop    | 2020-12-14 18:33:10,171 INFO started in 24.311s.

Quarkus-native

cd docker-compose-only-quarkus-scratch && docker stack deploy quarkus-jvm -c docker-compose.yaml

10 replicas:

docker service logs employee-native_employee-qk-native --since=3m 
  | grep started 
  | sed 's/(.* INFO).*(started in .*)Listening.*/1 2/'
employee-native_employee-qk-native.10.x6uri8a5ik3p@docker-desktop    | 2020-12-12 14:23:15,134 INFO started in 0.124s. 
employee-native_employee-qk-native.3.vy64wrk62ldf@docker-desktop     | 2020-12-12 14:23:15,335 INFO started in 0.086s. 
employee-native_employee-qk-native.1.ndjkdv71chzw@docker-desktop     | 2020-12-12 14:23:14,519 INFO started in 0.108s. 
employee-native_employee-qk-native.4.wrs5co0h9mc9@docker-desktop     | 2020-12-12 14:23:15,463 INFO started in 0.113s. 
employee-native_employee-qk-native.7.nh77c0xuf2ef@docker-desktop     | 2020-12-12 14:23:15,014 INFO started in 0.135s. 
employee-native_employee-qk-native.9.6zxednmeg1uo@docker-desktop     | 2020-12-12 14:23:15,485 INFO started in 0.113s. 
employee-native_employee-qk-native.6.99gz13gk1tm3@docker-desktop     | 2020-12-12 14:23:15,498 INFO started in 0.085s. 
employee-native_employee-qk-native.8.kmiaz9awwpya@docker-desktop     | 2020-12-12 14:23:15,615 INFO started in 0.072s. 
employee-native_employee-qk-native.2.ymcu092stu7d@docker-desktop     | 2020-12-12 14:23:15,504 INFO started in 0.091s. 
employee-native_employee-qk-native.5.5y4z5cm27s1q@docker-desktop     | 2020-12-12 14:23:15,152 INFO started in 0.111s.

Max startup time is less than 0.2 seconds for any replica, that’s awesome!

30 replicas:

squale@MacBookPro ~ % docker service logs employee-native_employee-qk-native --since=3m | grep 
  started | sed 's/(.* INFO).*(started in .*)Listening.*/1 2/'
employee-native_employee-qk-native.1.uh0k4gjec32g@docker-desktop     | 2020-12-12 14:20:14,190 INFO started in 0.097s.
employee-native_employee-qk-native.28.k2tzlzr6obg8@docker-desktop    | 2020-12-12 14:20:12,589 INFO started in 0.106s.
employee-native_employee-qk-native.24.w6ixdaf0soy1@docker-desktop    | 2020-12-12 14:20:11,942 INFO started in 0.169s.
employee-native_employee-qk-native.12.mrfw14vy9f2d@docker-desktop    | 2020-12-12 14:20:12,801 INFO started in 0.129s.
employee-native_employee-qk-native.6.p9dq23gridy5@docker-desktop     | 2020-12-12 14:20:13,836 INFO started in 0.079s.
employee-native_employee-qk-native.15.hln4kdktrcew@docker-desktop    | 2020-12-12 14:20:12,456 INFO started in 0.111s.
employee-native_employee-qk-native.11.wcswliizzt1e@docker-desktop    | 2020-12-12 14:20:14,169 INFO started in 0.130s.
employee-native_employee-qk-native.9.jyk39uuaxwsn@docker-desktop     | 2020-12-12 14:20:13,668 INFO started in 0.127s.
employee-native_employee-qk-native.4.qag8g796ktxo@docker-desktop     | 2020-12-12 14:20:14,190 INFO started in 0.086s.
employee-native_employee-qk-native.13.rcnlwzheks70@docker-desktop    | 2020-12-12 14:20:11,468 INFO started in 0.099s.
employee-native_employee-qk-native.21.rphxcs9erl25@docker-desktop    | 2020-12-12 14:20:13,093 INFO started in 0.124s.
employee-native_employee-qk-native.14.j9mbc7iro4qk@docker-desktop    | 2020-12-12 14:20:13,697 INFO started in 0.101s.
employee-native_employee-qk-native.5.f8949dvikrd0@docker-desktop     | 2020-12-12 14:20:14,171 INFO started in 0.105s.
employee-native_employee-qk-native.30.nqrxu4vuem00@docker-desktop    | 2020-12-12 14:20:10,600 INFO started in 0.406s.
employee-native_employee-qk-native.29.wfrccji8r9u0@docker-desktop    | 2020-12-12 14:20:11,170 INFO started in 0.123s.
employee-native_employee-qk-native.22.ygzx8z6c3tyu@docker-desktop    | 2020-12-12 14:20:13,483 INFO started in 0.141s.
employee-native_employee-qk-native.3.kjkshiogn528@docker-desktop     | 2020-12-12 14:20:12,695 INFO started in 0.128s.
employee-native_employee-qk-native.20.m6j4cngji78d@docker-desktop    | 2020-12-12 14:20:13,818 INFO started in 0.106s.
employee-native_employee-qk-native.18.2o39jw9nxo8h@docker-desktop    | 2020-12-12 14:20:14,032 INFO started in 0.110s.
employee-native_employee-qk-native.26.1kbc8i6fv52e@docker-desktop    | 2020-12-12 14:20:11,985 INFO started in 0.142s.
employee-native_employee-qk-native.19.v08077shjpws@docker-desktop    | 2020-12-12 14:20:13,146 INFO started in 0.173s.
employee-native_employee-qk-native.23.o7xsdu1pf4ha@docker-desktop    | 2020-12-12 14:20:14,360 INFO started in 0.087s.
employee-native_employee-qk-native.8.dt5nbq2refcw@docker-desktop     | 2020-12-12 14:20:14,334 INFO started in 0.092s.
employee-native_employee-qk-native.10.k797kqpr34y2@docker-desktop    | 2020-12-12 14:20:11,859 INFO started in 0.151s.
employee-native_employee-qk-native.17.q33zg6huphiv@docker-desktop    | 2020-12-12 14:20:10,853 INFO started in 0.086s.
employee-native_employee-qk-native.25.m9z80na4spus@docker-desktop    | 2020-12-12 14:20:13,703 INFO started in 0.147s.
employee-native_employee-qk-native.16.r83i31t10ged@docker-desktop    | 2020-12-12 14:20:14,359 INFO started in 0.076s.
employee-native_employee-qk-native.7.4gxm5w00o2rp@docker-desktop     | 2020-12-12 14:20:12,792 INFO started in 0.164s.
employee-native_employee-qk-native.27.koxsb6g3w22v@docker-desktop    | 2020-12-12 14:20:11,123 INFO started in 0.125s.
employee-native_employee-qk-native.2.97nl6oqe33bm@docker-desktop     | 2020-12-12 14:20:12,473 INFO started in 0.112s.

Max startup time is less than 0.3 seconds for any container, that’s also great!

Let’s try to be more aggressive, by scaling up to 100 replicas!

squale@MacBookPro ~ % docker service logs employee-native_employee-qk-native --since=7m 
  | grep started 
  | sed 's/(.* INFO).*(started in .*)Listening.*/1 2/'
employee-native_employee-qk-native.10.w82xak0n70pr@docker-desktop     | 2020-12-12 14:31:23,642 INFO started in 0.198s. 
employee-native_employee-qk-native.16.zfib4ejsy8pm@docker-desktop     | 2020-12-12 14:31:26,888 INFO started in 0.179s. 
employee-native_employee-qk-native.68.siydkpctmf7k@docker-desktop     | 2020-12-12 14:31:20,236 INFO started in 0.156s. 
employee-native_employee-qk-native.43.ljykwae0jdu6@docker-desktop     | 2020-12-12 14:31:23,503 INFO started in 0.146s. 
employee-native_employee-qk-native.62.al69lczqhtuv@docker-desktop     | 2020-12-12 14:31:21,796 INFO started in 0.143s. 
employee-native_employee-qk-native.48.wbqn1b1swogy@docker-desktop     | 2020-12-12 14:31:15,929 INFO started in 0.095s. 
employee-native_employee-qk-native.77.obbdg1eren75@docker-desktop     | 2020-12-12 14:31:21,551 INFO started in 0.159s. 
employee-native_employee-qk-native.78.nfkcsvq9eq5m@docker-desktop     | 2020-12-12 14:31:27,648 INFO started in 0.155s. 
employee-native_employee-qk-native.99.r28281n6dj83@docker-desktop     | 2020-12-12 14:31:24,327 INFO started in 0.086s. 
employee-native_employee-qk-native.76.jkpd36g8xv5h@docker-desktop     | 2020-12-12 14:31:27,675 INFO started in 0.126s. 
employee-native_employee-qk-native.51.w4ftxlzzdocm@docker-desktop     | 2020-12-12 14:31:29,148 INFO started in 0.094s. 
employee-native_employee-qk-native.81.m2ldpuutosl3@docker-desktop     | 2020-12-12 14:31:17,850 INFO started in 0.110s. 
employee-native_employee-qk-native.27.s1iz7jctl9gw@docker-desktop     | 2020-12-12 14:31:20,710 INFO started in 0.117s. 
employee-native_employee-qk-native.1.02pcvk5k3od3@docker-desktop      | 2020-12-12 14:31:25,671 INFO started in 0.121s. 
employee-native_employee-qk-native.29.zhle156a06tm@docker-desktop     | 2020-12-12 14:31:23,638 INFO started in 0.150s. 
employee-native_employee-qk-native.82.2pu8hpx7597h@docker-desktop     | 2020-12-12 14:31:26,945 INFO started in 0.150s. 
employee-native_employee-qk-native.11.kpdlmz5k7t6o@docker-desktop     | 2020-12-12 14:31:24,908 INFO started in 0.115s. 
employee-native_employee-qk-native.38.wzq968v6t2c3@docker-desktop     | 2020-12-12 14:31:21,679 INFO started in 0.157s. 
employee-native_employee-qk-native.41.2p5f1uo9qcea@docker-desktop     | 2020-12-12 14:31:18,878 INFO started in 0.089s. 
employee-native_employee-qk-native.56.h6z7j21zcmwv@docker-desktop     | 2020-12-12 14:31:25,721 INFO started in 0.117s. 
employee-native_employee-qk-native.45.ozs3tf95afeo@docker-desktop     | 2020-12-12 14:31:29,151 INFO started in 0.080s. 
employee-native_employee-qk-native.59.57taqxdv0log@docker-desktop     | 2020-12-12 14:31:23,944 INFO started in 0.105s. 
employee-native_employee-qk-native.20.2ycww39s6anw@docker-desktop     | 2020-12-12 14:31:24,717 INFO started in 0.086s. 
employee-native_employee-qk-native.80.2qr73f5xldtq@docker-desktop     | 2020-12-12 14:31:27,657 INFO started in 0.113s. 
employee-native_employee-qk-native.89.liiqqw8aax65@docker-desktop     | 2020-12-12 14:31:17,547 INFO started in 0.096s. 
employee-native_employee-qk-native.92.z2b8afaq4xfz@docker-desktop     | 2020-12-12 14:31:22,691 INFO started in 0.209s. 
employee-native_employee-qk-native.25.5qnspf8k8puu@docker-desktop     | 2020-12-12 14:31:18,257 INFO started in 0.107s. 
employee-native_employee-qk-native.73.a99oqhk9x3fa@docker-desktop     | 2020-12-12 14:31:28,367 INFO started in 0.099s. 
employee-native_employee-qk-native.47.rkat97ote5yr@docker-desktop     | 2020-12-12 14:31:26,935 INFO started in 0.149s. 
employee-native_employee-qk-native.6.0gvox8v5phba@docker-desktop      | 2020-12-12 14:31:28,678 INFO started in 0.109s. 
employee-native_employee-qk-native.83.o8zcyqy2f959@docker-desktop     | 2020-12-12 14:31:26,906 INFO started in 0.210s. 
employee-native_employee-qk-native.52.uogvu3xeage8@docker-desktop     | 2020-12-12 14:31:21,282 INFO started in 0.138s. 
employee-native_employee-qk-native.39.twa36dgqpso1@docker-desktop     | 2020-12-12 14:31:21,228 INFO started in 0.128s. 
employee-native_employee-qk-native.33.w5ledywf2b4c@docker-desktop     | 2020-12-12 14:31:23,622 INFO started in 0.227s. 
employee-native_employee-qk-native.40.7zodvmc36n5w@docker-desktop     | 2020-12-12 14:31:19,714 INFO started in 0.077s. 
employee-native_employee-qk-native.18.71oldf7t7eda@docker-desktop     | 2020-12-12 14:31:23,532 INFO started in 0.169s. 
employee-native_employee-qk-native.9.z8omrepehsul@docker-desktop      | 2020-12-12 14:31:28,047 INFO started in 0.124s. 
employee-native_employee-qk-native.24.l6aj846xvsjo@docker-desktop     | 2020-12-12 14:31:28,785 INFO started in 0.095s. 
employee-native_employee-qk-native.31.q0dy28vjqdav@docker-desktop     | 2020-12-12 14:31:24,959 INFO started in 0.095s. 
employee-native_employee-qk-native.8.5psoe7cnlu8l@docker-desktop      | 2020-12-12 14:31:22,673 INFO started in 0.177s. 
employee-native_employee-qk-native.64.pgljdiqvgpwc@docker-desktop     | 2020-12-12 14:31:21,292 INFO started in 0.139s. 
employee-native_employee-qk-native.58.2gcrmszzywjx@docker-desktop     | 2020-12-12 14:31:17,858 INFO started in 0.124s. 
employee-native_employee-qk-native.85.n50lutqb2z3t@docker-desktop     | 2020-12-12 14:31:22,690 INFO started in 0.141s. 
employee-native_employee-qk-native.94.za6ipeko10zq@docker-desktop     | 2020-12-12 14:31:28,370 INFO started in 0.089s. 
employee-native_employee-qk-native.55.vrfwqk9ebegn@docker-desktop     | 2020-12-12 14:31:23,123 INFO started in 0.104s. 
employee-native_employee-qk-native.7.vazz21hf8je0@docker-desktop      | 2020-12-12 14:31:25,689 INFO started in 0.139s. 
employee-native_employee-qk-native.17.71t01cp3wmm5@docker-desktop     | 2020-12-12 14:31:18,234 INFO started in 0.124s. 
employee-native_employee-qk-native.54.dm5bbnz0xgts@docker-desktop     | 2020-12-12 14:31:19,340 INFO started in 0.126s. 
employee-native_employee-qk-native.65.leyto2rjvh99@docker-desktop     | 2020-12-12 14:31:19,324 INFO started in 0.129s. 
employee-native_employee-qk-native.66.z5e5x3birtvi@docker-desktop     | 2020-12-12 14:31:24,995 INFO started in 0.103s. 
employee-native_employee-qk-native.37.gduhx5awz8gi@docker-desktop     | 2020-12-12 14:31:29,147 INFO started in 0.086s. 
employee-native_employee-qk-native.96.8cemovtvtew2@docker-desktop     | 2020-12-12 14:31:24,169 INFO started in 0.104s. 
employee-native_employee-qk-native.84.lttvfs7ytj4l@docker-desktop     | 2020-12-12 14:31:21,161 INFO started in 0.152s. 
employee-native_employee-qk-native.13.hr98xg83onn5@docker-desktop     | 2020-12-12 14:31:24,171 INFO started in 0.109s. 
employee-native_employee-qk-native.91.yz7n96nf6afr@docker-desktop     | 2020-12-12 14:31:22,609 INFO started in 0.127s. 
employee-native_employee-qk-native.32.bkaf4t65vdf0@docker-desktop     | 2020-12-12 14:31:19,364 INFO started in 0.103s. 
employee-native_employee-qk-native.67.ua3dscx1mi4c@docker-desktop     | 2020-12-12 14:31:19,395 INFO started in 0.106s. 
employee-native_employee-qk-native.60.g080pa4izn56@docker-desktop     | 2020-12-12 14:31:21,583 INFO started in 0.187s. 
employee-native_employee-qk-native.90.ytpi97tyxdwf@docker-desktop     | 2020-12-12 14:31:27,975 INFO started in 0.102s. 
employee-native_employee-qk-native.3.x9abreunoglt@docker-desktop      | 2020-12-12 14:31:22,029 INFO started in 0.116s. 
employee-native_employee-qk-native.63.wondy8ebmdk9@docker-desktop     | 2020-12-12 14:31:23,073 INFO started in 0.118s. 
employee-native_employee-qk-native.34.q7umcgrxjcul@docker-desktop     | 2020-12-12 14:31:24,291 INFO started in 0.097s. 
employee-native_employee-qk-native.75.iuer9kpp74o3@docker-desktop     | 2020-12-12 14:31:17,908 INFO started in 0.127s. 
employee-native_employee-qk-native.42.lmgskaj136ne@docker-desktop     | 2020-12-12 14:31:26,107 INFO started in 0.152s. 
employee-native_employee-qk-native.70.spyoy11st17e@docker-desktop     | 2020-12-12 14:31:16,387 INFO started in 0.091s. 
employee-native_employee-qk-native.22.w7g1f6njmjqq@docker-desktop     | 2020-12-12 14:31:28,723 INFO started in 0.116s. 
employee-native_employee-qk-native.72.xbewp76qqzs1@docker-desktop     | 2020-12-12 14:31:21,436 INFO started in 0.155s. 
employee-native_employee-qk-native.23.q1ko0fnm9ngi@docker-desktop     | 2020-12-12 14:31:28,352 INFO started in 0.093s. 
employee-native_employee-qk-native.71.6gkqbup60a2s@docker-desktop     | 2020-12-12 14:31:23,627 INFO started in 0.168s. 
employee-native_employee-qk-native.19.i6h2gd11h9i1@docker-desktop     | 2020-12-12 14:31:21,607 INFO started in 0.219s. 
employee-native_employee-qk-native.57.rsqna0ylvm5z@docker-desktop     | 2020-12-12 14:31:17,884 INFO started in 0.100s. 
employee-native_employee-qk-native.98.phf06msa3o09@docker-desktop     | 2020-12-12 14:31:16,127 INFO started in 0.102s. 
employee-native_employee-qk-native.12.so6fttuhl8x4@docker-desktop     | 2020-12-12 14:31:18,787 INFO started in 0.112s. 
employee-native_employee-qk-native.79.e7uwa2rtscbp@docker-desktop     | 2020-12-12 14:31:26,871 INFO started in 0.140s. 
employee-native_employee-qk-native.5.q9w4rrfp7v4d@docker-desktop      | 2020-12-12 14:31:26,122 INFO started in 0.163s. 
employee-native_employee-qk-native.95.yrut4opw5jze@docker-desktop     | 2020-12-12 14:31:18,874 INFO started in 0.096s. 
employee-native_employee-qk-native.35.odm5yf8xrdmg@docker-desktop     | 2020-12-12 14:31:22,493 INFO started in 0.148s. 
employee-native_employee-qk-native.30.mymcaml3ooac@docker-desktop     | 2020-12-12 14:31:17,311 INFO started in 0.100s. 
employee-native_employee-qk-native.36.smom7fzlomsp@docker-desktop     | 2020-12-12 14:31:12,154 INFO started in 0.092s. 
employee-native_employee-qk-native.44.pzwaxa97bu0n@docker-desktop     | 2020-12-12 14:31:16,461 INFO started in 0.092s. 
employee-native_employee-qk-native.97.cb92uw1698s4@docker-desktop     | 2020-12-12 14:31:27,194 INFO started in 0.087s. 
employee-native_employee-qk-native.53.r9rsqm2u1n74@docker-desktop     | 2020-12-12 14:31:23,174 INFO started in 0.093s. 
employee-native_employee-qk-native.86.f8oa7orsx1i9@docker-desktop     | 2020-12-12 14:31:19,563 INFO started in 0.078s. 
employee-native_employee-qk-native.61.q3fji0j07h7p@docker-desktop     | 2020-12-12 14:31:25,958 INFO started in 0.117s. 
employee-native_employee-qk-native.50.iwll43m5e7t9@docker-desktop     | 2020-12-12 14:31:22,926 INFO started in 0.116s. 
employee-native_employee-qk-native.74.cwi99uo73jyy@docker-desktop     | 2020-12-12 14:31:21,232 INFO started in 0.173s. 
employee-native_employee-qk-native.87.u8gk4iuporyu@docker-desktop     | 2020-12-12 14:31:24,564 INFO started in 0.089s. 
employee-native_employee-qk-native.28.f483520q35hw@docker-desktop     | 2020-12-12 14:31:27,137 INFO started in 0.101s. 
employee-native_employee-qk-native.69.o2s62t9mdmak@docker-desktop     | 2020-12-12 14:31:22,756 INFO started in 0.127s. 
employee-native_employee-qk-native.21.3ngz0qvjy6la@docker-desktop     | 2020-12-12 14:31:21,389 INFO started in 0.150s. 
employee-native_employee-qk-native.88.4atenqd63p98@docker-desktop     | 2020-12-12 14:31:27,454 INFO started in 0.122s. 
employee-native_employee-qk-native.46.sa2t94aetij6@docker-desktop     | 2020-12-12 14:31:26,861 INFO started in 0.202s. 
employee-native_employee-qk-native.4.ugz8tf692vx2@docker-desktop      | 2020-12-12 14:31:20,657 INFO started in 0.105s. 
employee-native_employee-qk-native.26.rlyb5mfheq0t@docker-desktop     | 2020-12-12 14:31:23,914 INFO started in 0.116s. 
employee-native_employee-qk-native.49.ckjnax4t7q4p@docker-desktop     | 2020-12-12 14:31:23,318 INFO started in 0.112s. 
employee-native_employee-qk-native.93.t7kwea02kz6k@docker-desktop     | 2020-12-12 14:31:25,945 INFO started in 0.121s. 
employee-native_employee-qk-native.15.mrhvtjc98ue7@docker-desktop     | 2020-12-12 14:31:18,870 INFO started in 0.108s. 
employee-native_employee-qk-native.2.p86v577pwvb9@docker-desktop      | 2020-12-12 14:31:23,467 INFO started in 0.186s. 
employee-native_employee-qk-native.14.3dd6lp3q2pq8@docker-desktop     | 2020-12-12 14:31:17,649 INFO started in 0.081s. 
employee-native_employee-qk-native.100.aspbsvgc3ocw@docker-desktop    | 2020-12-12 14:31:23,318 INFO started in 0.113s.

Startup time is still less than 1 second ( < 0.5 for the max): it will perfectly fit in a container world!

Above 300 containers, Swarm suffers issues (possibly network, you can always finger point the network) that makes it totally unresponsive so we scale up to 300 containers.

I don’t show logs for this case, they are similar to above and there are too many lines.

By scaling our service to 300, we never had a start time > 0.5s.

Summary of what has been tested:

Replicas # Spring Boot Quarkus JVM Quarkus native
10 30s 7s 0.135s
20 120s 17s 0.161s
35 25s 0.169s
100 0.227s
300 0.446s

Comparisons in a Kubernetes environment, using k3d

The goal of this part is to check if Quarkus really fits « perfectly » in Kubernetes, and have a look on how startup time can impact your service availability.

We use the excellent k3d from Rancher, which gives you a Kubernetes cluster in docker.

I recommend you to read this great post about k3d especially if you are not familiar with it.

What will we do?

  • Set up a Kubernetes cluster with 5 deployments:
    • One for the application in its Spring Boot form
    • One for the application in its Quarkus with old-fashioned JVM form
    • One for the application in its Quarkus with GraalVM form
    • Two Postgres databases (one for Quarkus, and the other for Spring Boot)
  • Set up 3 pods which check the availability of the service with a max timeout of 5 every 0.2 second
  • Kill 1 pod of each application every
    • 5 seconds in a script
    • 15 in the other

This simulates the case when your application crash every 5 or 15 seconds. For demonstration purposes, I didn’t create liveness and readiness probes – which is of course a very bad practice, but is useless in our demonstration. We kill the pod directly, to keep it simple.

Create, prepare your k3d cluster

I strongly recommend to use k3d in development environment, because it helps you build a Kubernetes ecosystem very easily.

A very simple k3d cluster can be created using the following command:

k3d cluster create quarkus-poc --switch-context

This is a one-node cluster, and we don’t mind because this is enough for the demo.
Since I don’t want my system to depend on public Docker registries, I used the k3d import image feature, to get images from my local Docker registry pushed in the registry of k3d:

k3d image import quarkus-employee-native:0.0.1-SNAPSHOT-scratch -c quarkus-poc

Now my k3d internal registry includes the Docker images which have been imported from my local registry.

Run deployments & services

Source code is available in the subdirectory quarkus-k3d of comparisons. Complete test scripts are available in this directory

  • create-kill-check.sh
  • create-kill-check-slow.sh

Create availability checkers

Since CronJob can execute only at the frequency of one execution per minute, this is not the right way for us to check availability on our cluster.

As we want to check every second, we just create a Pod responsible for checking availability, run them and delete them once the kill.sh script has finished the job.

These helpers use busybox image, with the following command – one for Spring Boot, one for Quarkus with standard jvm and one for Quarkus with Graalvm:

while(true) do echo "requesting..."; wget -T5 -O- http://qk-jvm-employee:8081/hello ; sleep 0.2; done

Run all together

Using scripts at the root of the k3d folder, all the steps above are running and will show you a result based on the availability checkers logs.

For the first test, which takes 5 minutes to complete, we deploy 1 replica fo each application and destroy pods every 15 seconds. If you remember previous sections, you know that the application will start:

  • in ~10 seconds for Spring Boot
  • in ~3 seconds for quarkus-jvm
  • in less than 1 second for quarkus-native

So with a kill every 15 seconds and checking every second, this should let Spring Boot a chance to restart properly and serve the requests.

Number of connection refused (i.e. probably the number of seconds in three minute the app has been available since we began to crash it): 
   Spring-boot:          375 /  762
   Quarkus standard JVM:  78 /  675
   Quarkus GraalVM:       25 /  762

While killing pods every 5 seconds, things get a little worse for JVM-based runtimes.

Number of connection refused (i.e. probably the number of seconds in three minute the app has been available since we began to crash it): 
   Spring-boot:         202 / 212
   Quarkus standard JVM: 46 / 164
   Quarkus GraalVM:      22 / 212

So we can have an unstable application that seems to be more stable than it is really for users, because of the startup time which is a key point for applications in Kubernetes.

Ratio of successful calls that have failed in the table below:

Scenario Spring Boot Quarkus JVM Quarkus native
Slow (15s) 51.2% 88.4% 96.7%
Fast (5s) 0.05% 72% 90%

It seems that Quarkus is a good candidate for container world:

  • low resource consumption
  • efficient
  • fast startup time

Conclusion

This post demonstrates that:

  • Quarkus helps us when we use with a containers’ orchestrator, to get a stable behavior even when the load increases on our application.
  • Quarkus help us to develop faster:
    • Many extensions are there to help you to have less boilerplate code.
    • The community is very reactive to help you if you have some issues.
  • Quarkus is very efficient and will not be as greedy as a Spring Boot application (which was the better way to do a lot of things up to now):
    • It will help you to decrease cost of your infrastructure as it needs less resources to do the same things than a classical platform.
  • Surface attack is minimalistic when we build it from scratch, and this is very important since developers must now think about security when developing applications (shift-left principle).
Spring Boot Quarkus JVM Quarkus native
Startup time ~10s ~2s < 0.1s
Build time ~18s ~20s ~4m by local GraalVM & ~6m using multistaging
Docker image size 237M 213M 68.2M>
RAM 386.5M 205.7M 15.96M
CPU 0.38% 0.32% 0.02%
Failures with one instance (restart every 60s & check every second) 19 7 0
Failures with one instance (restart every 20s & check every second) 99 37 1
Max running containers in parallel (8GB of RAM & Swarm) 20 35 300
Success ratio in kube with 1 replica (kill every 15 seconds & check every 0.2 second & requests timeout = 5s) 51,2% 88,4% 96,7%
Success ratio in kube with 1 replica (kill every 5 seconds & check every 0.2 second & requests timeout = 5s) 0.05% 72% 90%

So the conclusion of this post about Quarkus is clear:

Designing Cloud Native applications require flexibility, efficiency and security. Quarkus helps writing such applications and perfectly fits in a containers’ world, especially in Kubernetes, since startup time, resources and security have become a critical part.

  Edit this page