Bering Note – formerly 流沙河鎮

情報技術系のこと書きます。

OpenSearchCon North America 2025「OpenSearchのメモリ管理を探る:LuceneとJVMガベージコレクションの内部メカニズム」- Yeonghyeon Ko, SK hynix

OpenSearchCon North America 2025のセッション「Digging Into OpenSearch’s Memory: Lucene and JVM Garbage Collection Under the Microscope」をまとめます。

www.youtube.com

スピーカー


このセッションは、SK hynixでAI/Data Engineerを務めるMarcel Yeonghyeon Ko氏によって行われました。

Ko氏はSK hynixにおいて、Managed Search EngineとしてのOpenSearchの運用、EmbeddingとChunkingを含むRAG Pipelineの構築、そしてJVMKubernetesをベースとしたHybrid Cloud PlatformのDevOps業務を担当しています。OpenSearchプロジェクトへの積極的なコントリビュータでもあり、java-clientへのPR #1298、neural-searchへのPR #1342、k8s-operatorへのPR #1040などの実装を行ってきました。
github.com
github.com
github.com

2つのメモリのバランス

OpenSearchのメモリ管理

The Balance of Two Memories

大規模にOpenSearchを実行している人にとって、Javaのパフォーマンス最適化は非常に複雑なテーマです。その中でも特に重要なのがメモリ管理で、これは絶妙なバランスが求められる領域です。
メモリといっても単一のものではありません。実際にはJVMヒープとネイティブOSメモリの2つの領域が存在し、これらは協調しながらも時に競合する関係にあります。
JVMヒープはOpenSearchノードのエンジンルームとして機能します。ここではAPIリクエストの処理、ドキュメントの処理、そしてヒープメモリを安定させるためのガベージコレクションのスケジューリングといったアクティブな計算処理が行われています。
一方で、JVMプロセスの外側にはネイティブメモリ、つまりオフヒープメモリが存在します。ここではLuceneの検索速度を向上させるファイルシステムキャッシュが動作し、HNSWグラフなどのベクトル検索も実装されています。
ここで重要なのは、常にトレードオフが存在することです。JVMヒープにより多くのメモリを割り当てればインデックス作成の計算能力は向上しますが、その分検索パフォーマンスに使えるネイティブメモリが減少し、検索速度が低下する可能性があります。

インデックスバッファ


インデックスバッファはデータがLuceneセグメントになる前のステージングエリアです。
特に注目したいのはindices.memory.index_buffer_sizeです。これはドキュメントをバッファリングするための共有予算であり、予算が不足するとシャードがフラッシュを開始し、セグメントが作成されます。小さすぎると、小さなフラッシュが頻繁に発生することで、多くのセグメントが作成され、マージされることで書き込みの増幅が発生します。
Lucene RAMバッファのための別の設定もありますが、これはLuceneのインデックスライターと連携しているため、通常はこの値を変更する必要はありません。

Luceneインデックス操作の流れ


この図はメモリバッファがどのように機能するかを示し、Luceneのインデックスライターのflushメソッドとcommitメソッドの違いを説明しています。

OpenSearchにおけるrefreshとflush


OpenSearchの観点から、refreshとflushという2つの操作を通じてバッファを管理しています。refreshはデフォルトで1秒間隔で実行され、メモリ内のドキュメントを検索可能にしますが、まだディスクには永続化されません。一方、flushはLuceneのcommitを実行し、セグメントをディスクに永続的に保存します。
画面に表示されている結果を見ると、refreshプロセスでは複合ファイルセット(.cfs、.cfe)が作成されますが、flush APIを実行するまでセグメント番号は増加しません。これがインデックスライターのflushとcommitの重要な違いです。

ネイティブOSメモリ


JVMヒープとは異なり、ネイティブOSメモリはオペレーティングシステムによって管理されます。
Luceneがディスクからセグメントファイルを読み取ろうとすると、OSはそれらのファイルをRAMにキャッシュします。そのため、クエリが来ると、Luceneはディスクまで行くことなく、多くの場合、メモリから直接データを読み取ることができます。
従って、一般的に推奨されるアプローチでは、JVMにメモリの大部分を与えるのではなく、JVMヒープとネイティブOSメモリを半々に分割するのが良いとされています。
右側のコマンド出力では、page_cache_sync_readaheadなどのシステムコールが表示されており、OSがページキャッシュを通じてファイルアクセスを最適化している様子が確認できます。

Memory Mapping


ネイティブメモリでは、Memory Mappingを使用できます。
JavaがJNIブリッジを介してFileChannel.map()メソッドを呼び出すと、OSはmmap64システムコールを呼び出し、メモリマップされたアドレスを返します。つまり、Luceneはディスクに戻ることなく、メモリから直接セグメントファイルを読み取ることができます。
分かりやすい例はHNSWグラフを使用したベクトル検索です。これはメモリ集約型の構造であり、各ベクトルのサイズを計算する場合、それらをJVMヒープにロードすると、ガベージコレクションからの圧力が深刻な問題となり、メモリ不足が発生する可能性があります。
したがって、ネイティブメモリはセグメントファイルをキャッシュするためだけでなく、ベクトルインデックスのような大規模なデータ構造を処理するためにも重要です。これがJVMヒープとネイティブOSメモリのバランスが非常に重要である理由です。

Memory Mappingの観察

OpenSearchプロセスのプロセスIDを使用してメモリマッピングの状態を検査するのは非常に簡単です。
pmapコマンドやcat /proc/[pid]/mapsを使用して、仮想メモリにマップされたファイルを調査できます。

このページでは、ヒープのサイズを計算し、いくつかのcfsファイルを見つけることができます。Javaヒープはデバイス番号が00:00となっており、これはディスクからマッピングされたファイルではなく、動的に割り当てられたメモリ領域であることを示しています。16進数から10進数に変換することで、そのサイズを簡単に推定できます。この例では約128MBになっています。

メモリマップされたファイルに関しては、各行がOpenSearchのファイルパスを示しており、OpenSearchLuceneインデックスにアクセスするためにメモリマップを使用していることを明確に証明しています。これはOpenSearchのコアコンポーネント機能であり、オペレーティングシステムがキャッシュとIOをより効率的に処理できるようにします。
Luceneが使用するファイル形式に興味がある方は、セッション「Derived Source: Slash Storage Costs Without Losing Data in OpenSearch」が参考になるでしょう。
www.youtube.com

OpenSearchにおけるLuceneメモリ

Lucene学習者向けリソース


Luceneを深く掘り下げる前に、自分でLuceneを実装し、より深く理解したい開発者のために2つの役立つウェブサイトを紹介したいと思います。
GitHubLucene Universityは、私たちにとって完璧な遊び場かもしれません。私たち開発者の多くがこのリポジトリに慣れ親しんでいると確信しています。 github.com

もう一つの役立つウェブサイトはDeep Wikiです。これは公開されているオープンソースプロジェクトを分析し、コードが何を意味し、どのように機能するかを明確な説明と図で解説しています。これにより、LuceneOpenSearchのようなオープンソースを、コードベース全体を詳細に調べることなく簡単に理解することができます。
deepwiki.com

Lucene Core


さて、LuceneOpenSearchにおけるインデックス作成や検索の核となるエンジンであることは周知の事実です。ドキュメントが取り込まれるたびに、Luceneは大量のオブジェクトを作成する必要があります。このオブジェクト作成ストームと呼ばれる現象が、JVMヒープに高い圧力をかけ、システムパフォーマンスに影響を与えます。
ドキュメントが取り込まれるたびに、Analyzer、Tokenizer、TokenStream、Document、Fieldといった主要なオブジェクトが作成されます。Analyzerはテキスト分析ルールを定義するクラスで、Tokenizerはテキストをトークン単位に分割し、TokenStreamはトークンを洗練・処理します。Documentは検索対象となる論理的なドキュメントで、Fieldはドキュメントの個々のデータピースです。
右側のフローでは、ドキュメントがorg.apache.lucene.analysisからorg.apache.lucene.index、そしてorg.apache.lucene.storeやcodecsを経て、最終的にLuceneインデックス(シャード)の一部になるまでの流れを見ることができます。
ここでの課題はメモリプレッシャーです。これらのオブジェクトはすべて短命であるため、Memory Churnが発生し、頻繁なStop-The-World GCポーズが起こります。その結果、応答時間の増加とクラスタの不安定性の増大という深刻なパフォーマンス低下を経験することになります。

インデックス作成の流れ


この図はインデックス作成のパイプラインを示しています。
テキストはCharacter Filter、Tokenizer、Token Filterを通過し、その後IndexWriterがドキュメントのライフサイクルを管理し、マージポリシーをスケジュールします。最後に、ドキュメントはメモリマップやNIOファイルシステムといったDirectory実装によってディスクに保存されます。

Lucene Core [Advanced]


先ほどLuceneのメモリ管理の課題をオブジェクト作成ストームと表現しましたが、実際にはLuceneはアナライザーやトークンストリームのような既製のコンポーネントを再利用することでこの問題を克服しています。
マイクロレベルでは、トークンストリームが属性オブジェクトをリサイクルすることで、トークンテキストのためにオブジェクトを連続的に割り当てることを回避します。
そしてマクロレベルでは、アナライザーはフィールドごとに再利用されるため、同じインデックスにドキュメントをインデックス化するたびに分析チェーンを再構築する必要がありません。
これら2つの戦略を組み合わせることで、オブジェクト作成コストを削減し、ガベージコレクションからの圧力を大幅に軽減します。

Visual VM


Visual VMでメモリを視覚化すると、理想的にはのこぎりの歯のような規則的なギザギザのパターン(メモリが徐々に増加し、GCで急激に減少する周期的な動き)を期待しますが、実際にはヒープメモリは不安定に見えることがよくあります。
GCとCircuit Breakerは完全な保護とはなりません。これらはメモリ使用量が閾値を超えた後に反応しますが、重いワークロードの下では手遅れになる可能性があります。
Adaptive IHOPはGCを遅延させる可能性がありますが、Full GCストールのリスクを招きます。そのため、OOMシナリオを回避するためにはプロアクティブな分析が重要となります。
これを実際にどのように監視するのか疑問に思っているかもしれません。私のセットアップでは、Docker DesktopとDocker Composeを使用してOpenSearchクラスターを実行し、Visual VMに接続しました。ここでの鍵は、jmxremoteとrmiフラグを使用してJMX接続を有効にすることです。これにより、Visual VMとコンテナ内で実行されているプロセス間のネットワークブリッジが作成されます。
max_map_countを増やすことを忘れないでください。そうしないとOpenSearchは起動しません。これらの設定の後、追加のJavaオプションでクラスターを起動すると、前のスライドで見たように、ヒープ使用量とGCの動作について目に見える調査が可能になります。


Docker ComposeをベースにしたOpenSearchクラスターを、監視のためにVisual VMを接続して実行しています。クラスターは安定性を維持するために3つのクラスターマネージャー、インデックス作成のために2つのデータノード、そしてクエリを処理するために1つのコーディネーターノードを持っています。このようにして、メモリとCPUの使用状況を監視しながら、現実的なワークロードをシミュレートできます。


このクラスター設定では、コーディネーターノードも用意しています。その目的は、REST APIトラフィックをインデックス作成やクラスター管理のような実際のワークロードから分離することです。この分離がないと、すべてのノードがクエリ調整とインデックス作成の両方を処理しなければならず、重いワークロードの下ではオーバーヘッドと不安定な状態を引き起こす可能性があります。
専用のコーディネーターノードを介してAPI呼び出しをルーティングすることで、クラスターを外部トラフィックから独立させることができます。この設計により、システムはより予測可能で堅牢になり、特にロードテストやベンチマークテストを実装する際の重いクエリプレッシャーの下で効果を発揮します。

OpenSearch Benchmark(OSB)


OpenSearch Benchmark(OSB)はOpenSearchの公式ベンチマークツールです。
NYCタクシーデータ、HTTPログ、その他のデータセットを使用して、テストクラスターに対して現実的なワークロードを実行するのに役立ちます。ベンチマークホストはクラスターにクエリと取り込みを送信し、詳細な操作メトリクスを収集します。これらの結果は簡単に視覚化できます。

OSBによって、全体的なパフォーマンスを追跡し、アップグレードの適切な時期を決定し、異なる設定やマッピング変更の影響を評価できます。要するに、OSBは本番環境で変更を加える前に、クラスターがどのように動作するかを理解するのに役立ちます。

OSBを実行する際はストレージに注意してください。
一部のワークロードは、圧縮解除されると劇的に拡大します。たとえば、数百メガバイトに見えるものが数十ギガバイトになることもあります。 クラスターに十分な空き容量があること、特にHDDではなくSSDであることを確認してください。
次に、実際のユースケースに合ったワークロードを選択するようにしてください。それがログ、地理空間データ、ネストされたドキュメントのいずれであってもです。そうすることで、ベンチマークの結果は、実際に実行するインデックス作成と検索の種類を反映します。

ベンチマークの実行は簡単でシンプルです。ログからわかるように、簡単なコマンドでOSBが自動的にテストデータセットをダウンロードし、解凍し、インデックス作成してくれます。
OSBの詳細に興味がある場合はセッション「Mastering OpenSearch Benchmark: Advanced Techniques for Power Users」と「The Imitation Game: Encapsulate Your OSB Workload at Scale」をお勧めします。
www.youtube.com
www.youtube.com

JVMにおけるガベージコレクション

OpenSearchGC

では、JVMにおけるガベージコレクションについて見ていきましょう。これはJavaパフォーマンスのパフォーマンスと安定性にとって非常に重要です。

OpenSearchJava互換性を確認するところから始めましょう。
OpenSearchリリースは特定のJavaバージョンに紐付けられています。例えば、3.2のような最新バージョンは古いJavaバージョンをサポートしていますが、JDK 24とバンドルされています。Dockerイメージをベースにした実行中のクラスターでJava versionコマンドを実行することで簡単に確認できます。
ここで重要な洞察は、バージョンに関係なく、バンドルされたJDKはG1GCをデフォルトとしていますが、単にJVMオプションでuse ZGCを追加するだけでZGCに切り替えることができるということです。

このタイムラインからわかるように、G1GCは長い間代表的な地位に位置してきました。Java 9でデフォルトになり、現在まで維持されています。G1GCの最大の利点は、スループットレイテンシーのバランスが取れていることであり、汎用的で優れた選択肢となっています。

ガベージコレクション基礎知識


ガベージコレクションは複雑な概念なので、基本的な図から始めましょう。
新しいオブジェクトが作成されると、Young GenerationのEden Spaceに保存されます。

そのオブジェクトがいくつかのガベージコレクションサイクルを生き残ると、Survivor Spaceの1つに昇格します。

長い間生き残り続けるオブジェクトは、最終的にもう一度Tenured Space、つまりOld Generationに昇格します。現時点では、個別の用語については心配しないでください。この図を理解することが、本セッションで次に進むために必要なすべてです。
GCについてもっと勉強したい方には、このYouTubeビデオをお勧めします。基本的なGCを超えて学ぶことができます。
www.youtube.com

Compressed Pointer


今からガベージコレクションの重要な概念に焦点を当てます。最初にいくつか重要な用語を指摘したいと思います。
1つ目はJavaの圧縮ポインタです。Javaオブジェクトには参照が非常に多いため、JVMはこれらの参照サイズを小さくしようとします。不要なビットを圧縮することで、64ビットではなく32ビットでオブジェクト参照を表現できるのです。
仕組みはこうです。64ビットプラットフォームでは、メモリアドレスは8バイト単位で配置されます。つまり8で割り切れるため、アドレスの最後の3ビットは必ずゼロになります。JVMはこのゼロを省略して32ビットのオフセットだけを保持し、必要な時に3ビットを復元します。この方法により、32ビット参照で最大2の35乗、つまり約32GBまでのメモリ空間を扱えるようになります。
ただし、ヒープサイズが32GBを超えると、JVMは完全な64ビット参照を使わざるを得なくなります。この場合、参照サイズが2倍になるため、同じ数のオブジェクトを扱うのにより多くのメモリが必要となり、十分なメモリを追加しないとパフォーマンスが低下してしまいます。

STW (Stop-the-world)


2番目の用語はStop-The-Worldです。
これは、ガベージコレクターが動作している間、すべてのアプリケーションスレッドが一時停止される期間を指します。この一時停止は、ガベージコレクターがアプリケーションの進行中のタスクからの干渉なしにヒープメモリを安全かつ正確に管理するために必要です。

この図は、G1GCの並行サイクルを示しています。これはYoung GCの後に発生し、Mixed GC(Young GenerationとOld Generationを組み合わせたGC)を開始するタイミングを決定します。
Stop-The-Worldポーズを減らすことは一見常に有益に見えますし、多くの場合それは正しいです。しかし、完全に排除することは不可能であり、時にはポーズを許容した方がGC全体のスループットが向上することもあります。つまり、レイテンシとスループットトレードオフの関係があるということです。

Young/Old Generation


ほとんどすべてのGCは、弱い世代仮説に基づいて世代の概念を持っています。これは、ほとんどのオブジェクトは長く生き残らないというものです。しかし、これは常に真実ではありません。JVMが開始されてから終了するまで、最初から割り当てられる不滅のオブジェクトは常に存在します。

このグラフからわかるように、オブジェクトの寿命の分布は、ほとんどのオブジェクトが長く生き残れないため、弱い世代仮説の概念を理解するのに役立ちます。Minor CollectionsはYoung/Old領域で頻繁に発生し、Major CollectionsはOld領域でより少ない頻度で発生します。

Adaptive IHOP


最後の一つは、Initiating Heap Occupancy Percent(Adaptive IHOP)です。
G1GCは、以前のアプリケーションの動作に基づいてIHOPのしきい値を自動的に決定します。マーキングにかかる時間と、マーキングサイクル中にOld Generationに割り当てられるメモリの量を観察することで、最適な値を計算します。このオプションを無効にすると、Old Generationのヒープ占有率パーセントの固定値によって、並行サイクルでの初期マーキングを開始するタイミングが決定されます。

Stop-The-Worldを説明したページで、GCがMixed GCの適切なタイミングを決定しようとする並行サイクルについて議論しました。このサイクルは、IHOP、G1ReservePercent、およびHumongous Allocation(巨大オブジェクトの割り当て)の3つの原因によってトリガーされる可能性があります。明示的にサイクルを早く開始させるには、IHOPとG1ReservePercentのみを制御できます。なぜなら、Humongous AllocationはJavaランタイムの動作に依存するからです。

JVMのチューニングポイント

重要な概念をいくつか見てきたので、JVMのチューニングポイントを見ていきましょう。

UseStringDeduplication


興味深いオプションの1つは、文字列の重複排除を使用することです。これにより、異なる文字列オブジェクトが同じ基盤となる文字配列を共有できるようになり、メモリフットプリントが削減されます。この操作は文字列クラスのinternメソッドと非常によく似ていますが、ユーザーコードによって実行されるか、JVMによって管理されるかという観点に違いがあります。

OpenSearchやその他のLuceneベースのアプリケーションは、大量のテキストを処理します。このテキストは一時的な文字列オブジェクトを生成します。このサンプルコードは、その動作をシミュレートします。ログエントリをトークン化し、新しい文字列インスタンスを作成し、それらの一部を保持して、Old Generationに昇格させます。UseStringDeduplicationを有効にすると、JVMは異なる文字列オブジェクトが同じ文字を含んでいるかどうかを検出し、それらに同じ基盤となるバイト配列を共有させます。その結果、特にログデータのような反復的なテキストが支配的なワークロードでは、ヒープのフットプリントが大幅に削減されます。

これらのチャートは、UseStringDeduplicationが無効になっている状態でのサンプルコードの実行結果です。 ヒープメモリのほぼ70%がオブジェクトによって割り当てられ、ランタイム中にガベージコレクションが1000回発生しました。
JDK 21でこのシンプルなアプリケーションを実行したので、バイト配列の数は文字列オブジェクトの数に比例して増加しました。

フラグをオンにして、ヒープメモリに影響があるかどうか見てみましょう。
ヒープメモリの50%未満がオブジェクトによって割り当てられ、GCの数は1000回に達しませんでした。CPU使用率も以前よりも低かったのは、ガベージコレクションの頻度が少なかったため、文字列の重複排除の計算コストを補償したからです。
バイト配列の大幅な削減に注目してください。バイト配列のライブオブジェクトの数は、50万個以上からわずか1万5千個に大幅に減少しています。これはJVMが賢く、重複する文字列に同じ基盤となるバイナリを共有させているからです。

G1UseAdaptiveIHOP


次のチューニングポイントは、Adaptive IHOPを無効にすることです。通常、JVMは非常に賢く、クラスターがメモリをどのように使用しているかに基づいて、次のガベージコレクションサイクルを開始するタイミングを自動的に決定します。これはAdaptive IHOPと呼ばれ、ほとんどの場合、うまく機能します。しかし、OpenSearchのような要求の厳しいアプリケーションでは、より多くの制御が必要な場合があります。
Adaptive IHOPオプションを無効にすることで、JVMは並行サイクルを開始するタイミングを調整しなくなります。その場合、HeapOccupancyPercentはランタイム全体を通して固定されます。これにより、Initial Markingから並行サイクルをトリガーするタイミングをより予測可能にできます。

OpenSearch自体も関連するJVMのデフォルト値を変更しています。
GitHubJVMオプションファイルを見ると、OpenSearchはすでにG1ReservePercentとInitiatingHeapOccupancyPercentをそれぞれ25%と30%に変更してG1をチューニングしています。これらのデフォルト値では、GCが起動する前にヒープ使用量が増えすぎて、Circuit Breakerがトリガーされる可能性があるためです。
これは、Javaプロセスの理想的なオプションが、それがメモリ集約型であるかどうかによって異なることを示しています。また、ワークロードによっては、さらにカスタマイズする必要があることも意味します。

Adaptive IHOPを無効にし、IHOPを20に設定したとします。並行サイクルは早く開始され、マーキングとクリーンアップにより多くの時間が与えられ、昇格失敗やFull GCのリスクを軽減します。IHOPを45に設定すると、並行サイクルは遅れて開始され、GCが始まる前にドキュメントのインデックス作成やクエリのキャッシュのためにより多くのヒープをアプリケーションに与えます。これは、ワークロードが予測可能で、あまりバースト的でない場合に役立つかもしれません。

Adaptive Sizing


3つ目のチューニングポイントはG1GCがYoung GenerationとOld Generationの比率を決定する際の適応戦略です。
NewRatio、G1NewSizePercent、G1MaxNewSizePercentという3つのオプションがありますが、NewRatioのカスタマイズは推奨しません。これを使うとポーズタイム目標達成のためのYoung Generationサイズの柔軟な調整ができなくなるからです。

代わりにG1NewSizePercentとG1MaxNewSizePercentで世代サイズを自動管理させるべきです。ただし両者を同じ値に設定するとNewRatioを固定するのと同じになり、柔軟性が失われます。適切なアプローチは、これらの境界を狭めることで適応的サイジングの不確実性を減らしながら、ポーズタイム目標を達成できる柔軟性を維持することです。

2つの世代の黄金比を見つけたい方は、Open JDKのG1YoungGenSizerを確認することをオススメします。

OpenSearchのメモリ最適化の今後

OpenSearchのメモリ最適化に関する最近の機能についても簡単に紹介します。

OpenSearch 3.2が登場しました。
このリリースではLucene 10.2が統合され、JavaランタイムがJDK 24にアップグレードされました。
インデックス作成とベクトルの検索に関する重要なアップデートもあります。例えば、このグラフでわかるように、BIg 5のクエリのレイテンシが徐々に低下しています。リリースノートを読んで、アップデートを把握することをお勧めします。

バージョン2からは、すでにセグメントレプリケーション機能が導入されており、プライマリシャードノードのみがインデックス操作を実行し、セグメントファイルをリモートオブジェクトストアに書き込みます。
その後、レプリカシャードはオブジェクトストアからセグメントファイルを並行してダウンロードするため、各データノードでインデックス操作をリプレイする必要がなくなります。これにより、既存のデータノードのメモリ使用量を最適化できます。これは素晴らしい機能です。

以前、G1がJavaにおいて長い間代表的なGCであったと述べましたが、ZGC、特に世代別ZGCは徐々に注目を集めています。OpenSearch 3.2への移行に伴い、JDK 24に対応してZGCの非世代別モードは完全に削除されました。

ZGCの主な目標は、スループットの低下を最小限に抑えながら、Stop-The-Worldを10ミリ秒未満に削減することです。
OpenSearchプロジェクト内では、世代別DGCの採用について議論がなされています。もし、大量のヒープサイズを必要とするワークロードや、極めて低いレイテンシーを必要とするワークロードがある場合は、検討してみる価値があるでしょう。そこには興味深いベンチマークテストがいくつか見つかるはずです。
github.com

最後に、GitHubにあるOpenSearchプロジェクトのロードマップを見て終わりたいと思います。ご覧のとおり、多くのエキサイティングな改善が予定されており、これまでにないほど貢献が活発です。この講演の後、OpenSearchプロジェクトに参加し、このプロジェクトの未来を形作ることに意欲を感じていただければ幸いです。

QA

質問:JVMの文字列重複排除の欠点は何ですか?メモリの削減効果は印象的ですが。
回答:長所と短所があります。長所は同じ内容の文字列のバイト配列を別の文字列で再利用できることです。短所は、重複を検出するための計算が必要なため、CPU使用率が上がることです。しかし、私のケースでは、文字列重複排除を使用することでGCの頻度が減少したため、結果的にCPU使用率は文字列重複排除を使用しない場合よりもかなり低くなりました。一般的にはCPU使用率は増加しますが、GC頻度の削減効果によってトレードオフが有利になる場合があります。