ElasticsearchのためのJVMの基本:メトリクス、メモリー、監視

Elasticsearchは、Apache Luceneをベースに構築されたJavaベースの検索・分析エンジンおよびベクトルデータベースであり、Elasticsearch Platformのコアです。サポートされているPlatformでElasticsearchを実行するには、Java仮想マシン, VM(JVM)が必要です。JVMはPlatformに依存しない実行環境を提供しており、既存のオペレーティングシステム, OS上の仮想環境でElasticsearchを実行できます。JVMは基盤となるOSとハードウェアを抽象化するため、JavaアプリケーションはあらゆるPlatform上で実行できます。

JVMのメモリー管理とガーベージコレクション(GC)によるオブジェクトの回収を理解することが、java.lang.OutOfMemoryError(終了コード 127)のような問題をトラブルシューティングする鍵となります。これらの問題は、JVMの実行中にメモリーが不足した場合や、終了コード 137 が発生した場合に起こります。これは通常、過度なメモリー使用により、ホストの OOM(Out-of-Memory)キラーが JVM プロセスを終了させたことを示しています。本ブログでは、メモリー使用パターンの調査方法と、これらの JVM 問題のトラブルシューティング方法について解説します。

また、JVMの役割と、既存のElasticsearch APIを使用してこれを関連付ける方法についても説明します。これにより、Elasticsearch JVMの状態に関する貴重な洞察が得られ、独自のユースケースに合わせてさらに調整を行うべきかどうかを判断するのに役立ちます。

Elasticsearch JVMドキュメントで言及されているように、製品に同梱されているJVMオプションのデフォルト値は、サポートされているすべての本番環境で効率的に機能します。これらのデフォルト値は、検索およびインデキシング操作のほとんどのユースケースに対応できます。JVMオプションや設定の値を変更したり、カスタム値を追加したりすることは推奨されません。

ワークロードに有益な変更があると思われる場合は、変更を行う前に Elastic Support までご連絡ください。

JVMとは何ですか?

Java仮想マシン, VMは、Java Development Kit(JDK)にバンドルされているJava Runtime Environment(JRE)の不可欠な要素です。

JVMフローチャート

JVMは、Javaアプリケーションの実行を支援するコンピュータプログラムのようなものです。物理的なマシンとしては存在しませんが、Javaコードをサポート対象のオペレーティングシステム(OS)が理解できる命令に変換することで、マシンとして機能します。またJVMは、メモリー管理、ガーベージコレクションの処理、アプリケーションのセキュリティ維持といった重要なタスクも担います。

次に、JVMメモリー管理とガーベージコレクションに焦点を当てます。これはElasticsearchにおける重要なポイントであり、メモリーパフォーマンスやOOMに関連する問題をトラブルシューティングする際に頻繁に確認する項目です。

メモリー管理とガベージコレクションを理解するには、まずJVMの起動時にすべてのオブジェクトが配置されるヒープ領域(またはヒープメモリー)の論理的な表現を理解する必要があります。

JVM ヒープ領域の理解:Young 世代と Old 世代

ヒープ領域(ヒープメモリー)とは、オブジェクトのライフサイクルや経過時間に基づいてオブジェクトが配置されるメモリープールの集合体です。JVMヒープ領域は、主にヤングジェネレーション(Young Generation)とオールドジェネレーション(Old Generation)で構成されています。ヤングジェネレーションは、下図に示すように、さらにEden領域とSurvivor領域に分割されます。Javaオブジェクトは、その経過時間や参照に基づいてこれらの領域に割り当てられ、その後ヤング領域からオールド領域へと移動します。

Eden領域におけるヒープ使用量の大部分は、迅速に回収されます。解放されなかったヒープは、いずれかのSurvivor領域(S0またはS1)に移動し、通常は指数関数的な減衰パターンに従って回収されます。これを超えて存続する場合、Tenured領域に移動します。

JVMヒープ領域の合計 = Young世代(Eden領域およびSurvivor領域S0とS1)+ Old世代(Tenured領域)

ヒープ領域の論理表現(使用しているガーベージコレクターの種類には依存しません)
ヒープ領域の論理表現(使用しているガーベージコレクターの種類には依存しません)

Young generation領域
Javaアプリケーションが起動すると、新しいオブジェクトが作成され、それらのオブジェクトはヒープ領域と呼ばれる領域に割り当てられます。Young generationのEdenスペースは、アプリケーションが新しく作成されたオブジェクトを最初に割り当てる場所です。この領域は新しいオブジェクトの割り当てのために確保されています。そして、この領域がいっぱいになると、対象となるオブジェクトはガーベージコレクションによって回収されるか、他の領域に昇格されます。

マイナーガーベージコレクションが発生すると、参照されているオブジェクトは別のサブセクションであるSurvivor領域S0に移動されます。また、参照されていないオブジェクトは削除され、Eden領域のスペースが解放されます。この領域にはガーベージコレクションのプロセスを生き残ったオブジェクトが含まれるため、Survivor領域という名前が付けられています。 

Eden spaceが再びいっぱいになると、同じサイクルが繰り返されます。今回、EdenとS0の両方を生き残ったオブジェクトはS1リージョンに移動され、このプロセスが継続されます。

Eden、S0、およびS1でのガーベージコレクションを生き残ったオブジェクトは、年齢計算機に基づいてオールドジェネレーションに昇格されます。

オールドジェネレーション領域/リージョン
オールドジェネレーションテニュアードジェネレーションとも呼ばれます)は、ヤングジェネレーション(マイナーGC)での複数回のガベージコレクションイベントを生き延びた長寿命オブジェクトの集合です。サバイバースペースで一定回数のサイクルを生き延びたオブジェクトは、アルゴリズムによってオールドジェネレーションに移動されます。これはオブジェクトプロモーションとも呼ばれます。

GCレートおよびJVMヒープのグラフ

スタックモニタリングによる上記のノコギリ状のGCパターンは、マイナーガーベージコレクションが発生しているときに、GCがメモリから不要なオブジェクトを削除するため、JVMヒープ領域が減少していることを示しています。いくつかのサイクルを経てオブジェクトが蓄積されると、再びGCが実行され、オブジェクトがクリアされます。

ガーベージコレクション方式

Elasticsearchにはサポート対象の同梱JDKバージョンが付属しており、詳細はリリースノートに記載されています。v6.4まで、ElasticsearchはConcurrent Mark Sweep(CMS)ガベージコレクターを使用していましたが、これはJDK9で非推奨となりました。現在、ElasticsearchはデフォルトのGC方式としてGarbage-First(G1)ガベージコレクターを使用しています。また、CMS GC方式と比較してパフォーマンスも向上しています。G1GCの内部メカニズムは複雑で多くのフェーズを含みますが、その基本を解説します。 

G1GCを使用するには、ElasticsearchのJVMオプションでJVMフラグ-XX:+UseG1GCを使用します。G1GCでは、ヒープ領域全体が等しいサイズのリージョンに分割されます-XX:G1HeapRegionSize=1m。このサイズはヒープサイズに応じて1MBから32MBの間で変動します。Eden、Survivor、およびOld世代はこれらのリージョンの論理セットであり、連続していません。これとは別に、ヒープ領域内にはHumongousリージョンと空き(利用可能)リージョンが存在します。Humongousリージョンは、リージョンサイズの半分を超えるオブジェクトを割り当てるために使用されます。

G1GCにおけるヒープ(領域)の割り当て
G1GCにおけるヒープ(領域)の割り当て

G1GCには、ガーベージコレクション中に達成を目指す一時停止時間の目標(-XX:MaxGCPauseMillis=200)があります。これは、ガーベージコレクション中にJVMがこの値を超える一時停止を経験しないようにすることを意味し、このオプションがないCMSや他のコレクターよりも優れています。全体として、GC設定はデフォルト値のままにすることをお勧めします。これにより、GCの一時停止が制限内に収まり、パフォーマンスが向上します。

JVMの状態に関するElasticsearch API

Elasticsearchが実行しようとするタスクは、それを実行するためにJVMへメモリーの割り当てを要求する必要があります。上記のとおり、JVMがElasticsearchに代わってメモリー管理を行うため、Elasticsearch側でメモリーを気にする必要はありません。ただし、管理者は、期待どおりに処理が進行していることを確認するために、Elasticsearch API経由でJVMの状態を確認したいと考えるかもしれません。

Elasticsearch JVMのインプット引数として指定または提供されたすべての設定を確認するには、JVM属性を指定してnode info APIを使用します(ドキュメントにも記載されています)。

GET _nodes/_all/jvm 

この API は、JVM インプット引数、メモリプール、ヒープサイズなど、Node 設定の詳細を提供します。

さらに、特定のノードのアップタイム、すべてのメモリプールで使用されているヒープ、またはガベージコレクションに関する統計など、Elasticsearch JVMのメトリックを確認したい場合は、node statistics APIを使用できます。これにより、前述の各メトリックに関する詳細なメトリックが提供されます。

GET _nodes/stats/jvm
or
GET _nodes/stats/jvm?filter_path=nodes.*.jvm

注:Elasticsearchでは、Node stats APIから取得したheap_percentの値に基づいて判断を行うことは推奨していません。当社のUIではメモリプレッシャーを計算しており、ユーザーは本ブログで説明されている計算に基づいて必要な措置を講じる必要があります。

組み込まれたJDKツールを使用したJVMメトリックの確認

上級ユーザーで、Elasticsearch APIを使用せずに実行中のJVMに関するリアルタイムの統計を確認したい場合は、バンドルされたJavaを使用してjstatを実行し、ライブ統計を確認できます。これを行うには、まずJavaのホームディレクトリまたはbinディレクトリと、ElasticsearchプロセスのPIDを見つけます。binディレクトリから、jstatツールを使用します:

./jstat -gcutil <PID> 2000

ここで、2000は統計が取得され、CLIに出力されるまでの間隔(ミリ秒単位)です。このコマンドは、すべての領域でGCがどの程度の頻度で発生しているか、またオブジェクトによってメモリプールがどの程度の速さで埋まっているか(オブジェクトの作成率および昇格率)を特定するのに役立ちます。

これは上述のAPIの代替となるものではなく、開発者レベルのデバッグを目的としたものです。

本イベントは終了しました。

本ブログでは、Java仮想マシン, VMの基本概念、さまざまなメモリープール、ガーベージコレクションの重要性、およびElasticsearchのJVM統計を確認する方法について解説しました。次回のブログでは、重要なJVM設定と、Elasticsearch内で現在のメトリックをポーリングする方法について説明します。

ご質問やさらなるサポートが必要な場合は、お気軽にお問い合わせください。いつでも喜んでサポートいたします!

本記事に記述されているあらゆる機能ないし性能のリリースおよびタイミングは、Elasticの単独裁量に委ねられます。現時点で提供されていないあらゆる機能ないし性能は、すみやかに提供されない可能性、または一切の提供が行われない可能性があります。