Use Spring Native and Save Money
In this post, we will analyze some results I got from a 3 service migration that I did and how using native images can improve hardware usage and save money.
Join the DZone community and get the full member experience.Join For Free
Spring Native is the way to compile Spring applications to native images with GraalVM without changing anything (or at least some stuff) in our code. How do we use it? Well, that's not the topic of this post; just follow this awesome getting started document!
In a company, every choice we do as software developers should be guided from a simple sentence: solving problems and saving money.
How we can do that? Optimizing resources used by our software.
In this post, we will analyze some results I got from a 3 service migration that I did during an internal company hackathon and how using native images can help us to improve hardware usage and save money as a consequence.
The service analyzed are 3 microservices developed using Apache Camel Elasticsearch, and Spring Boot.
Application at startup
|Image Size||623 MB||153 MB||~4x|
|Startup Time||11.707 seconds||0.452 seconds||~25x|
|Memory||346.7 MB||108.6 MB||~3x|
It’s clear that the native image is better than the JVM image:
- The image size is ~4 times less; this means that it is easier to transfer the image across a network and also tools that use to bill based on image size will cost less (e.g. some security analysis software). As a side note, the native image can be lower, but during the Hackathon, we didn’t have time to work on that.
- Normally we don’t consider the startup time; it is not so important but a reason to build native image is to use them in a serverless environment and they use to bill calculating the execution duration that is calculated from the time your code begins executing until it returns or otherwise terminates.
- Memory is important not only for the obvious performance reason but also because the price depends on the amount of memory you allocate to your serverless function or virtual machine.
Using Containermon we also got some performance metrics performing 1000 requests to the 3 services:
In the graphs above we can see also that the native image performs better from the memory and CPU consumption, even if the JVM memory consumption is more constant. Those test results should get with a grain of salt because it isn’t stressing so much the environment to have a meaningful result, but it is helpful to have a first idea of the performances.
With response time they are quite the same. Both Docker containers have much memory allocated so also the JVM can elaborate all the requests without any issue, but it needs more memory and more memory means more money, for instance in order to deploy our services we can use a micro instance on AWS instead of a small instance spending exactly the half price.
- t4g.small 2CPU 2 GB 0,0168$/h 147.168$/year
- t4g.micro 2CPU 1 GB 0,0084$/h 73.584$/year
It isn’t all fun and games. GraalVM uses a garbage collection algorithm; it is currently supporting:
- Serial GC: a non-parallel/concurrent stop and copy GC.
- G1 GC: a generational, incremental, parallel, mostly concurrent, stop-the-world, and evacuating GC. It aims to provide the best balance between latency and throughput, but it works only in the GraalVMenterprise version.
- Epsilon GC: no-op garbage.
Unfortunately, the modern garbage collector as ZGC and Shenandoah are not available. So, if you are running a monolith and you need to optimize the garbage collection you have a problem.
Now we are talking about the dark side of Spring Native, and GraalVM in general. When you develop your application you have to do it as it is a normal Java application, so the development cycle will not be influenced so much.
The first main issue you have to face is the total absence of a reflection mechanism, so if you or your framework is using it you have to describe how it has to behave using some annotation like TypeHint. You can have a taste of that reading this article of mine, but anyway I’ll write something in detail soon.
When you deploy your application you have to build the image and I have a piece of bad news, the building time could be 60 times slower.
On smaller applications, the deterioration is not so huge, but it is better to consider a worst-case.
What does it mean in terms of money? If you are using a CI system that bills you using the build time (like Travis CI) you could spend a lot of money.
But you don’t have to generate a native image at every build, you can generate it only when you need to deploy the image somewhere or when you merge a PR, or once a day. Everything depends on your testing and deployment strategy.
In my opinion, JVM is still a valid, strong, trustful environment where run our applications, but this doesn’t fit in modern challenges like Cloud Native and serverless development where heavy images and slow application startup can invalidate your architecture making all JVM based languages out of this game.
Why we should migrate to Cloud Native? Basically to save money and not because it is a buzzword. If we do that using a classic JVM application we can spend money and time without having the best result, and so, it’s better to use other languages. Using GraalVM we can be competitive and use our favorite JVM language in the Cloud Native world!
Published at DZone with permission of Davide Cerbo. See the original article here.
Opinions expressed by DZone contributors are their own.