Demo Application with CRaC and Loading Data in Memory

Coordinated Restore at Checkpoint (CRaC) is a JDK project, initiated by Azul. With CRaC, you can start Java programs with a shorter time to first transaction, combined with less time and resources to achieve full code speed. This is achieved by taking a snapshot (checkpoint) of a fully warmed-up Java process and launching one or more new JVMs from that snapshot.

I have been experimenting with CRaC on a Raspberry Pi in 2023 and blogged about, first when it didn’t work yet, and a second time when it did work perfectly.

Improving the startup time of Java applications is only one use case for CRaC. But in this post, I describe a different kind of example to illustrate how it can also help speed up applications that need to load a lot of data into memory. Below you can find the detailed explanation and I recorded the demo on a Raspberry Pi and published it on YouTube:

Demo Application

The sources of the demo application can be found on GitHub. They are based on another example from the CRaC repository at github.com/CRaC/example-jetty and the PostgreSQL integration is based on stackabuse.com/working-with-postgresql-in-java. The CSV files located in src/main/resources/data were downloaded from datablist.com.

Goal of the Application

This demo application shows a few use-cases:

In the code, a few CRaC-specific implementations can be found:

After starting the application, you can find all available endpoints on http://localhost:8080.

Running the Application

As I like to experiment with Java on the Raspberry Pi, and the full CRaC-functionality is only available on a Linux machine, I ran this application on a Raspberry Pi 4 with:

$ sdk install java 21.crac-zulu

Build and Run From JAR

I compiled the application on the Raspberry Pi and executed it with the following command which specifies the directory where the checkpoint must be created:

$ git clone https://github.com/FDelporte/crac-example.git
$ cd crac-example
$ mvn package
$ java -XX:CRaCCheckpointTo=cr -jar target/crac-example.jar

These are the durations needed for the HTTP endpoints to respond, as logged in the database. As you can see, the biggest file needs about 12 seconds to unzip and return the results.

duration=11800, description=Handled request for /files/organizations-1000000.csv
duration=8721, description=Data was converted to Java objects from organizations-1000000.csv
duration=3054, description=ZIP was unpacked from organizations-1000000.csv
duration=5197, description=Handled request for /files/organizations-500000.csv
duration=3609, description=Data was converted to Java objects from organizations-500000.csv
duration=1567, description=ZIP was unpacked from organizations-500000.csv
duration=1482, description=Handled request for /files/organizations-100000.csv
duration=1080, description=Data was converted to Java objects from organizations-100000.csv
duration=372, description=ZIP was unpacked from organizations-100000.csv
duration=373, description=Handled request for /files/organizations-10000.csv
duration=309, description=Data was converted to Java objects from organizations-10000.csv
duration=35, description=ZIP was unpacked from organizations-10000.csv
duration=222, description=Handled request for /files/organizations-1000.csv
duration=94, description=Data was converted to Java objects from organizations-1000.csv
duration=7, description=ZIP was unpacked from organizations-1000.csv
duration=0, description=Started from main
duration=0, description====================================================]

Once this is done, we can create a checkpoint by opening a second terminal and executing the following command:

$ jcmd target/crac-example.jar JDK.checkpoint

In the first terminal, you can see what’s happening during the checkpoint creation, and the application getting terminated at the end if the checkpoint creation was successful.

Oct 17, 2023 7:21:29 PM jdk.internal.crac.LoggerContainer info
INFO: Starting checkpoint
17/10/2023 19:21 | ServerManager                       | beforeCheckpoint     | INFO     | Executing beforeCheckpoint
2023-10-17 19:21:29.978:INFO:oejs.AbstractConnector:Attach Listener: Stopped ServerConnector@77a98a6a{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
17/10/2023 19:21 | DatabaseManager                     | beforeCheckpoint     | INFO     | Executing beforeCheckpoint
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/log4j-api-2.20.0.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/log4j-core-2.20.0.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/checker-qual-3.31.0.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/postgresql-42.6.0.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/crac-1.4.0.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/jetty-io-9.4.51.v20230217.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/jetty-util-9.4.51.v20230217.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/jetty-http-9.4.51.v20230217.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/javax.servlet-api-3.1.0.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/dependency/jetty-server-9.4.51.v20230217.jar is recorded as always available on restore
Oct 17, 2023 7:21:31 PM jdk.internal.crac.LoggerContainer info
INFO: /home/crac/crac-example/target/crac-example.jar is recorded as always available on restore
Killed

Run From Checkpoint

Our checkpoint has been created in the cr directory, so we can now restart it with the following command.

$ java -XX:CRaCRestoreFrom=cr

17/10/2023 19:22 | DatabaseManager                     | afterRestore         | INFO     | Executing afterRestore
17/10/2023 19:22 | DatabaseManager                     | initConnection       | WARN     | Setting up database connection
17/10/2023 19:22 | DatabaseManager                     | initConnection       | INFO     | Database connection status: {ApplicationName=PostgreSQL JDBC Driver}
17/10/2023 19:22 | ServerManager                       | afterRestore         | INFO     | Executing afterRestore
2023-10-17 19:22:48.800:INFO:oejs.AbstractConnector:Attach Listener: Started ServerConnector@77a98a6a{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}

As you can see, within a second the afterRestore methods get executed, meaning the application is up-and-running in the same state as it was just before the checkpoint was created. As unzipping and converting the CSV files into Java objects was already done, the HTTP endpoint can respond immediately. For the largest file this means a response in 7 milliseconds instead of over 10 seconds.

duration=7, description=Handled request for /files/organizations-1000000.csv
duration=7, description=Handled request for /files/organizations-500000.csv
duration=6, description=Handled request for /files/organizations-100000.csv
duration=8, description=Handled request for /files/organizations-10000.csv
duration=15, description=Handled request for /files/organizations-1000.csv
duration=0, description=Reopened DB connection after restore
duration=0, description====================================================

Conclusion

Of course, this time-consuming CSV-reading process is not a typical use-case, but it’s a nice illustration of how time-consuming processes can be stored in a checkpoint.

In December ‘23, I shared the “Things I learned On OpenJDK While Experimenting With CRaC for Fast Java Startup” at the Brussels Java User Group (BruJUG). The complete session has been recorded and is available on YouTube: