JVMアプリケーションを運用する際のメジャーどころチューニングポイントメモ (original) (raw)

この広告は、90日以上更新していないブログに表示しています。

JVMにチューニング項目は多々あれど、プロダクションで運用する際に予めおさえておきたい項目をまとめてみるエントリです。*1勿論、OSもJVMもデフォルトである程度のパフォーマンスは発揮でき、計測を伴わないチューニングは悪手であることはよく知られています。 しかし、設定しておかないとパフォーマンスにそのまま影響すると分かるものを調べないのは裸で戦場に赴くようなものです。*2どんな項目をどう変更すれば良いのか知っていることは重要な武器なのです。

なぜ調べるのか

今回、チューニングポイントを調べるにあたって、私のモチベーションはどこにあるのかを考えると、以下の要件を満たしたいということがあげられます。

ここでいう品質水準・異常とは、パフォーマンスが明らかに低い、アプリケーションがクラッシュする、などの(JVM・アプリケーション双方の)期待しない振る舞いを指します。

性能目標

やみくもにチューニングしても終わりがなく、何をチューニングすれば良いかも判断しづらいものです。 性能目標を定めることで、このラインをクリアし続けることを目指す方が健全です。 例えば以下のような項目があげられるかと思います。

TL;DR

だいたいのことはJavaパフォーマンスに書いてあるので、これを読もう。

www.oreilly.co.jp


なお、今回の主な関心範囲はウェブアプリケーションJVMおよびそのうえで動くアプリケーション)を想定しています。*3代表的なものをあげつつ、これは最低限チェックしたいというものに ★ を付けています。

Kernel

JVMの前に土台についても少し考えます。 ホストのリソースを十分解放しないと、当然JVMもパフォーマンスを発揮できません。 昨今ではDockerなどのコンテナ技術も当たり前になりつつあり、ホストだけでなくコンテナのチューニングも必要になっているように思います。

transparent huge page

メモリのページサイズが大きくなれば、それだけ少ない回数でI/Oができるため高速化につながります。JVM以外にもシステム上のすべてのプログラムが使用するようになる点に注意が必要です。JVM側へのオプションの指定は不要(-XX:+UseLargePagesは別のものなので無効のままにしておく)です。

cat /sys/kernel/mm/transparent_hugepage/enabled

always [madvise] never

echo always > /sys/kernel/mm/transparent_hugepage/enabled

cat /sys/kernel/mm/transparent_hugepage/enabled

[always] madvise never

Kubernetes上で動く際のリソース制限

コンテナオーケストレーションのためにKubernetesを使うケースも多くなってきました。 アプリケーションはPodの上のコンテナ単位で動作しますが、このコンテナにリソースのrequest/limitを指定できます。明示しなくても動作させられますが、指定した方がアプリ側の見積もりもしやすいかなと思っています。 このlimitを元にアプリケーション(JVM)の設定を決めていけば良いでしょう。*4

いずれも、リソースを指定しない場合、あるだけ使おうとするため、複数のPodを一つのNodeに載せたときの動作が不安定となる恐れがあります。*5大きい数を指定してもNodeのリソースが足りないと起動すらできません。

see also:

JDK 10のコンテナサポート

JDK 10からコンテナサポートが強化されます。-XX:-UseContainerSupport がデフォルトで有効となり、JDK 10からはDockerの設定*6から値を取得するようになるようです。

DockerではいくつかのCPU設定が可能ですが、JVMmin(cpuset-cpus, cpu-shares/1024, cpus) という計算により active_processor_count を決定するようです。端数の場合は切り上げされて整数になります。 cpusは cpu_quota / cpu_period によって求められる値です。 この値は -XX:ActiveProcessorCount=N によって上書きすることができます。

メモリはJDK 10からFraction(1/N)による指定ではなくPercentage(%)による指定ができるようになります。 使用可能なホストメモリの割合を指定します。

Kubernetes上で動作する場合、Kubernetesのドキュメントによると、次のようにコンテナにパラメータが渡されるとあります。

たとえば、request.cpuとlimits.cpuの両方を1とした場合は、cpu-shares: 1 * 1024 / 1024 = 1cpucpu-quota: 1000m * 100 / 100ms = 1cpu/ms となるのでJVMにはCPU 1としてセットされます。 つまり、Kubernetesの定義する1cpuは、そのままJVMから見るCPU数となります。 ですので、両者の値に差があるとき、JDK 10では前述の計算式で最小値がCPUとなると思われます。※未検証

see also:

書籍「Javaパフォーマンス(オライリー)」は、一般に必要になることを網羅しており、とりあえずコレを読んでおけばだいたいのケースでは十分でしょうと思える一冊です。

JIT

Memory/GC

CMS GCはJava9で非推奨となっているため、現実的にはスループットGCまたはG1 GCのいずれを選択するかになるように思います。 どちらを選択した方が良いかは、CPUの余力とヒープメモリのサイズによって変わります。両方が十分に大きい(多く必要とする)のであればG1 GCの方が基本的に適しています。フルGCに伴う停止を極力なくしたい場合はCPUとメモリを積んでG1 GCを選択しましょう。

see also: HotSpot Virtual Machineガベージ・コレクション・チューニング・ガイド

スループットGC(ParallelGC)の設定

最大一時停止時間目標→スループット目標→最少フットプリント目標(-Xmx)の順に達成するよう動作します。JVMはadaptive sizingがデフォルトで有効で、指定された目標値になるように自動的にメモリの拡張など調整を行います。 そのため、必要なヒープサイズが予め分かる場合には-Xms -Xmxを同値に指定するとGCが最小限に留められるためパフォーマンスにプラスとなるようです。しかし、GC時間以上にスループットOps)の目標を十分に満たすことが重要であり、適切なサイズのヒープ(-Xmx)とGCTimeRatioを指定してJVMに委ねても手作業でチューニングする場合と同等のスループットを達成できることがある点には留意が必要です。(上記Oracleのガイドでもデフォルトより必要なヒープサイズが大きい場合を除きXmxを指定しないことを勧めている。)

G1 GCの設定

まずはMaxGCPauseMillisのチューニングから始め、それでもフルGCが発生する場合に他のチューニングを行った方が良いでしょう。

その他

Application

Thread Pool

see also: Understanding Play thread pools

DB Connection Pool

see also: HikariCP設定

JDBC driver

ステートメントキャッシュなどはドライバの設定で調整します。 Aurora(MySQL)への接続にMariaDB Connector/Jを使用しており、ここではそのドライバの設定項目を検討しています。*13

Aurora(MySQL) failover support

Coding

計測について

チューニングには計測がセットでなければいけないことは周知の通りです。 例えば、以下のような方法が考えられます。

おわりに

ここまで見てきたように、最初に検討すべき項目は少なく、基本的にJVMは優秀です。 パフォーマンスに影響する多くのケースではアプリケーションコードが原因であることがほとんどであるように思います。 チューニングポイントを知るだけでなく、内部動作(スレッドモデルやメモリモデル、GCの仕組みなど)を知ることも不可欠です。

また、制約理論によれば局所最適化の罠などもあります。 システム全体のボトルネックを探すことが最重要ですが、パフォーマンス劣化に直面した際はパニックにもなるものです。 予めどのようなポイントがあるのか知ることができるのなら、備えるにこしたことはありません。 アプリケーションの性格によって最適解は異なりますが、最初に考える範囲は似通うのかなと思います。


更新履歴