Published in Blog
How to speed up Timefold Solver Startup Time by 20x with native images
Discover how to build a Spring native image and the benefits from doing so.
As more applications are moving towards cloud deployments, minimizing startup time is becoming more and more important. Read on to find out how to use Timefold Solver with Spring native images to reduce startup time in your application.
If you want to follow along, clone the Timefold quickstarts and change to the technology/java-spring-boot
directory, which is already set up to support Spring native images:
git clone https://github.com/TimefoldAI/timefold-quickstarts.git
cd timefold-quickstarts/technology/java-spring-boot
Setup
You can modify your existing Spring applications to support building Spring native images by modifying your configuration.
- Maven
-
Add
org.graalvm.buildtools:native-maven-plugin
to your build plugins:<build> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> </plugin> </build>
NoteIf you are not using spring-boot-starter-parent
, you would need to configure executions for theprocess-aot
goal from Spring Boot’s plugin and theadd-reachability-metadata
goal from the Native Build Tools plugin. - Gradle
-
Add
org.graalvm.buildtools.native
to your plugins:plugins { // ... id 'org.graalvm.buildtools.native' version '0.10.1' }
Building
Spring provides two ways of building a native image:
-
Using Docker
-
Using a locally installed GraalVM
Build using Docker
- Maven
-
Run
mvn -Pnative spring-boot:build-image
- Gradle
-
Run
gradle bootBuildImage
This will produce an image tagged as docker.io/library/${name}:${version}
where ${name}
is the artifactId
in Maven or the archivesBaseName
in Gradle.
You can then run the image using Docker:
docker run --rm -p 8080:8080 docker.io/library/docker.io/library/timefold-solver-spring-boot-school-timetabling-quickstart:1.0-SNAPSHOT
Build using locally installed GraalVM
- Maven
-
Run
mvn -Pnative native:compile
This will create an executable in
target
using the project’sartifactId
as the name. The native image can then be run directly:./target/timefold-solver-spring-boot-school-timetabling-quickstart
- Gradle
-
Run
gradle nativeCompile
This will create an executable in
build/native/nativeCompile
with the same name as the parent directory forbuild.gradle
. The native image can then be run directly:./build/native/nativeCompile/java-spring-boot
Benefits
The primary benefit of native images is the massively reduced startup time:
./target/timefold-solver-spring-boot-school-timetabling-quickstart
...
20:40:44.888 INFO [main ] Started TimetableSpringBootApp in 0.07 seconds (process running for 0.073)
Compared to running with a JVM:
java -jar target/timefold-solver-spring-boot-school-timetabling-quickstart-1.0-SNAPSHOT.jar
...
20:42:40.323 INFO [main ] Started TimetableSpringBootApp in 1.216 seconds (process running for 1.426)
The native image started 20 times faster! This means a freshly started Kubernetes pod can respond to its first requests 20 times faster when a native image is used, changing an abysmal ~1 seconds wait time to a much more acceptable ~0.1 seconds wait time.
However, it is not all roses, since a native image is unable to perform various profiling based optimizations that a JVM can perform, reducing the speed of compute bound problems (such as solving):
./target/timefold-solver-spring-boot-school-timetabling-quickstart
...
20:58:43.064 INFO [pool-5-thread-1] Solving ended: ... score calculation speed (124774/sec) ...
Compared to running with a JVM:
java -jar target/timefold-solver-spring-boot-school-timetabling-quickstart-1.0-SNAPSHOT.jar
...
20:56:17.441 INFO [pool-2-thread-1] Solving ended: ... score calculation speed (213780/sec) ...
In a native image, Timefold Solver ran about 42% slower compared to a JVM run. This means to get to the same solution as a JVM run, the native image would need to run almost twice as long!
Important
|
Make sure to use GraalVM JDK 22 or above to generate the native image, which fixes a major performance regression involving record hashCode and equals .
|
Conclusion
It is easy to modify your existing Spring applications to make use of native images, allowing you to massively reduce startup time and thus respond to requests quicker on newly spawned Kubernetes pods. However, using native images currently prevents profiling based JIT optimizations, significantly impacting performance for compute bound tasks such as solving. If you have long-running solving tasks, consider running the solver in a separate application, which will allow you to gain the startup time benefits without affecting the performance of the solver.