Quarkus.io: Moving from 200MB to 1MB; 10 Seconds to 100 MilliSeconds

Himanshu Kapoor
6 min readApr 24, 2019
Java as Cloud Native

Are you using Spring boot, containerised on Docker, orchestrated on Kuberenetes?

Are you on cloud and thinking to move over to golang or Rust as they start super fast with minuscule memory usage? But you still love JVM and especially Java.

First let’s see the problem. Here is a simple spring boot app calling a third party API(TvMaze) to fetch TV shows details. Running it on docker, a single instance would consume around few hundred MB of space on the disk, moreover the start up time would be around few seconds(roughly around 10 seconds)

Spring boot Reactive API’s stack has been used to create the service. Application is deployed on Netty which is a default server of Spring boot reactive stack.

Let’s start with the controller.

And the service contains code to call the client and of course some business logic

Client to call the REST API

For code details refer to https://github.com/himanshukapoor04/tv-shows-search-service

Assuming you have build the docker image, let’s run the service using

docker run -p 8080:6200 <image-name>:<docker image tag>

Application takes 10.075 seconds to start as shown in the following logs

2019–04–16 08:19:01.378 INFO 1 — — [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 6200
2019–04–16 08:19:01.426 INFO 1 — — [ main] c.e.tvshows.search.Application : Started Application in 10.075 seconds (JVM running for 12.062)

Not an ideal candidate if you want your services to be highly available and start quickly as 10 Seconds can be a huge waiting time for business critical applications.

How about memory? Let’ do a docker stats and check how much the container is consuming.

— — — — — — — — — — — — — — — — — — — — — — — —
| CONTAINER ID | CPU % | MEM USAGE / LIMIT | MEM % |
— — — — — — — — — — — — — — — — — — — — — — — —
| 8ea4a8dcc3c7 | 0.65% | 182.9MiB / 10.48GiB | 1.70% |
— — — — — — — — — — — — — — — — — — — — — — — —

182.9 MiB for a very simple service(without any traffic), which is not doing many things. If you are on cloud then running multiple instances of such service would not be a cost effective solution for a company.

As the system resources get more strained, for example, if you are running Kuberentes on-prem then the start up time shoots up to 40–50 sec range. For a more business intensive service, memory usage can also shoot up to 500MB- 1Gi. What’s the way out?

Quarkus comes to the rescue. Micronaut is another framework with the same capability, but will be describing it in the next article. Both the frameworks are built to support Java for building the cloud native applications. Both frameworks avoid the usage of reflection and do a compile time metadata processing, thus reducing the memory usage at run time. Along with GraalVM, the application start up time reduces to few milliseconds and memory consumption to few MB.

Migration to Quarkus is quite straightforward. It supports JAX-RS through Resteasy for exposing the Rest endpoints. Autowire annotation is replaced with Inject annotation. Service annotation is replaced with ApplicationScoped annotation.

Eclipse Microprofile is also used, so RestTemplate or WebClient could be replaced by an interface supporting RestClient annotations. Some of the code snippet would be as shown below

The Controller

The service

And finally the client

For full code refer https://github.com/himanshukapoor04/tv-show-search

Now let’s run the docker image.

Look out for startup time in the logs, as shown below

Apr 23, 2019 2:03:49 PM io.quarkus.runtime.Timing printStartupTime
INFO: Quarkus 0.13.0 started in 2.044s. Listening on: http://0.0.0.0:8080

Alright, so our start up time went to 2.044 seconds from 10.075 seconds. What about memory? Let’s do a docker stats

— — — — — — — — — — — — — — — — — — — — — — — —
| CONTAINER ID | CPU % | MEM USAGE / LIMIT | MEM % |
— — — — — — — — — — — — — — — — — — — — — — — —
| 79fd8356c215 | 0.42% | 83.15MiB / 10.48GiB | 0.77% |
— — — — — — — — — — — — — — — — — — — — — — — —

That 83.1 MB from 183 MB. So, we are saving 100 MB for one instance of our service.

Let’s make it even more faster and leaner.

Quarkus provides support to create native images using GraalVM. You could define a profile in pom.xml as mentioned below

After this, running it will be a two step process but instead let’s dockerize the process and build the native image during the docker build step. We can then create image using the executable created as part of this step.

So our docker file would look like

Let’s see what’s happening during the build process.

  1. First we are building runner jar using maven qurakus plugin.
  2. After that we invoke graalvm’s native image tool to create a native executable of the application. This is the step where bytecode is converted into native code. The output of this step is an executable file which we need to use in step 3.
  3. And finally this executable file is used to run on port 8080.

NOTE- Docker build might take more time and memory because of the native-image execution.

Now let’s run the docker image and see the difference in startup time and memory consumed.

2019–04–24 09:30:59,165 INFO [io.quarkus] (main) Quarkus 0.13.0 started in 0.162s. Listening on: http://0.0.0.0:8080

Application starts in 0.162s i.e. 162 milliseconds. From10 Sec on Spring boot to 162 milliseconds.

& the memory.

— — — — — — — — — — — — — — — — — — — — — — — —
| CONTAINER ID | CPU % | MEM USAGE / LIMIT | MEM % |
— — — — — — — — — — — — — — — — — — — — — — — —
| fcfc5bea14d1 | 0.00% | 1.531MiB / 10.48GiB | 0.01% |
— — — — — — — — — — — — — — — — — — — — — — —

Memory is 1.531 MB from 183 MB of Spring boot.

It looks good and fast but of course there are some caveats.

If you are using some old libraries which are not supported by Quarkus then you might end up in a loop of resolving reflection problems. Since lot of libraries do use reflection, there is a workaround for that too.

All the classes which are loaded dynamically can be marked using GraalVM’s substratevm library; to be identified by GraalVM while generating native image which otherwise would give error during runtime. Sample code for the same can look like

The resolution process can go very deep as you might be using too many libraries which do use reflection, hopefully both Quarkus.io and GraalVM add some support to avoid it.

Summary

Quarkus.io is a framework which took AoT approach which is completely against the reflection based frameworks like Spring. On top of that it provide support for some of the major frameworks like Microprofile, Vertex, Hibernate, Kafka, Apache Camel, Infinispan etc. GraalVM can be a bit challenging to setup but there is great support from the community. Moreover it is driven by Redhat. So if you still want to keep your Java skills intact and develop super fast cloud native applications then Quarkus can be a good option.

--

--