パフォーマンスの測定
このガイドでは以下をカバーしています:
-
メモリ使用量の測定方法
-
起動時間の測定方法
-
Quarkusがデフォルトでネイティブイメージに適用する追加フラグ
-
ツールにおける協調的省略(Coordinated omission)問題
すべてのテストは、バッチに対して同じハードウェアで実行しています。言うまでもないことですが、言うべきことです。
メモリ使用量の測定方法
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, rest, rest-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)問題
Quarkusのようなフレームワークのパフォーマンスを測定する場合、ユーザーが経験するレイテンシーは特に注目の対象で、そのためにさまざまなツールがあります。残念なことに、多くのツールはレイテンシを正しく測定することができず、協調的省略問題を引き起こしています。つまり、ツールは、システムが負荷下にあるときに新しいリクエストを送信する遅延に適応することに失敗し、これらの数値を集計するため、待ち時間とスループットの数値が非常に誤解を招きやすくなります。
このビデオ で、wrk2 の作者である Gil Tene がこの問題について説明しています。 Quarkus Insights #22 では、Quarkus パフォーマンス チームの John O’Hara が、それがどのように現れるか説明しています。
そのビデオと関連する論文や記事の日付はすべて2015年にさかのぼりますが、現在でも、協調的省略の問題で不十分なツールを見つけることができます。
現時点では、この問題を引き起こすことが知られているツールは、レイテンシ/スループットの測定には使用しないでください(他のことには使用できるかもしれません):
-
JMeter
-
wrk
影響を受けないことが確認されているツールは以下の通りです:
したがって、 wrk2
や hyperfoil
を使う場合でも、その数値が何を意味するのかを理解するようにしてください。