🔥

Quarkus + GraalVM (native-image) + Kotlin でハマったこと

2022/05/02に公開

想定読者

  • Java (JVM 言語) のネイティブ実行に興味のある方
  • Spring Framework 等のフレームワークの知識がある方

はじめに

Quarkus は非常に優れた Java (Kotlin) のフレームワークです。様々な用途に使用ができ、一般的にはその分野で一番採用率の高い Spring Framework ではまだサポートの弱い GraalVM での native-image ビルドをしたい時に採用するフレームワークだと思います。Quarkus はそのサポートが厚く、JVM で動かす場合も native-image ビルドして動かす場合も、同一コードで、あまり native-image のことを気にすること無く 実装することができます。

厄介なのは、JVM で動かしながら実装を進めていたところ、native-image にしたらなんか挙動がおかしい、ということがままあるということです。この記事では、そういった部分を含めてハマったことをいくつか列挙し、自分のメモ代わりとして残しておこうと思います。

この記事は Quarkus, GraalVM について非難したいというわけではありません。Quarkus, GraalVM は素晴らしい技術であり、これからの Java 界隈の可能性を広げてくれる、エンジニアにとって救世主ともいえる技術だと思います。だからこそ、現状を正しく認識する上で、どのようなところにハマるのかといのを、Java (JVM 言語) のフレームワークを使ってアプリを記述したことのある方に向けて、記事にする価値があると思って記載しています。

ハマったこと

更新がアグレッシブでよくバグる

Quarkus が積極的にアップデートが重ねられていることは、非常に好ましいことなのですが、GraalVM も非常に高頻度で更新され、アグレッシブに機能が取り込まれています。それ自体はとても良いことなのですが、GraalVM は native-image に変換するための肝となる技術で、その技術自体がアグレッシブに最適化されているため、度々その煽りを受けてバージョンアップの度に native-image の生成ができない、または native-image での動作が変わったなどは遭遇します。

直近だと、GraalVM の 22.1.0 がリリースされ、その時に native-image ビルドが動作しなくなるということがありました。理由としてはプロパティーの読み込みに問題が発生したというエラーが発生し、原因としては、Quarkus で設定ファイルを yaml 記述することができる Extension を導入しているとコケるという問題でした。もちろんその当時は、Quarkus ではまだ正式に GraalVM 22.1.0 に対応はしていないのですが、JVM 言語に慣れていると、なんとかなる思ってバージョンを最新に上げられると思いがちです。このような問題は JVM で実行している時には発生せず、さあデプロイするぞ、という時に気付いたりするので、非常に厄介です。他の問題にも言えることですが、必ずシナリオレベルでのテストを native-image ビルド実行でも作成し、定期的に確認できる CI 環境を構築することを強く推奨します。

Jackson の挙動が不審

Quarkus は Extension を組み合わせて機能を拡張していくフレームワークなのですが、その Extension は複雑に依存関係をもっており、それと native-image ビルドを組み合わせると、複雑怪奇な問題を踏むことがあります。その一つに Jackson の問題があります。

Quarkus はデフォルトでは、フレームワークのインターフェース定義を提供する Microprofile に従って、JAX-RS で Web サーバーを実装します。その際は、同様に Microprofile に従って、JSON のシリアライズ・デシリアライズに JSON-B というライブラリを使用するのですが、Quarkus の Extension の一つに、Spring Webがあります。これは、Web API の作成に広く利用されている Spring Framework のインターフェースを Quarkus でも用いることが出来るようにしたライブラリで、これを利用するためには、Jackson という JSON ライブラリに変更する必要があります。

この Jackson が曲者で、かなり内部実装が複雑になっているようで、native-image 実行時に問題を引き起こすことがわかっています。具体的には、Kotlin Extension を混ぜて Kotlin で記述した時には、クラスの構造が複雑になった場合に、デシリアライズに失敗する問題が報告されています。jacksonKotlinModule が入ってないのでは? と思われるかもしれませんが、Quarkus はちゃんとその Module のインストールを Kotlin Extension を入れた時にしてくれるので、それが問題でありません。

Quarkus の公式ホームページでは、クラスのフィールドを全部 nullable の型で定義して初期値を null にして、かつ全てのフィールドに @JsonProperty をつけて属性も明示的にしろ。という、半ばやけくそのようなワークアラウンドが記載されていましたが、個人的に色々試したところ、ある程度モデルが複雑になってしまうと、そのワークアラウンドを試したところで、同様のエラーが発生しました。モデルがある程度複雑になった時に発生する問題なので、他の問題にも言えることですが、必ずシナリオレベルでのテストを native-image ビルド実行でも作成し、定期的に確認できる CI 環境を構築することを強く推奨します。

Bean 生成のタイミングの差異

Quarkus では、native-image ビルド時にサーバーのセットアップをある程度してしまう処理が入っています。要するに、DI で注入するクラスを予め先にある程度静的に決定してしまい、native-image 実行時のオーバーヘッドを軽くするという処理になります。それ自体は非常に素晴らしい機能なのですが、ここで DI で注入するクラスを作成するために環境変数が必要な場合はどうすればいいのか? という問題が発生します。

結果的に、Quarkus には native-image を利用する際のプロパティーの設定タイミングが「ビルド時」と「実行時」の2パターン存在します。ビルド時のプロパティーは DI として注入するクラスの決定や、そのクラスの初期化や設定に使われるプロパティーで、実行時にもそのプロパティーが引き継がれます。実行時のプロパティーはその通り実行時に参照されるプロパティーになります。このように、複数のプロパティー設定タイミングが存在するため、各プロパティーがどのタイミングで使用されるかを意識する必要があります。JVM で実行する際は、DI 関連の処理は実行時のセットアップとして行われるため、この問題を意識する必要はありません。

この問題を意識したくない場合、 デプロイする環境ごとに、native-image ビルドをし、ビルド時に全てのプロパティーを決定してしまうという解決方法が一番ミスがなく楽です。 ですが、ネイティブビルドはそれなりに時間がかかるため、CI/CD 環境をちゃんと整備していない場合やや面倒です。同じバイナリを全ての環境で使いまわしたい場合は、この問題を意識しつつ開発することが必須です、避けては通れません。この問題も他と同様に、非常に問題に気付きにくいです。Extension を追加したら、その Extension の設定がビルド時に決定する、ということもしばしばあります。その場合は回避策は、その Extension 毎によって異なるので、なおのこと面倒です。

自分が引っかかった問題として、OpenTelemetryQuarkus Extension があります。この Extension はアプリケーションがトレースやメトリクス情報などを送信できるようにするものなのですが、そのトレースやメトリクスのメタ情報、例えば、送信したアプリケーション名や、Quarkus のバージョンなどは、ビルド時に決定します。しかしながら、そのアプリケーション名をデプロイする環境毎に変更したかったので、アプリケーションコード上で実行時に強引に上書きを行ったりなどして対処を行いました。このような問題は決して少なくなく、問題に遭遇した際にかなりのストレスになります。

結論

現状 (2022/5) の時点での Quarkus + GraalVM (native-image) + Kotlin はハマリポイントが JVM に慣れた人ほど想定外になりやすく、開発終盤で踏んでしまうと、かなりの痛手になりやすいと思います。この開発に限ったことではないですが、ともかく CI 環境を整え、ある程度再帰的な網羅テストができる環境を早期に整えてください。 どのコミットでバグが入り込んだかがわからないと、native-image ビルドでバグを踏んだ場合、その見極めから始めることになり、それが非常に困難です。

こんな結論ではありますが、Quarkus + GraalVM は非常に可能性を持った技術と思っていて、開発も非常に楽しかったです。今後のバージョンアップでより高機能に、より安定していってくれることに期待しています。

https://quarkus.io/

https://www.graalvm.org/

Discussion