CRaCを用いたJavaアプリケーションの起動高速化
検索エンジンプロダクトを一緒に開発してた同窓会 Advent Calendar 2023 の9日目です。
Javaアプリケーションの起動を高速化する技術として、Application CDSやGraalVM Native ImageによるAOTコンパイルなどがあります。
CRaC(Coordinated Restore at Checkpoint) もまた、Javaアプリケーションの起動高速化を目的としたOpenJDKのプロジェクトです。CRaCはLinuxのCRIUを利用し、実行中のJavaアプリケーションの状態をスナップショットとして保存します。スナップショットからはアプリケーションを高速に復元することができます。
今回はSpring Bootアプリケーションのスナップショットを作成し、スナップショットからアプリケーションを起動してみます。
CRaCをサポートするJDKのインストール
今回は Liberica JDK with CRaC を利用します。
私は SDKMAN でインストールしました。
$ sdk install java 17.0.9.crac-librca $ sdk use java 17.0.9.crac-librca
確認します。
$ sdk current java Using java version 17.0.9.crac-librca
CRaCののドキュメント に従い、実行ユーザがCRIUを利用できるように lib/criu
のパーミッションを変更します。
$ sudo chown root:root jdk/lib/criu $ sudo chmod u+s jdk/lib/criu
helpが表示できれば準備OKです。
$ $JAVA_HOME/lib/criu --help
環境によってはSELinuxのインストールが必要かもしれません。
$ $JAVA_HOME/lib/criu --help /home/todokr/.sdkman/candidates/java/17.0.9.crac-librca/lib/criu: error while loading shared libraries: libselinux.so.1: cannot open shared object file: No such file or directory
$ ldd $JAVA_HOME/lib/criu ... libselinux.so.1 => not found ...
Spring Boot アプリケーションの用意
spring-boot-starter-web をベースにプロジェクトを作成します。
依存ライブラリに org.crac:crac を追加し、SpringがCRaCを利用できるようにします。
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.crac:crac") ... }
アプリケーションの初回起動と復元の差を際立たせるため、mainメソッド内にsleepを入れておきました。
@SpringBootApplication class SpringBootWithCracApplication fun main(args: Array<String>) { println("Starting app...") Thread.sleep(3000) runApplication<SpringBootWithCracApplication>(*args) }
コードの全体はこちらのリポジトリ にあります。
初回起動とスナップショット作成
アプリケーションをビルドして起動します。CRaCCheckpointTo
にはスナップショットを保存するディレクトリを指定します。
$ ./gradlew build $ java -XX:CRaCCheckpointTo=checkpoint -jar ./build/libs/spring-boot-with-crac-0.0.1-SNAPSHOT.jar Starting app... ... [main] c.e.s.SpringBootWithCracApplicationKt : Started SpringBootWithCracApplicationKt in 0.997 seconds (process running for 4.2)
初回起動はsleepの3秒込みで 4.2
秒かかりました。
jcmd
でスナップショットを作成します。スナップショット作成後、アプリケーションは停止します。
$ jcmd spring-boot-with-crac JDK.checkpoint 1710359: CR: Checkpoint ...
CRaCCheckpointTo
に指定したディレクトリ内にファイルがモリモリと作成されています。
これらは先ほどのアプリケーションのJVMメモリがダンプされたものです。
$ ls -l ./checkpoint core-1712983.img core-1712998.img core-1713046.img dump4.log core-1712984.img core-1712999.img core-1713047.img fdinfo-2.img ... core-1712994.img core-1713042.img core-1713068.img stats-dump
スナップショットからのアプリケーション起動
スナップショットから起動してみます。
$ java -XX:CRaCRestoreFrom=checkpoint [Attach Listener] o.s.c.support.DefaultLifecycleProcessor : Restarting Spring-managed lifecycle beans after JVM restore [Attach Listener] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '' [Attach Listener] o.s.c.support.DefaultLifecycleProcessor : Spring-managed lifecycle restart completed (restored JVM running for 60 ms)
アプリケーションが正常に起動したことがわかります。
ログにあるように、アプリケーションの起動にかかった時間はわずか 60ms
でした。
プロダクションでの利用にはCI/CDのや各種リソースのクローズ/再作成、スナップショットにシリアライズされた機密情報の管理など、考慮が必要な事項が多々ありますが、今後の発展が非常に楽しみな技術に思えました。