This guide describes how to create a CRaC snapshot for a Helidon MP application.

Introduction

CRaC - Coordinated Restore at Checkpoint

Note
CRaC support is a preview feature. The feature shown here is subject to change, and will be finalized in a future release of Helidon.

What You Need

For this 10 minute tutorial, you will need the following:

Linux/x64 or Linux/ARM64

While CRaC snapshotting can be simulated on MacOS or Windows, full CRaC functionality is only available on Linux/x64 and Linux/ARM64.

Maven 3.8+

Helidon requires Maven 3.8+.

Azul Zulu JDK CRaC 21+

Zulu Warp CRaC engine allows snapshotting without elevated privileges

Install JDK with CRaC support

There are two JDK builds with CRaC support as of now to choose from.

In this example we will use Azul implementation with Warp CRaC engine. Warp CRaC engine allows creating snapshots without elevated privileges. That not only simplifies the example, but it is very practical for K8s usage.

Use SDKMAN! to install Azul JDK with Warp CRaC engine:

sdk install java 23.0.1.crac-zulu

Generate the Project

Generate the project using the Helidon MP Quickstart Maven archetype.

mvn -U archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-mp \
    -DarchetypeVersion=4.3.0-SNAPSHOT \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-mp \
    -Dpackage=io.helidon.examples.quickstart.mp

The archetype generates a Maven project in your current directory (for example, helidon-quickstart-mp). Change into this directory and build.

cd helidon-quickstart-mp

Add dependency for Helidon CRaC support to pom.xml. This allows Helidon to properly close and reopen resources which would normally break snapshot creation.

<dependency>
    <groupId>io.helidon.integrations.crac</groupId>
    <artifactId>helidon-integrations-crac</artifactId>
</dependency>

Build the project.

mvn package

Check if you are using Java build with CRaC support.

➜  helidon-quickstart-mp java -version
openjdk version "23.0.1" 2024-10-15
OpenJDK Runtime Environment Zulu23.30+13-CRaC-CA (build 23.0.1)
OpenJDK 64-Bit Server VM Zulu23.30+13-CRaC-CA (build 23.0.1, mixed mode, sharing)

At this point you can run the application using the CRaC aware JVM:

java -XX:CRaCEngine=warp -XX:CRaCCheckpointTo=./target/cr -jar target/helidon-quickstart-mp.jar
Tip
If you hit Unrecognized VM option at this point, check if you are using correct JVM with CRaC support.

You should see in the output that Helidon MP has started with CRaC feature enabled.

Started all channels in 10 milliseconds. 1937 milliseconds since JVM startup. Java 23.0.1
Server started on http://localhost:8080 in 1941 milliseconds (since JVM startup).
Helidon MP 4.3.0-SNAPSHOT features: [CDI, CRaC, Config, Health, Metrics, Open API, Server]

In another shell test an endpoint:

curl -X GET http://localhost:8080/greet

The application should respond with {"message":"Hello World!"}

For more information about the Quickstart application and other endpoints it supports see the Helidon MP Quickstart Guide.

Creating snapshot

In another shell trigger the snapshot creation with jcmd command JDK.checkpoint:

jcmd $(jcmd | grep helidon-quickstart-mp.jar | awk '{print $2}') JDK.checkpoint
warp: Checkpoint 138991 to ./target/cr
warp: Checkpoint successful!
[1]    138991 killed     java -XX:CRaCEngine=warp -XX:CRaCCheckpointTo=./target/cr -jar
➜  helidon-quickstart-mp ls -la ./target/cr
total 124M
-rw------- 1 frank frank 124M Feb  1 19:12 core.img

Restoring from snapshot

Run following command to restore your application from saved snapshot.

java -XX:CRaCEngine=warp -XX:CRaCRestoreFrom=./target/cr
Tip
If you hit Unrecognized VM option at this point, check if you are using correct JDK with CRaC support.

Expected output shows that application restore from snapshot is drastically faster than previous start.

➜  helidon-quickstart-mp java -XX:CRaCEngine=warp -XX:CRaCRestoreFrom=./target/cr
warp: Restore successful!
[0x501ce1b2] http://0.0.0.0:8080 bound for socket '@default'
Restored all channels in 3 milliseconds. 43 milliseconds since JVM snapshot restore. Java 23.0.1

Yep, it starts fast. You can exercise the application’s endpoints as before.

Multi-stage Docker build

Build Docker image with pre-warmed snapshot.

Tip
For this example you don’t need Linux OS but docker environment is needed.

Create Dockerfile.crac in your project folder with following content.

# syntax=docker/dockerfile:1.7-labs
ARG BASE_IMAGE=azul/zulu-openjdk:23-jdk-crac-latest
FROM $BASE_IMAGE AS build
RUN apt-get update && apt-get install -y maven

WORKDIR /helidon

# Create a first layer to cache the "Maven World" in the local repository.
# Incremental docker builds will always resume after that, unless you update
# the pom
ADD pom.xml .
RUN mvn package -Dmaven.test.skip -Declipselink.weave.skip

# Do the Maven build!
# Incremental docker builds will resume here when you change sources
ADD src src
RUN mvn package -DskipTests

FROM build AS checkpoint
ENV ENDPOINT=http://localhost:8080/simple-greet
RUN apt-get update && apt-get install -y curl siege
ENV PATH="$PATH:$JAVA_HOME/bin"

# Copy the binary built in the 1st stage
COPY --from=build /helidon/target/helidon-quickstart-mp.jar ./
COPY --from=build /helidon/target/libs ./libs

# We use here-doc syntax to inline the script that will
# start the application, warm it up and checkpoint
RUN <<END_OF_SCRIPT
#!/bin/bash
java -XX:CPUFeatures=generic -XX:CRaCEngine=warp \
    -XX:CRaCCheckpointTo=./cr -jar ./helidon-quickstart-mp.jar &
PID=$!
# Wait until the connection is opened
until curl --output /dev/null --silent --fail $ENDPOINT; do
    sleep 0.1;
done
# Warm-up the server by executing 100k requests against it
siege -c 1 -r 100000 -b $ENDPOINT
# Trigger the checkpoint
jcmd ./helidon-quickstart-mp.jar JDK.checkpoint
# Wait until the process completes, returning success
# (wait would return exit code 137)
wait $PID || true
END_OF_SCRIPT

FROM $BASE_IMAGE
ENV PATH="$PATH:$JAVA_HOME/bin"
WORKDIR /helidon

# Copy checkpoint creted in the 2st stage
COPY --from=checkpoint /helidon/target/helidon-quickstart-mp.jar ./
COPY --from=checkpoint /helidon/target/libs ./libs
COPY --from=checkpoint /helidon/cr ./cr
CMD [ "java", "-XX:CRaCEngine=warp", "-XX:CRaCRestoreFrom=/helidon/cr" ]
Tip
This does a full build inside the Docker container. The first time you run it, it will take a while because it is downloading all of the Maven dependencies and caching them in a Docker layer. Subsequent builds will be much faster as long as you don’t change the pom.xml file. If the pom is modified then the dependencies will be re-downloaded.

Build the application, notice that warmup and snapshot of the application is created during build time in the 2nd stage. For warming up the siege load testing utility is used. Dockerfile is based on Radim Vansa’s article introducing Warp CRaC engine.

docker build -t helidon-quickstart-mp-crac -f Dockerfile.crac .

Start the application directly from snapshot created at build time.

docker run --rm -p 8080:8080 helidon-quickstart-mp-crac:latest

Again, it starts fast. You can exercise the application’s endpoints as before.