ElasticsearchパフォーマンスのためのJVMチューニング:ヒープとガベージコレクションのヒント

JVMチューニングをマスターして、Elasticsearchデプロイメントのピークパフォーマンスを引き出しましょう。このガイドでは、ヒープメモリ割り当て(50% RAMルールに従う)、G1GCを使用したガベージコレクションの最適化、および重要な監視テクニックについて詳しく説明します。レイテンシスパイクを排除し、大量の検索とインデックス負荷に対して長期的なクラスター安定性を確保するための実用的な設定を学びます。

ElasticsearchパフォーマンスのためのJVMチューニング:ヒープとガベージコレクションのヒント

ElasticsearchはJVM上で動作するため、ヒープとガベージコレクションは重要です。しかし、クラスターが遅い場合、JVMチューニングが最初に始めるべき場所ではありません。まず、シャード数、クエリ形状、インデックス負荷、ディスクレイテンシ、そしてノードが単にサイズ不足かどうかを確認してください。JVM設定は重要ですが、それは悪い値が健全なクラスターを不安定にする可能性があるからです。これらは、貧弱なインデックス設計や過負荷のハードウェアを回避するための近道ではありません。

このガイドでは、日々の運用で依然として有用なElasticsearch JVMチューニングに焦点を当てます:ヒープサイジング、ガベージコレクションの症状、メモリ負荷、そしてJavaが本当に問題かどうかを教えてくれる実用的なチェックです。


Elasticsearchのメモリ要件を理解する

Elasticsearchは、ヒープメモリオフヒープメモリの2つの主要な領域にメモリを必要とします。適切なチューニングには、ヒープを正しく設定し、オフヒープ要件のためにオペレーティングシステムに十分な物理メモリを残すことが含まれます。

1. ヒープメモリ割り当て(ES_JAVA_OPTS

ヒープは、Elasticsearchのオブジェクト、インデックス、シャード、キャッシュが存在する場所です。これは設定する上で最も重要な設定です。

ヒープサイズの設定

Elasticsearchは、初期ヒープサイズ(-Xms)を最大ヒープサイズ(-Xmx)と等しく設定することを強く推奨します。これにより、JVMが動的にヒープをサイズ変更するのを防ぎ、顕著なパフォーマンスの一時停止を引き起こす可能性があります。

ベストプラクティス:50%ルール

物理RAMの50%以上をElasticsearchヒープに割り当てないでください。残りのメモリは、オペレーティングシステム(OS)のファイルシステムキャッシュにとって重要です。OSはこのキャッシュを使用して、ディスクから頻繁にアクセスされるインデックスデータ(転置インデックス、保存されたフィールド)を保存します。これはディスクから読み取るよりもはるかに高速です。

推奨事項: マシンに64GBのRAMがある場合、-Xms-Xmx31g以下に設定します。

設定場所

これらの設定は通常、Elasticsearch設定ディレクトリ(例:$ES_HOME/config/jvm.options)にあるjvm.optionsファイルで構成されるか、設定を外部で管理したい場合(ES_JAVA_OPTSを使用するなど)は環境変数を介して構成されます。

設定例(jvm.options内):

# 初期Javaヒープサイズ(例:30ギガバイト)
-Xms30g

# 最大Javaヒープサイズ(-Xmsと一致する必要があります)
-Xmx30g

ヒープサイズに関する警告: ヒープサイズを31GB(または約32GB)以上に設定しないでください。これは、64ビットJVMが約32GB未満のヒープに対して圧縮オブジェクトポインタ(Compressed Oops)を使用し、よりメモリ効率の良いオブジェクトレイアウトを実現するためです。このしきい値を超えると、この効率の利点が無効になることがよくあります。

2. オフヒープメモリ(ダイレクトメモリ)

ElasticsearchはJavaヒープ外のメモリも使用します。Luceneはオペレーティングシステムのページキャッシュに大きく依存しており、Elasticsearchはネットワークおよびネイティブ操作にダイレクトメモリを使用する場合があります。ほとんどのインストールでは、Elasticのドキュメントやサポートガイダンスが正確なバージョンとワークロードに対して指示しない限り、-XX:MaxDirectMemorySizeを設定しないでください。手動のダイレクトメモリ制限は、低すぎる場合や時代遅れの仮定に基づいている場合、新たな障害モードを生み出す可能性があります。

ガベージコレクション(GC)のチューニング

ガベージコレクションは、JVMが参照されなくなったオブジェクトによって使用されていたメモリを再利用するプロセスです。Elasticsearchでは、GCの管理が不十分だと、"stop-the-world"ポーズと呼ばれる重大なレイテンシスパイクを引き起こし、ノードのタイムアウトや不安定性につながる可能性があります。

適切なコレクタの選択

最新のElasticsearchリリースは、サポートされているJVMデフォルトを同梱して出荷され、一般的に最近のJavaバージョンではG1GCを使用します。これらのデフォルトをベースラインとして扱ってください。ログとメトリクスが実際のガベージコレクションの問題を示している場合にのみ、コレクタ設定を変更してください。

G1GCチューニングパラメータ

G1GC最適化の主要なパラメータは、最大ポーズ時間目標を設定することです。これは、コレクタがメモリをどの程度積極的にクリーンアップするかを指示します。

G1GC設定例:

# 例のみ:バージョンがサポートしていて、デフォルトの動作が問題であるという証拠がない限り、GCフラグを追加しないでください。
-XX:MaxGCPauseMillis=200

GCアクティビティの監視

効果的なチューニングには、GCがいつ実行され、どのくらい時間がかかるかを知る必要があります。Elasticsearchでは、GCイベントをファイルに直接ログ記録できます。これはレイテンシの問題のトラブルシューティングに不可欠です。

GCログの有効化:

詳細なGCログを有効にするには、jvm.optionsファイルに次のフラグを追加します:

# GCログを有効にする
-Xlog:gc*:file=logs/gc.log:time,level,tags

# オプション:ログローテーションサイズを指定(例:10MB後にローテーション)
-Xlog:gc*:file=logs/gc.log:utctime,level,tags:filecount=10,filesize=10m

結果のgc.logファイルをGCEasyや特定のスクリプトなどのツールを使用して分析し、以下を特定します:

  1. 頻度: GCの実行頻度。
  2. 期間: ポーズの長さ(Total time for GC in...)。
  3. 昇格率: 古い世代に移動するのに十分な期間生存しているデータの量。

GCポーズが一貫してMaxGCPauseMillis目標を超えている場合(例:頻繁に500ms以上に達する)、メモリ負荷を示しています。解決策には、ヒープサイズの増加(RAMが許せば、50%ルールに従う)、またはオブジェクトのチャーンを減らすためのインデックス/クエリパターンの最適化が含まれます。

実践的なチューニングワークフローとベストプラクティス

Elasticsearch JVM設定をチューニングするには、次の体系的なアプローチに従ってください:

ステップ1:ノード容量を決定する

Elasticsearchノードをホストしているマシンで利用可能な物理RAMの合計を特定します。

ステップ2:ヒープサイズを計算する

最大ヒープサイズを計算します:最大ヒープ = 物理RAM * 0.5(最も近い安全な端数に切り捨て、通常は1〜2GBの空きバッファを残します)。-Xms-Xmxをこの値に設定します。

ステップ3:理由がない限り、ダイレクトメモリはそのままにする

古いブログ投稿からダイレクトメモリフラグをコピーしないでください。最初にElasticsearchバージョンのドキュメントと現在の起動ログを確認してください。

ステップ4:GCを構成する

-XX:+UseG1GCが存在することを確認し、-XX:MaxGCPauseMillis=100のような妥当な目標を設定することを検討してください。

ステップ5:ログ記録を有効にして監視する

GCログをアクティブにし、クラスターを数時間または数日間、典型的な本番負荷で実行させます。ログを確認します。

ステップ6:ログに基づいて反復する

  • ポーズが長すぎる場合: インデックス負荷を減らすか、RAMが許せばヒープサイズをわずかに増やし、50%ルールを再評価する必要があるかもしれません。
  • GCが非常に頻繁に実行されるがポーズが短い場合: ヒープが少し小さすぎて、マイナーコレクションが過剰に発生しているか、短期間のオブジェクトを大量に作成しすぎている可能性があります。

シャードサイジングのヒント: JVMチューニングは、適切なインデックス戦略と組み合わせると最も効果的です。過剰なシャーディング(シャードが多すぎる)は、JVMに多数の構造にわたって膨大な数のオブジェクトを管理させるため、GCオーバーヘッドが増加します。ノードあたりのオーバーヘッドを減らすために、より大きなシャード(例:10GB〜50GB)を目指してください。

実際のクラスターでのヒープ負荷の様子

ヒープ負荷が、オンコール担当者に「ヒープ負荷」として知らされることはほとんどありません。それは、検索レイテンシのスパイク、インデックス拒否、遅いクラスター状態の更新、ノードの離脱と再参加、またはトラフィックがピークになるまで問題なく見えるダッシュボードとして現れます。有用なシグナルは、JVMヒープが上昇し、ガベージコレクションが実行され、その後ヒープが健全なレベルに戻るかどうかです。

ビジー期間中にヒープが上昇し、ガベージコレクション後に低下する場合、ノードは単にハードに動作している可能性があります。ヒープが上昇し、古い世代のコレクション後も高いままの場合、持続的な負荷がある可能性があります。長いGCポーズがノードの切断、マスター選出、またはクライアントのタイムアウトと一致する場合、JVMの動作がインシデントの一部である可能性が高いです。

Elasticsearchノードの統計を使用してJVMの動作を確認します:

curl -s "http://localhost:9200/_nodes/stats/jvm,indices,thread_pool?pretty"

ヒープ使用率、ガベージコレクションの回数と時間、フィールドデータメモリ、リクエストキャッシュ、クエリキャッシュ、インデックス負荷、および拒否されたスレッドプールタスクを確認します。単一のメトリクスは誤解を招く可能性があります。たとえば、拒否されたタスクがない高いヒープは、中程度のヒープに検索拒否と長い古い世代のポーズが伴う場合よりも緊急性が低い場合があります。

50%ルールには理由がある

ElasticsearchヒープをシステムRAMの約半分以下に保つという一般的なアドバイスは恣意的ではありません。Luceneはディスクからインデックスファイルを読み取り、オペレーティングシステムのページキャッシュにより繰り返しの読み取りがはるかに高速になります。ほとんどすべてのメモリをJVMに与えると、ヒープは寛大に見えるかもしれませんが、OSがホットセグメントを効果的にキャッシュできないため、検索パフォーマンスは低下します。

64GBノードでは、約30GBまたは31GBのヒープが一般的な上限です。16GBノードでは、8GBが出発点になる可能性があります。小さな開発ノードでは、Elasticsearchははるかに少ないメモリで実行される場合があります。適切な値は、ワークロード、バージョン、ノードの役割によって異なります。専用のマスター候補ノードは通常、ホットデータノードよりもはるかに少ないヒープを必要とします。コーディネート専用ノードは、大規模な検索をファンアウトして大きな応答をマージする場合、意味のあるヒープを必要とする可能性があります。

ヒープが時々高いからといって、ヒープを増やさないでください。まず、何がそれを使用しているのかを尋ねてください。シャードが多すぎる、高価な集計、大きなフィールドデータ、大きなバルクリクエスト、巨大な検索結果ウィンドウ、および重いクラスター状態はすべて、ヒープを押し上げる可能性があります。ヒープを増やすと、症状が遅れる可能性がありますが、根本的な設計は悪化し続けます。

圧縮オブジェクトポインタと32GBの罠

多くのJavaデプロイメントは、約32GBを超えるヒープを避けます。これは、JVMが圧縮通常オブジェクトポインタ(しばしば圧縮Oopsと呼ばれる)を失う可能性があるためです。その場合、オブジェクト参照はより多くのメモリを消費する可能性があり、追加のヒープは期待されるほど多くの使用可能なスペースを購入しない可能性があります。正確なカットオフは異なる場合があるため、32GBを魔法の数字として扱うのではなく、起動ログを確認してください。

Elasticsearchは起動時にJVMエルゴノミクスをログに記録します。しきい値に近い場合は、圧縮Oopsが有効になっているかどうかを確認してください。31gのヒープは、ある程度の安全マージンを持ってラインを下回るために選ばれることがよくあります。ノードが本当にはるかに多くのメモリを必要とする場合、1つの巨大なヒープと苦痛なGC動作を作成する代わりに、ノードを追加したり、シャード負荷を減らしたり、役割を分割したりする方が良い場合があります。

シャード、マッピング、クエリがJVMの問題を引き起こす可能性がある

JVMチューニングは、過剰なシャード数からクラスターを救うことはできません。すべてのシャードにはオーバーヘッドがあります:データ構造、セグメントメタデータ、キャッシュ、検索調整、リカバリ作業。数千の小さなシャードは、各シャードに非常に少ないデータが含まれている場合でも、ヒープを消費し、クラスター操作を遅くする可能性があります。多くの日次インデックスを追加した後にヒープの問題が現れた場合、修正はインデックスライフサイクル管理とシャード統合であり、GCフラグではありません。

マッピングも重要です。テキストフィールド、キーワードフィールド、doc values、フィールドデータ、ネストされたドキュメント、ランタイムフィールドは、メモリ動作が異なります。大きなテキストフィールドでフィールドデータを有効にすると、特にコストがかかる可能性があります。集計中にヒープが跳ね上がる場合は、ユーザーがそのために設計されていないフィールドで集計していないか確認してください。

クエリはメモリ使用のバーストを引き起こす可能性があります。大きなfrom値を使用した深いページネーション、幅広いワイルドカードクエリ、高カーディナリティの集計、および大きな結果サイズはすべて、コーディネートノードとデータノードに負荷をかけます。search_after、ポイントインタイム検索、狭いフィルター、および適切に設計された集計を、適切な場所で使用してください。開発では無害に感じられるクエリでも、数百のシャードにわたって実行されると、大きなダメージを与える可能性があります。

バルクインデックスとヒープ

バルクインデックスは、混乱のもう1つの一般的な原因です。より大きなバルクリクエストは、ある時点までスループットを向上させることができますが、サイズが大きすぎるとメモリを消費し、キュー時間を増やし、再試行をより高価にします。インデックス負荷、書き込みスレッドプールの拒否、または取り込み中のGCスパイクが見られる場合は、JVMフラグを変更する前に、バルクリクエストサイズまたは同時実行性を減らしてください。

実用的なアプローチは、本番環境に近いドキュメントでバルクサイズをテストすることです。控えめに開始し、スループットが向上しなくなるまで増やし、その後戻します。CPU、ヒープ、GC、ディスクI/O、マージアクティビティ、および拒否数を監視します。ノードがほとんどの時間をセグメントのマージまたはディスクの待機に費やしている場合、ヒープチューニングは取り込みのボトルネックを修正しません。

リフレッシュ間隔もインデックス動作に影響します。ニアリアルタイム検索が必要ない大量の取り込みの場合、refresh_intervalを増やすとセグメントのチャーンを減らすことができます。これはインデックス設定であり、JVMチューニングではありませんが、人々がJVMのせいにする症状を改善することがよくあります。

コンテナメモリ制限

コンテナ内のElasticsearchは、Javaのバージョンと構成によってJVMがコンテナ制限を異なる方法で認識するため、特別な注意が必要です。コンテナに4GBのメモリ制限があり、4GBのヒープを設定した場合、オフヒープメモリ、スレッドスタック、ネイティブメモリ、ファイルシステムキャッシュにもスペースが必要なため、プロセスが強制終了される可能性があります。

ヒープをホストのメモリではなく、コンテナのメモリ制限に関連付けて設定します。非ヒープメモリのための余地を残します。KubernetesまたはコンテナランタイムのログでOOMKilledイベントを監視します。クリーンなElasticsearchエラーなしで消えるポッドは、Java内でクラッシュするのではなく、プラットフォームによって強制終了された可能性があります。

Kubernetesの場合、リクエストと制限は実際のメモリプロファイルを反映する必要があります。ヒープに近すぎる制限は、OOMキルを招きます。低すぎるリクエストは、ポッドを他のワークロードと激しく競合するノードに配置する可能性があります。Elasticsearchは、日和見的なオーバーコミットよりも、予測可能なメモリとディスクI/Oの恩恵を受けます。

GC設定を変更するタイミング

ほとんどのオペレーターは、コレクタの実験を避けるべきです。Elasticsearchは、各リリースでサポートされているJVM設定をテストして出荷します。古いCMSフラグ、攻撃的なポーズ目標、またはコピーされたチューニングバンドルをランダムに追加すると、起動を妨げたり、動作を悪化させたりする可能性があります。

ログで問題を説明できる場合にのみ、GC設定を変更してください:古いGCポーズが長すぎる、若いGCが頻繁すぎる、ヒープが回復しない、またはポーズイベントがクラスターの不安定性と一致する。それでも、小さな変更を好み、ロールバックパスを維持してください。JVMフラグは本番構成の一部であり、シャード割り当てやセキュリティ変更と同じレビューを受ける必要があります。

MaxGCPauseMillisなどのポーズ目標を変更する場合、それは目標であり、約束ではないことを覚えておいてください。JVMは、高い割り当て負荷の下ではそれを満たせない可能性があります。アプリケーションがオブジェクトをあまりにも速く作成しすぎると、コレクタはそれを無料のパフォーマンスに変えることはできません。

短いインシデントチェックリスト

Elasticsearchのレイテンシがスパイクし、JVMが疑われる場合、私は次の順序でこれらをチェックします:

  1. 1つまたは2つのノードが異常か、それともクラスター全体が影響を受けているか?
  2. ヒープ使用量はレイテンシと同時に上昇したか?
  3. 古い世代のGCポーズは発生したか、そしてそれらはどのくらいの長さだったか?
  4. 検索または書き込みスレッドプールは作業を拒否しているか?
  5. インデックスレート、バルクサイズ、またはクエリボリュームは変化したか?
  6. シャード数、セグメント数、またはクラスター状態サイズは最近増加したか?
  7. ディスクI/Oレイテンシは高いか?
  8. デプロイメント、マッピング変更、または新しいダッシュボードクエリが同じ頃に開始されたか?

そのチェックリストは、調査を地に足のついたものに保ちます。JVMチューニングはレバーの1つですが、いくつかのレバーのうちの1つにすぎません。

実用的な要点

適切なJVM設定はElasticsearchの安定性を維持するのに役立ちますが、ほとんどの成果は、ヒープを慎重にサイジングし、ファイルシステムキャッシュのための余地を残し、実際のGC動作を監視し、メモリ負荷を生み出すシャードやクエリの問題を修正することから得られます。-Xms-Xmxを等しく保ち、圧縮Oopsのしきい値近くでは控えめにし、証拠が反対を示すまでバージョンのデフォルトを信頼し、GCログを装飾ではなく運用上の証拠として扱ってください。