こんにちは。
今回は全文検索エンジン「Mujo」をSpring BootからQuarkusに変更したら、さまざまな点で大きく削減できた話をご紹介したいと思います。
念のため、あらかじめ申し上げますが、SpringとQuarkusの比較をして、互いに優劣をつけようというという類の話では一切ございません。
まず、「Mujo」の内部構造の話をしたいと思います。

これまでは、検索エンジンのコアとなる機能を純粋なSpringフレームワークで構築していました。
これを依存ライブラリとして利用し、「grpc-spring-boot-starter」で構築した別のアプリケーションで薄くラップして、gRPCサービスとして公開していました。
純粋なJavaアプリケーションですので、コンテナイメージにJREは必要ですし、コンテナをホストする環境によっては、クラスローディングがなかなかの重労働でした。

2023年9月のリリースを持ちまして「gprc-spring-boot-starter」でラップしていたアプリケーションをQuarkusベースに書き換えて、ネイティブな実行ファイルのみで動作できるように変更しました。
検索エンジンのコアとなる機能は、依然としてSpringフレームワークベースで構築しており、gRPCで機能を公開するガワとなる層が多少変更されても、問題なく動作するだろうとは思っていましたが、後述するとおり、いくつかの難題をクリアする必要はあったものの、この構成で弊社のインテグレーションテストを無事にパスすることができました。
大削減① コンテナイメージのサイズ
さて、表題のもろもろ大削減できた話に戻りますが、まず、コンテナイメージサイズが半分以下になりました。
$ docker images
mukeisoftllc/compact_full_search_engine_native latest eb895b35e843 8 days ago 301MB
mukeisoftllc/compact_full_search_engine_boot_amd64 latest 294c386e6cf3 2 months ago 642MB
どちらも「Mujo」のベースイメージですが、上がQuarkusを利用したネイティブバージョン、下が従来のJRE同梱バージョンです。
コンテナイメージのサイズが半分以下に削減できましたので、可搬性はより向上したと考えています。
大削減② 起動時のCPUとメモリの使用量
おそらくプロセスそのものの起動時間も短縮されているとは思いますが、「Mujo」は起動時に初期データをインデクシングする都合上、利用可能になるまでに少々時間がかかりますし、何をもってアプリケーションの起動とするかは解釈が分かれるところですので、ここではデモ用コンテナの起動時のメトリクスをお見せいたします。(デモ用のコンテナはAWS lightsailのMicro (1 GB RAM、0.25 vCPU)で稼働させています)


コンテナサービスのCPU性能が低いこともあり、どちらも起動直後は100%まで上昇していますが、左が従来のJREバージョンで、起動時のわずかな間、CPU使用率が高止まりしている状況がわかります。(おそらくですが、本来はもう少しCPU性能が必要です)
右はネイティブバージョンですが、ピークのあと速やかに下落しています。


次にメモリですが、左のJREバージョンでは起動時は50%近くまで上昇しています。
右がネイティブバージョンですが、起動時に30%程度しか消費していません。
なお、Lightsailのログ上では”Creating your deployment”が出力されてから、”Reached a steady state”が出力されるまで、ネイティブバージョンでは30秒程度短くなっています。
実行時のオーバーヘッドをビルド時に肩代わり
さて、良いこと尽くめに思えるネイティブ化ですが、QuarkusからSpringフレームワークベースのライブラリを利用するには、ビルド時に解決するべきいくつかの問題があります。
- 当たり前ですが、CDI(Jakarta EE)では、SpringのBeanを解決できない
- SpringのcoreにあるEnvironment依存のコードが書けなくなる
また、ネイティブビルドができるようになっても、実行時の互換性を保つための工夫が必要となります。
- Springの@PostConstructなど、Beanライフサイクルメソッドは明示的に呼んであげないとダメ
- ライブラリ(search-engine-core)のログが出力されなくなる
これらについて、次回以降、サンプルを交えながらご説明したいと思います。
それでは!