より高速なビルドに向けて
Quarkusを最初にリリースしたとき、業界はマイクロサービスに大きく焦点を当てており、それが私たちの主なターゲットでした。しかし、Quarkusは、既存のアプリケーションをよりモダンなランタイムに移行する場合でも、新しいアプリケーションをゼロから構築する場合でも、大規模なモノリスにも完全に適しています。
Quarkusは常に大規模なアプリケーションを処理できましたが、特にビルド時間に関しては、この分野で最近大幅な改善を行いました。この記事では、これらの改善点の一部を詳しく説明します。
物語の始まり
昔々、私は大規模なモノリスを構築するためのSpring BootとQuarkusを比較した記事 (フランス語で書かれているため、フランス語を話さない方には申し訳ありませんが、結果はほぼ自明です) に出会いました。
その記事では、同じ機能を実装するSpring BootアプリケーションとQuarkusアプリケーションを比較し、両方のアプリケーションを作成するためのジェネレーターも含まれていました。生成されるアプリケーションはシンプルで、いくつかのエンティティ、RESTサービス、典型的なCRUDエンドポイントを備えています。非常に興味深かったのは、エンティティとサービスの数を簡単にスケールアップして、両方のフレームワークがより大規模なアプリケーションをどのように処理するかを確認できたことです。
この種のフィードバックは本当に貴重です。改善すべき領域を特定するのに役立つだけでなく、それらを探求するための再現可能な方法も提供してくれるからです。
予想どおり、Quarkusはメモリー消費と起動時間で優れていました。しかし、ビルド時間はSpring Bootよりも著しく高かったです。これもまた驚くことではありませんでした。結局のところ、Quarkusはより多くの作業をビルド時に移行します。しかし、その差はそれでも詳しく調べるに値するほど大きかったのです。
手短に言えば、私たちは調査し、大幅に改善しました。
謝辞
まず、元の記事の著者であるSpaceFox氏に、執筆とこのような有用なジェネレーターを提供していただいたことに対し、感謝いたします。
Quarkusの世界ではよくあることですが、この道のりにおいて私は一人ではありませんでした。したがって、コード、議論、レビュー、洞察、フィードバックを通じて貢献してくださったすべての方々に感謝したいと思います。アルファベット順に、Tamás Cservenák、Sanne Grinovero、Martin Kouba、David Lloyd、Alexey Loubyansky、Matej Novotny、Yoann Rodière、Ladislav Thon (もし漏れがありましたら、お知らせください!) に感謝します。
これは主要なQuarkusの作業を対象としていますが、後述するように、 SmallRye OpenAPIプロジェクト にもいくつかの改善が加えられました。これらの貢献に対して、Mike Edgar氏とMartin Panzer氏に感謝いたします。
より高速なビルドへの道のり
この種の道のりには、当然ながらプロファイリングやフレームグラフを見つめる作業がつきものです。ホットスポットを見つけるだけでなく、自分の変更が実際に状況を改善するのか (願わくば正しい方向に!) 評価することも重要です。Javaの世界では、 Async Profiler のような強力なツールをそばに持つ幸運に恵まれてきました。
他の最適化の取り組みと同様に、賢明に戦いを選択し、避けられないトレードオフを慎重に検討することが鍵となります。
コード最適化
ビルドプロセスの様々な部分を最適化するために多大な努力が注がれました。メモリー割り当ての削減、アルゴリズムとデータ構造の最適化がその大きな部分を占めていました。
この作業により、Quarkus、Jandex、さらにはByteBuddyにわたる多数のプルリクエストが生まれました。
並列化
私たちのビルドプロセスは既に大規模に並列化されていますが、Hibernate ORMプロキシの生成など、並列化されていなかったいくつかの領域を特定しました。私たちはそれを修正しました。
もう一つの改善点は、大規模なJARアーカイブの作成でした。これは、大量のリソースを読み取り、圧縮することを含むため、本質的に低速であり、I/OとCPUの両方を大量に消費します。これまで、私たちはZipFileSystemを使用して、シングルスレッドでリソースを一つずつ追加しながらJARを構築していました。それ以来、Commons Compressが提供する並列圧縮サポートを使用するように切り替えました。
この変更には、JARアセンブリコードのかなりの大規模なリファクタリングが必要でしたが、それだけの価値は十分にありました。
無駄をなくす
|
他のすべての最適化はGradleビルドにも同様にメリットをもたらしますが、以下の変更はMavenのデフォルトライフサイクルに関連するため、Mavenに固有のものです。 |
これらすべての最適化により、quarkus-maven-plugin の様々なゴールに費やされる時間は、参照バージョン3.25.1と比較して半分に短縮されました。 これは素晴らしいことですが、サンプルアプリケーションのビルドには依然として約2分かかり、これは望ましい時間よりも長いです。
一歩引いて、全体像を見る時が来ました。
Quarkusアプリケーションの場合、主に次の2つの理由で独自のJARを構築します。
-
追加のリソースとメタデータを含める必要があります。
-
起動時間を改善するために設計されたカスタムJARパッケージを導入しました。
これは、Commons Compressが提供する並列圧縮サポートを活用することで最適化したプロセスです。
しかし、Mavenを使用する場合、Quarkusアプリケーションは依然として従来の jar Mavenプロジェクトです。これは jar パッケージングの標準ライフサイクルに従うため、Mavenは maven-jar-plugin を使用して従来のJARもビルドします。
一歩引いて考えると、実際にはこのJARは99%のケースで必要ありません。したがって、構築を避けるべきです(ただし、絶対に必要な場合はその柔軟性を維持します)。
Quarkus 3.31では、独自のライフサイクルを持つ quarkus パッケージングを導入しました。このパッケージングは、Quarkusアプリケーションモジュール自体にのみ使用されることを意図しています。これにより、quarkus-maven-plugin のゴールが自動的にバインドされ、pom.xml のボイラープレートが減り(新しいゴールが追加されても変更は不要)、さらに重要なことに、maven-jar-plugin の実行も maven-install-plugin もバインドされません。これにより、大規模なアプリケーションのビルドが大幅に高速化され、すべてのアプリケーションに全体的なメリットをもたらします。
新しく生成されるアプリケーションでは、この新しい quarkus パッケージングがデフォルトになります。
3.31がリリースされたら、既存のアプリケーションを新しいパッケージングに切り替えるために、以下の変更を適用することもできます。
diff --git a/pom.xml b/pom.xml
index 98660b8..3c60220 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,6 +5,7 @@
<groupId>fr.spacefox.perftests.quarkus</groupId>
<artifactId>perftests-quarkus</artifactId>
<version>1.0.0-SNAPSHOT</version>
+ <packaging>quarkus</packaging> (1)
<properties>
<compiler-plugin.version>3.14.0</compiler-plugin.version>
@@ -66,16 +67,6 @@
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions> (2)
- <executions> (3)
- <execution>
- <goals>
- <goal>build</goal>
- <goal>generate-code</goal>
- <goal>generate-code-tests</goal>
- <goal>native-image-agent</goal>
- </goals>
- </execution>
- </executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
| 1 | デフォルトの jar パッケージングの代わりに quarkus パッケージングを使用します。 |
| 2 | これは重要であり、生成されたプロジェクトにはかなり前から存在しています。まだ存在しない場合は追加してください。 |
| 3 | ゴールは削除してください。これらは自動的に処理されるため、二重に実行する必要はありません。 |
その影響のアイデアを示すと、私たちのサンプル大規模アプリケーションでは、この変更だけでビルド時間が2分から37秒に短縮されました。
その後の展開
SmallRye OpenAPI
2025年11月、私は最初のQuarkusコミュニティコールでこの作業の一部を発表しました(コミュニティコールの詳細については こちら を参照してください)。
このプレゼンテーションに続いて、私たちの常連コミュニティ貢献者の一人であるMartin Panzer氏が、大規模アプリケーションのビルドがいかに遅くなるかを示す SmallRye OpenAPIプロジェクトの課題 を提起しました。彼は堅牢な再現手順を提供し、これによりMike Edgar氏が、SmallRye OpenAPIが全体のビルド時間にもたらす貢献を大幅に削減するいくつかの改善を実装することができました。
これらの変更はQuarkusのビルド時間に直接的な利益をもたらしますが、SmallRye OpenAPIは他のランタイムでも使用されているため、より広範なJavaエコシステムにも良い影響を与えるでしょう。
要点はシンプルです。何かおかしいことに気づいたら報告してください。私たちはそれを改善できるかもしれません。
悪名高き ClassTooLargeException
Quarkusでは、かなりの量のバイトコードを生成します。大規模なアプリケーションでは注意が必要で、規模が大きくなると、生成されたバイトコードが特定の制限(例えば、メソッドサイズやクラスサイズの制限)に達する可能性があります。
元のフランス語の記事では、特定の規模でこのような制限に達したことが述べられていました。公平に言えば、その規模はすでにかなり大きいものでしたが、あるクラスがたまたま限界を超えたというだけで、任意の制限に遭遇するのは理想的ではありません。
私たちは最近この制限を緩和し、Quarkusはより大規模なアプリケーションを扱えるようになりました。この改善もQuarkus 3.31で利用可能になります。
Java 25
最近、私たちはバイトコード生成のほとんどを、Class-File API を基盤とする Gizmo 2 を使用するように書き換えました。この作業はまだ進行中ですが、当社の CDI 実装である ArC などの主要コンポーネントはすでにこれに依存しています。
Java 17 と 21 との互換性を維持するため、現在は Class-File API のバックポートを使用していますが、Class-File API は基盤となる JDK の一部のクラスに依存しています。これらのクラスのいくつかは Java 25 で大幅な最適化が施されており、その結果、Java 25 で実行するとバイトコード生成のパフォーマンスが著しく向上します。
Quarkus 3.31 は Java 25 の完全なサポートを提供する予定であるため、これらのパフォーマンス向上を活用し、アプリケーションのビルドにこれを使用することをお勧めします。
まとめ
Quarkus は常に開発者エクスペリエンスの限界を押し広げてきました。私たちは開発モードを導入し、Dev Services のコンセプトを開拓するなど、多くのことを実現してきました。
しかし、開発者エクスペリエンスを向上させることは、時には基本に立ち返ることを意味します。つまり、ビルド時間です。
まさにそこに私たちは注力しました。Quarkus アプリケーションをより速く(そしてより環境に優しく!)ビルドすることを楽しんでいただければ幸いです。
もしビルドプロセスの改善にさらなる機会を見つけられた場合は、遠慮なく課題をオープンしてください。私たちは常に新しいアイデアを歓迎しています。
参加のお誘い
私達は皆様からのフィードバックに重きを置いています。バグ報告、改善要望を是非お願いします。一緒に素晴らしいものを作り上げていきましょう!
Quarkusユーザーの場合でも、単に興味を持っているだけの場合でも、恥ずかしがらずにコミュニティに参加して下さい!:
-
GitHub でフィードバック
-
コードを作成し、 プルリクエスト を送信
-
Stack Overflow で質問