A true microservice is a very thin program with only one function with its own database (if necessary) and a very small memory footprint…… Is it possible in Java?

The problem

I’ve been using the Spring Framework for years, and now with my team I’m starting to question it, especially Spring Boot: is it the right choice to develop microservices? No, because in our experience, it’s too memory intensive.

I’ve found that basic Java applications running on Spring Boot require at least 1GB of RAM to run, and that’s fine when developing middleware applications, but that’s terrible in a microservice architecture!

We noticed that Spring Boot applications deployed on CloudFoundry or OpenShift with K8S experienced out-of-memory errors and crashed if they were not set to a minimum of 1GB.

We are looking for a new tool to help us develop true microservices using this technical specification:

  • Java standard compliant
  • lightweight
  • No useless libraries
  • Less memory
  • Quick service request

The right frame

Here is a short excerpt from POM.xml

<dependencies> <! -- Spark dependencies (the core) --> <dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-core</artifactId> The < version > 2.8.0 < / version > < / dependency > <! -- Logback version (I hope you want to log!) --> <dependency> <groupId>ch.qos. Logback </groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>Copy the code

So, now we define the Docker file…… correctly

FROM openjdk:8-jre-alpine LABEL maintainer="" \ name="my-microservice" \ description="my description" \ Mygroupid. myArtifactId="0.0.1-SNAPSHOT" ARG JAR_NAME="service-name-version.jar" ENV HTTP_PORT=4567 #Default Spark port EXPOSE ${HTTP_PORT} WORKDIR /usr/src/appCopy the code

We chose “OpenJDK: 8-jRE-alpine” because the start image is smaller and lighter in terms of memory and size. If you want to improve performance, you can choose Java 11… But for now, unfortunately, smaller images probably don’t exist, and you can use “openjdk: 12-jre-alpine” in the future when Java12 is released.

The results of

First, the Jar package size: a 10MB Jar! That’s great! With SpringBoot, we always reach at least 30MB…… It’s not that bad!

  • Startup time (development) : Using our MCU library (find information here) and Spark’s startup time is minimal. You can complete the “hello-world” GET method in 1 minute……
  • Startup time: We are used to waiting nearly 30-40 seconds…… Now our wait time is less than a second.
  • Line of code: In a microservice architecture, you should create a very small program with a minimalist configuration, dependencies, and only one function…… Given these assumptions, we assume that there should be very few lines of code. In Spring Boot, this is not always true because frameworks tend to be wordy. If Spark is used, the number of LOCs is very low.
  • Memory footprint: You probably know that Java is memory greedy, but with the right configuration and optimization, you can achieve a good goal! In Docker containers with Spring Boot, we were frustrated because it was hard to get less than 500MB of RAM…… Right now we keep about 30 to 60MB of RAM.
  • Maintainability: Simpler is better! One of the goals of microservices is to divide the major problem areas into n problems. There are no difficult configurations, no dependencies, and the code is small and very easy to maintain.
  • Reliability: there are no problems with our stress tests…… The framework is very powerful and resilient.

The benchmark

We created two types of benchmarks:

  • Hello World – A simple “Hello World” message serialized in the JSON response
  • Computing tasks – Medium complexity computing tasks with JSON deserialization, ETL jobs, and object serialization responses

We have implemented these two benchmark types:

  • Spring Boot using Undertow Application Server
  • Spring Boot Webflux
  • VertX
  • SparkJava

The results are as follows:

The frameworks are pretty much the same, but as the number of contemporary users increases, Spark is starting to suffer. So, the focus is on memory footprint and not just throughput.

  • The Spark footprint using this benchmark is irrelevant: a heap size of 60 MB and an average utilization of 35 MB, with no fine-tuning of any kind.

For other frameworks, the heap size is very large, for example with Spring Boot we have around 290-300 MB of heap (limited by the -xMX JVM parameter)

The actual usage is about 100-150MB, which is four to five times heavier than Spark.

conclusion

If you have to do a simple microservice, the right choice is Spark because:

  • It’s really light
  • The startup time is too fast
  • There are no useless classes/libraries
  • The final can weighs very little

For now, you focus on development and scalability, leaving it to Docker and Kubernetes and not memory.

For example, two Spark containers use 70MB or less memory and provide more request processing than one Spring Boot container.