Running a Microservice in Quarkus on GraalVM

Iain Porter
5 min readSep 1, 2020

--

Photo by Mathew Schwartz on Unsplash

This is the fifth part of a series on building a microservice in Quarkus. The other parts were:

In this part we are going to deploy the service running in GraalVM.

GraalVM

GraalVM is a game-changing technology that has only been production ready since 2019. It is a Java VM that contains a JIT compiler and supports building of native images allowing AOT (Ahead Of Time) compilation of java applications. The executable file built by the native image does not run on a JVM but as a platform-specific native application. The binary image contains all of the necessary libraries and dependencies significantly reducing the startup and execution time.

Installing GraalVM

You can get instructions for downloading GraalVM for your OS of choice here.

Next configure the runtime. Here is how you would do it for macOS

export GRAALVM_HOME=/Library/Java/JavaVirtualMachines/graalvm-ce-java11-20.1.0/Contents/Home

For building native images you will need to install the native image tool

$ {GRAALVM_HOME}/bin/gu install native-image

Once you have installed the native image tool you can create native image binaries using the tool

$ native-image my-app.jar

This creates a native binary in the current directory. You can now invoke the binary as follows:

$ ./my-app

Building the Native Image

You can follow along with the code by pulling the branch for this article

git clone git@github.com:iainporter/sms-service.git
cd sms-service
git checkout part_five

Up until now we have built the SMS microservice to run in a JVM. To run it as a native image we need to either pass an argument to maven on the command line:

mvn install -Dquarkus.package.type=native

Or we can add a profile and use the same argument which also allows us to run integration tests against the native image. Before we do that however let’s add a docker file to build a minimal image in which to run the native executable.

FROM registry.access.redhat.com/ubi8/ubi-minimal:8.1
WORKDIR /deployments/
COPY --chown=1001:root target/*-runner /deployments/application
EXPOSE 8080
USER 1001
CMD ./application -Dquarkus.http.host=0.0.0.0

The profile in the pom

Now we can install the executable with the maven command

mvn clean install -Pnative

It is here that we run into trouble. In order to build the native executable the GraalVM compiler needs to run a static analysis of the code to perform ahead of time compilation. It has to make a closed world assumption about the code, meaning that all classes have to be available at compile time. No dynamic class loading at runtime or java agents such as JMX, and the use of reflection is limited. All of these limitations are what make the application fast to boot so compromises have to be made. Quarkus is built to run on GraalVM and is optimised for that purpose.

The SMS microservice makes use of the OKHttp client that falls over at compilation time:

Error: No instances of sun.security.provider.NativePRNG are allowed in the image heap as this class should be initialized at image runtime. To see how this object got instantiated use -H:+TraceClassInitialization.
Detailed message:
Trace: object java.security.SecureRandom

There are a bunch of properties that you can use to tweak the compiler behaviour but in this instance it gives us an opportunity to rip out the rest client and use the rest client in Quarkus which won’t have compilation issues.
The Rest client interface can be found here and the implementation here. Once we have replaced the rest client the application compiles to a native executable.

Memory Footprint and Boot Time

Now that we can build the native application we can compare the memory size and startup time. The normal JVM image size is 559MB

Image size for JVM build

The startup time for the JVM image is over 15s

Boot time for JVM image

The native image size is less than half of the JVM image

The startup time is lightening fast. In fact it is so quick that is starts up before the postgres database is ready and so fails to start. To fix this we need to add a wait utility to the container

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait
RUN chmod +x /wait
EXPOSE 8080
USER 1001
CMD /wait && ./application -Dquarkus.http.host=0.0.0.0

We make use of this in the docker-compose file

environment:
WAIT_HOSTS: postgres-db:5432

The sms service will wait until postgres is available until starting up. When it does start up the result is less than 2 seconds.

This might seem like a long time but consider that the service has to connect to the database, run several flyway migrations, as well as set up the debezium connector to tail the database logs and set up kafka listeners. Compared to the JVM image it is incredibly fast.

The code for this whole series on building a production-ready microservice in Quarkus is available here.

The other parts in this series are:

--

--

Iain Porter

Technology pathfinder who likes to code when not on his bike