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:
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:
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.
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):
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:
In order to compare various approaches, we have a look at 3 key metrics:
Spring Boot – Startup time: ~10 seconds
Quarkus JVM – Startup time: ~2-3 seconds
Quarkus native – Startup time < 0.1 second as shown above
Spring Boot | Quarkus JVM | Quarkus native | |
---|---|---|---|
Startup time | ~10s | ~3s | <0.1s |
Build time | ~18s | ~20s | ~4m by local GraalVM ~6m using multistaging |
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.
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:
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)
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:
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.
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 |
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.
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.
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.
Source code is available in the subdirectory quarkus-k3d of comparisons. Complete test scripts are available in this directory
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
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:
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:
This post demonstrates that:
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.