パフォーマンスの測定
このガイドでは以下をカバーしています:
-
メモリ使用量の測定方法
-
起動時間の測定方法
-
Quarkusがデフォルトでネイティブイメージに適用する追加フラグ
-
Coordinated omission Problem in Tools
すべてのテストは、バッチに対して同じハードウェアで実行しています。言うまでもないことですが、言うべきことです。
メモリ使用量の測定方法
Quarkusアプリケーションのフットプリントを測定する際には、 Resident Set Size(RSS) を測定します。JVMのヒープサイズではありません。JVMヒープサイズは全体的な問題のほんの一部です。JVMは、ヒープ用のネイティブメモリー( -Xms
, -Xmx
)を割り当てるだけでなく、アプリケーションを実行するためにjvmが必要とする構造体も割り当てます。JVMの実装に応じて、アプリケーションに割り当てられる総メモリーは、以下のものを含みますが、これに限りません。
-
ヒープ空間
-
クラスメタデータ
-
スレッドスタック
-
コンパイルされたコード
-
ガベージコレクション
ネイティブメモリのトラッキング
JVMで使用されているネイティブメモリを表示するには、hotspotでNMT( Native Memory Tracking )機能を有効にします;
コマンドラインでNMTを有効に設定;
-XX:NativeMemoryTracking=[off | summary | detail] (1)
1 | 注意: この機能は、約 5-10% のパフォーマンスオーバーヘッドを発生させます。 |
そして、jcmdを使ってアプリケーションを実行しているHotspot JVMのネイティブメモリ使用量のレポートをダンプすることができます;
jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
クラウドネイティブのメモリ制限
Cloud Nativeアプリケーションの影響を見るためには、メモリー全体を測定することが重要です。特にRSSメモリーの全使用量に基づいてプロセスを強制終了させてしまうことがあるコンテナー環境では特にそうです。
同様に、プロセスが使用している、他のプロセスと共有されないプライベートメモリだけを測定するという罠には陥らないようにしましょう。プライベートメモリは、多くの異なるアプリケーションをデプロイする環境(メモリを多く共有する)では有用かもしれませんが、Kubernetes/OpenShiftのような環境では非常に誤解を招く可能性があります。
Docker上でメモリを正しく測定する
メモリを正しく測定するためには、 docker statやそれに由来するもの(ctopなど)を使用しないでください 。この方法では、使用中のレジデントページのサブセットしか測定できません。一方、Linuxカーネル、cgroups、およびクラウドオーケストレーションプロバイダは、アカウンティング(プロセスが制限を超えているかどうか、強制終了すべきかどうかの判断)に完全なレジデントセットを利用します。
正確に測定するためには、Linux上でRSSを測定するのと同様の手順を実行する必要があります。docker top
コマンドは、コンテナインスタンス内のプロセスに対して、コンテナホストマシン上で ps
コマンドを実行することができます。これを出力パラメータのフォーマットと組み合わせて利用することで、RSSの値を返すことができます:
docker top <CONTAINER ID> -o pid,rss,args
例えば、以下のようになります:
$ docker top $(docker ps -q --filter ancestor=quarkus/myapp) -o pid,rss,args
PID RSS COMMAND
2531 27m ./application -Dquarkus.http.host=0.0.0.0
また、特権シェル(ホスト上のroot)に直接ジャンプして、 ps
コマンドを直接実行することもできます:
$ docker run -it --rm --privileged --pid=host justincormack/nsenter1 /bin/ps -e -o pid,rss,args | grep application
2531 27m ./application -Dquarkus.http.host=0.0.0.0
Linuxを使用している場合は、シェルがコンテナホストと同じであるため、 ps
コマンドを直接実行することができます:
ps -e -o pid,rss,args | grep application
プラットフォーム固有のメモリレポート
NVMを有効にして実行する際のパフォーマンスオーバーヘッドを発生させないために、各プラットフォームに特化したツールを使用して、JVMアプリケーションの総RSSを測定します。
$ ps -o pid,rss,command -p <pid>
PID RSS COMMAND
11229 12628 ./target/getting-started-1.0.0-SNAPSHOT-runner
$ pmap -x <pid>
13150: /data/quarkus-application -Xmx100m -Xmn70m
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 55652 30592 0 r-x-- quarkus-application
0000000003c58000 4 4 4 r-x-- quarkus-application
0000000003c59000 5192 4628 748 rwx-- quarkus-application
00000000054c0000 912 156 156 rwx-- [ anon ]
...
00007fcd13400000 1024 1024 1024 rwx-- [ anon ]
...
00007fcd13952000 8 4 0 r-x-- libfreebl3.so
...
---------------- ------- ------- -------
total kB 9726508 256092 220900
プロセスに割り当てられた各メモリ領域が一覧表示されます;
-
Address: 仮想アドレス空間の開始アドレス
-
Kbytes(キロバイト): リージョン用に予約された仮想アドレス空間のサイズ (キロバイト)
-
RSS:レジデント・セット・サイズ(キロバイト)。実際に使用されているメモリ領域の大きさを示します。
-
Dirty:ダーティページ(共有とプライベートの両方)、キロバイト単位
-
Mode: メモリ領域のアクセスモード
-
Mapping: プロセス用のアプリケーション領域と共有オブジェクト(.so)のマッピングを含む
Total RSS (kB)の行は、プロセスが使用しているネイティブメモリの合計を報告します。
- macOS
-
macOSでは、
ps x -o pid,rss,command -p <PID>
と指定すると、指定したプロセスのRSSをKB(1024バイト)単位で一覧表示することができます。
$ ps x -o pid,rss,command -p 57160
PID RSS COMMAND
57160 288548 /Applications/IntelliJ IDEA CE.app/Contents/jdk/Contents/Home/jre/bin/java
つまり、IntelliJ IDEAは281,8MBのRSSを消費していることになります。
起動時間の測定方法
フレームワークの中には、アグレッシブな遅延初期化技術を使用しているものがあります。フレームワークを開始するために必要な時間を最も正確に反映させるために、最初のリクエストまでの起動時間を測定することが重要です。そうしないと、フレームワークが 実際に 初期化にかかる時間を見逃してしまいます。
ここでは、テストにおける起動時間の測定方法を説明します。
アプリケーションのライフサイクルのある時点のタイムスタンプを記録するサンプルアプリケーションを作成します。
@Path("/")
public class GreetingEndpoint {
private static final String template = "Hello, %s!";
@GET
@Path("/greeting")
@Produces(MediaType.APPLICATION_JSON)
public Greeting greeting(@QueryParam("name") String name) {
System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(System.currentTimeMillis())));
String suffix = name != null ? name : "World";
return new Greeting(String.format(template, suffix));
}
void onStart(@Observes StartupEvent startup) {
System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
}
}
シェル内でループを開始し、テストしているサンプルアプリケーションのRESTエンドポイントにリクエストを送信します。
$ while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/greeting)" != "200" ]]; do sleep .00001; done
別のターミナルで、テストしているタイミングアプリケーションを起動し、アプリケーションが起動した時刻表示します
$ date +"%T.%3N" && ./target/quarkus-timing-runner
10:57:32.508
10:57:32.512
2019-04-05 10:57:32,512 INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.002s. Listening on: http://127.0.0.1:8080
2019-04-05 10:57:32,512 INFO [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, resteasy-reactive-jackson]
10:57:32.537
最終タイムスタンプと最初のタイムスタンプの差は、アプリケーションが最初のリクエストにサービスを提供するまでの総起動時間です。
Quarkusで適用される追加フラグ
QuarkusがGraalVM native-image
を起動すると、 デフォルトでいくつかの追加フラグが適用されます。
他のビルドと性能特性を比較する場合には、以下のようなものを把握しておくとよいです。
フォールバックイメージの無効化
フォールバックネイティブイメージは、何らかの理由でネイティブコードへのコンパイルに失敗した場合に、アプリケーションを通常のJVMで実行するために「フォールバック」するためのGraalVMの機能です。
Quarkusは、 -H:FallbackThreshold=0
を設定することでこの機能を無効にします。これにより、アプリケーションが本当にネイティブモードで実行できないことに気づかないリスクの代わりに、コンパイルに失敗するように出来ます。
その代わりに Java モードで実行したい場合は、ネイティブイメージのビルドをスキップして jar として実行することも可能です。
すべてのサービスローダー実装の自動登録を無効にする
Quarkusのエクステンションでは、必要なサービスを自動的に選択することができますが、GraalVMのネイティブイメージのデフォルトでは、クラスパス上で見つけることができるすべてのサービスが含まれています。
より最適化されたバイナリを生成するために、明示的にサービスをリストアップすることをお勧めします。 -H:-UseServiceLoaderFeature
により、これを無効にすることも可能です。
その他…
このセクションは、高レベルのガイダンスとして提供されています。フラグの中には、エクステンションや構築しているプラットフォーム、設定の詳細、あなたのコード、そして場合によってはこれらの組み合わせによって動的に制御されものがあるため、このセクションは包括的なものではありません。
一般的に言えば、ここに挙げたものはパフォーマンス・メトリクスに影響を与える可能性が高いものですが、適切な状況下では、他のフラグによる影響も無視できないものになるでしょう。
いくつかの差異を詳細に調査する場合は、Quarkusが呼び出しているものを正確にチェックしてください。ビルドプラグインがネイティブイメージを生成しているときには、コマンドライン全体がログに記録されます。
Coordinated Omission Problem in Tools
When measuring performance of a framework like Quarkus the latency experience by users are especially interesting and for that there are many different tools. Unfortunately, many fail to measure the latency correctly and instead fall short and create the Coordinate Omission problem. Meaning tools fails to acoomodate for delays to submit new requests when system is under load and aggregate these numbers making the latency and throughput numbers very misleading.
A good walkthrough of the issue is this video where Gil Tene the author of wrk2 explains the issue and Quarkus Insights #22 have John O’Hara from Quarkus performance team show how it can show up.
Although that video and related papers and articles date all back to 2015 then even today you will find tools that fall short with the coordinated oission problem
Tools that at current time of writing is known to excert the problem and should NOT be used for measuring latency/throughput (it may be used for other things):
-
JMeter
-
wrk
Tools that are known to not be affected are:
Mind you, the tools are not better than your own understanding of what they measure thus even when using wrk2
or hyperfoil
make sure you understand what the numbers mean.