A real microservice is a very slim and light program with only one function with own database (if it’s necessary) and a very small memory footprint… It’s possible in Java?
I’ve been working with Spring Framework for many years, and now with my team we’ve started to question it, Spring Boot in particular: is it the right choice to develop a microservice? No, because in our experience it’s too memory hungry.
I found that a basic Java application running atop Spring Boot would require a minimum of 1GB of RAM in order to run and that’s ok when you develop a middleware application, but in the microservices architecture this is very bad!
We noticed that Spring Boot applications deployed on CloudFoundry or OpenShift with k8s suffered an Out of Memory error and crash if they weren’t set to 1GB minimum.
We were looking for a new tool to help us to develop a real microservice with this technical specification:
- Java compliant
- without useless libraries
- small memory footprint
- fast to serve request
The right framework
After a long research for a good framework (I’ll explain it in an other post) we have chosen Spark (please don’t confuse with Apache Spark). This is the right Spark http://sparkjava.com/
Right click → new Maven Project → add only strictly necessary dependencies. Below a small excerpt of the pom.xml
Well, now we define Docker file correctly…
We have chosen “openjdk:8-jre-alpine” because this starter image is the smaller and lightest in terms of memory and size. If you want to improve a little bit more the performance, you can choose Java 11…but at the moment, unfortunately, this does not exist: you can use, in the future, “openjdk:12-jre-alpine” when Java12 will be released. (You can see the complete list here: https://hub.docker.com/_/openjdk/).
First of all, the weight: only 10MB jar!!! Fantastic! With SpringBoot we have always reached at least 30MB… Not so bad!
- Start-up time (develop): with our MCU library (here to find information) and Spark the startup time is very minimalist. You can do the “hello-world” GET method less in 1 minute…
- Start-up time: we were used to waiting almost 30–40 seconds… Now our wait time is less than 1 second.
- Lines Of Code: in the microservices architecture you should create a very small program with minimalist configuration, dependecies and only one functionality… With these premises we assume that the lines of code should be few. In Spring Boot this is not always true, because the framework tends to be wordy. With Spark, the LOC number is very low.
- Memory Footprint: you probably know that Java is memory greedy, but with the right configuration and optimization you can reach a good goal! In a Docker container with Spring Boot we were frustrated because it was very difficult to get under 500MB of RAM… Now we stay around 30~60MB of RAM.
- Maintainability: simple is better! One of the microservice goal is to divide et impera the main problem in a small n problems. Without difficult configurations, no dependencies, and a small code the maintainability is very simple.
- Reliability: we didn’t have any problems with our stress tests… The framework is very robust and resilient.
We have created 2 types of benchmark:
- Hello World — Simple “hello world” message serialized in a JSON response
- Compute Task — A medium complexity level compute task with JSON deserialization, ETL job and Object Serialization response
We have implemented this two benchmark types with:
- Spring Boot with Undertow Application Server
- Spring Boot Webflux
There are the results:
As you can see all frameworks are equivalent but with the increasing contemporary users number, Spark starts to suffer. Well, the right focus point is the memory footprint and not only the throughput.
- Spark memory footprint with this benchmark is irrelevant: 60 MB of Heap size and 35MB of average usage without any type of fine tuning.
With others frameworks the heap size is very big, for example with Spring Boot we have approximately 290–300 MB of heap (limited with -xmx JVM parameter)
The real usage is around 100–150MB, 4–5 times more heavy like Spark.
If you have to do a simple microservice the right choice is Spark, because:
- it’s really very light
- startup time is so fast
- there aren’t any useless classes/libraries
- the final jar weight is very light
Now you can focus only on development and scalability leaving it to Docker and Kubernetes without thinking about the memory.
For example: 2 Spark containers uses 70MB memory or less and serves more requestes than 1 Spring Boot container.
I would like to stress and express my gratitude for the cooperation to all my team in particular Luca Pompei a great Team Leader and Developer.