The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

パフォーマンスの測定

このガイドでは以下をカバーしています:

  • メモリ使用量の測定方法

  • 起動時間の測定方法

  • 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を測定します。

Linux

Linux の pmapps ツールは、プロセスのネイティブメモリマップのレポートを提供します

 $ 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 として実行することも可能です。

分離の無効化

分離はGraalVMの優れた機能ですが、Quarkusは現段階では使用していません。

-H:-SpawnIsolates で無効化します。

すべてのサービスローダー実装の自動登録を無効にする

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.

関連コンテンツ