die 50-Prozent-Regel, das 32-GB-Limit und der Lucene-Dateicache
JVM-Heap-Tuning in Elasticsearch ist keine Frage von "je mehr Heap, desto besser", sondern eine Balance zwischen dem JVM-Heap fuer Java-Objekte und dem Betriebssystem-Dateicache, den Lucene fuer Segmentzugriffe nutzt. Wer den Heap zu gross oder ohne Ruecksicht auf die 32-Gigabyte-Grenze fuer Compressed Oops setzt, verschenkt Performance und riskiert lange Garbage-Collection-Pausen genau bei hoher Last.
Inhaltsverzeichnis
- 1. Warum JVM-Heap-Tuning kein Rateversuch sein sollte
- 2. Die 50-Prozent-Regel: Heap versus Betriebssystem
- 3. Das 32-GB-Compressed-Oops-Limit
- 4. Off-Heap: der Lucene-Dateicache
- 5. Heap konkret konfigurieren
- 6. Garbage Collector: G1GC richtig einstellen
- 7. GC-Pausen erkennen und vermeiden
- 8. Circuit Breaker als Schutz vor Heap-Ueberlauf
- 9. Heap-Nutzung dauerhaft im Blick behalten
- 10. Zusammenfassung
- 11. FAQ
1. Warum JVM-Heap-Tuning kein Rateversuch sein sollte
Elasticsearch laeuft auf der JVM und erbt damit alle Eigenheiten von Java-Speicherverwaltung, inklusive Garbage Collection. Der haeufigste Fehler beim JVM-Heap-Tuning ist die Annahme, ein groesserer Heap sei automatisch besser: mehr Heap bedeutet mehr Speicher fuer Objekte, aber auch laengere Garbage-Collection-Zyklen, weil der Collector mehr Speicher durchsuchen muss, um nicht mehr referenzierte Objekte zu finden. Ab einer bestimmten Groesse kippt der Effekt und der Cluster wird durch GC-Pausen langsamer statt schneller.
Ein zweites, ebenso verbreitetes Missverstaendnis: Elasticsearch braucht nicht den gesamten verfuegbaren RAM als Heap. Lucene, der Suchindex-Unterbau, greift ueber Memory-Mapped-Files direkt auf den Betriebssystem-Dateicache zu, ohne den JVM-Heap zu belasten. JVM-Heap-Tuning bedeutet deshalb, bewusst Speicher zwischen Heap und Dateicache aufzuteilen, statt reflexartig den kompletten RAM dem Heap zuzuweisen.
Die zwei zentralen Faustregeln fuer JVM-Heap-Tuning, auf die dieser Beitrag im Detail eingeht, lauten: maximal fuenfzig Prozent des verfuegbaren RAM als Heap, und niemals ueber die Grenze von etwa zweiunddreissig Gigabyte hinausgehen, an der die JVM-Optimierung Compressed Oops wegfaellt. Beide Regeln zusammen verhindern die haeufigsten Fehlkonfigurationen in Produktionsclustern.
2. Die 50-Prozent-Regel: Heap versus Betriebssystem
Die zentrale Faustregel fuer JVM-Heap-Tuning lautet, maximal fuenfzig Prozent des auf einem Data-Node verfuegbaren RAM dem JVM-Heap zuzuweisen. Der Rest bleibt dem Betriebssystem fuer den Dateicache, in dem Lucene-Segmentdateien vorgehalten werden, sowie fuer andere Prozesse und den Netzwerkstack. Auf einem Node mit vierundsechzig Gigabyte RAM bedeutet das einen Heap von maximal zweiunddreissig Gigabyte, nicht mehr.
Diese Aufteilung wirkt zunaechst kontraintuitiv, weil ungenutzter Heap-Speicher vermeintlich verschenkt wird. Tatsaechlich ist der Dateicache fuer die Lesegeschwindigkeit von Suchanfragen mindestens genauso wichtig wie der Heap: Lucene greift ueber mmap auf Indexsegmente zu, und je mehr davon im Betriebssystem-Cache liegt, desto weniger Disk-I/O ist fuer eine Suchanfrage noetig. Ein zu grosser Heap auf Kosten des Dateicaches fuehrt paradoxerweise zu langsameren Suchanfragen, obwohl scheinbar mehr Speicher zur Verfuegung steht.
3. Das 32-GB-Compressed-Oops-Limit
Die JVM referenziert Java-Objekte normalerweise ueber 64-Bit-Pointer. Bei Heaps unter etwa zweiunddreissig Gigabyte aktiviert die JVM automatisch Compressed Oops, eine Optimierung, die Objektreferenzen auf 32 Bit komprimiert und dadurch Speicher spart sowie die CPU-Cache-Effizienz verbessert. Ueberschreitet der Heap diese Schwelle, schaltet die JVM automatisch auf unkomprimierte 64-Bit-Pointer um, was den effektiven Speicherbedarf pro Objekt erhoeht.
Das paradoxe Ergebnis: ein Heap von vierzig Gigabyte kann in der Praxis weniger nutzbaren Speicher fuer Objekte bereitstellen als ein Heap von einunddreissig Gigabyte mit aktivierten Compressed Oops, weil der Pointer-Overhead den vermeintlichen Groessengewinn auffrisst. Fuer JVM-Heap-Tuning bedeutet das: die Zone zwischen zweiunddreissig und etwa achtundvierzig Gigabyte Heap ist praktisch immer eine schlechtere Wahl als ein Heap knapp unter der Schwelle. Die exakte Grenze variiert leicht je nach JVM-Version, liegt aber typischerweise zwischen einunddreissig und zweiunddreissig Gigabyte.
# jvm.options.d/heap.options
# Rule 1: never exceed roughly 32 GB, compressed oops get disabled above that
# Rule 2: never exceed 50 percent of available system RAM
# Example for a data node with 64 GB total RAM
-Xms31g
-Xmx31g
# Xms and Xmx must always match, prevents heap resizing pauses at runtime
4. Off-Heap: der Lucene-Dateicache
Der Lucene-Dateicache ist kein separat konfigurierbarer Cache im Elasticsearch-Sinne, sondern schlicht der Betriebssystem-Seitencache, der automatisch kuerzlich gelesene Dateien im freien RAM vorhaelt. Da Lucene-Indexsegmente ueber Memory-Mapped-Files gelesen werden, profitieren wiederholte Zugriffe auf dieselben Segmente direkt vom Betriebssystem-Cache, ganz ohne explizite Konfiguration in Elasticsearch selbst.
Fuer JVM-Heap-Tuning folgt daraus eine klare Konsequenz: der RAM, der nicht dem Heap zugewiesen wird, ist kein verschwendeter Speicher, sondern aktiv genutzter Cache fuer den haeufigsten Zugriffspfad in einem Suchsystem, naemlich das Lesen von Indexdaten. Ein Data-Node mit sechzehn Gigabyte RAM und acht Gigabyte Heap hat effektiv acht Gigabyte fuer den Dateicache zur Verfuegung, ein Node mit vierundsechzig Gigabyte RAM und zweiunddreissig Gigabyte Heap hat entsprechend deutlich mehr Cache-Kapazitaet fuer denselben relativen Heap-Anteil.
5. Heap konkret konfigurieren
In modernen Elasticsearch-Versionen wird der Heap ueber Dateien im Verzeichnis config/jvm.options.d/ konfiguriert, statt die zentrale jvm.options-Datei direkt zu editieren. Das erleichtert automatisiertes Deployment, da eigene Heap-Einstellungen unabhaengig von Elasticsearch-Updates erhalten bleiben. Xms und Xmx, die minimale und maximale Heap-Groesse, sollten beim JVM-Heap-Tuning immer identisch gesetzt werden, um zu verhindern, dass die JVM den Heap zur Laufzeit dynamisch vergroessert, was selbst zu Pausen fuehrt.
Alternativ kann seit neueren Versionen die Umgebungsvariable ES_JAVA_OPTS oder die automatische Heap-Erkennung von Elasticsearch genutzt werden, die standardmaessig fuenfzig Prozent des verfuegbaren RAM bis zur Compressed-Oops-Grenze waehlt. Diese automatische Erkennung ist ein guter Startpunkt, ersetzt aber keine bewusste Ueberpruefung fuer produktive Data-Nodes mit spezifischen Workload-Anforderungen.
# Alternative: override heap via environment variable at container start
# Useful for Docker or Kubernetes deployments with per-node RAM sizes
ES_JAVA_OPTS="-Xms31g -Xmx31g"
# Elasticsearch also ships an automatic heap sizing script that picks
# roughly 50 percent of available RAM up to the compressed oops limit,
# a reasonable default but always worth verifying for production nodes
| RAM des Nodes | Empfohlener Heap | Dateicache verbleibend | Compressed Oops |
|---|---|---|---|
| 16 GB | 8 GB | 8 GB | aktiv |
| 32 GB | 16 GB | 16 GB | aktiv |
| 64 GB | 31 GB | 33 GB | aktiv |
| 128 GB | 31 GB (nicht 64 GB) | 97 GB | aktiv |
| 128 GB (falsch) | 64 GB | 64 GB | deaktiviert |
6. Garbage Collector: G1GC richtig einstellen
Seit Elasticsearch 7 ist G1GC der Standard-Garbage-Collector und fuer die allermeisten Workloads die richtige Wahl, da er im Vergleich zum aelteren CMS-Collector kuerzere und vorhersehbarere Pausen erzeugt. G1GC arbeitet regionenbasiert und kann konfiguriert werden, welche maximale Pausenzeit angestrebt wird, ueber -XX:MaxGCPauseMillis, wobei diese Einstellung als Zielwert und nicht als harte Garantie zu verstehen ist.
Fuer die meisten Produktionscluster ist es beim JVM-Heap-Tuning sinnvoller, die Standardwerte von G1GC beizubehalten und stattdessen die Heap-Groesse selbst zu optimieren, statt einzelne GC-Parameter manuell zu justieren. Detailliertes GC-Tuning jenseits der Heap-Groesse ist meist nur bei sehr spezifischen Workload-Mustern noetig, etwa extremen Bulk-Indexierungsraten, und sollte auf Basis von GC-Logs entschieden werden, nicht auf Verdacht.
# jvm.options.d/gc.options
# G1GC is the default since Elasticsearch 7, usually leave defaults untouched
-XX:+UseG1GC
-XX:G1ReservePercent=25
-XX:InitiatingHeapOccupancyPercent=30
# Enable GC logging to diagnose pause issues before tuning further
-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
7. GC-Pausen erkennen und vermeiden
Lange GC-Pausen sind fuer Elasticsearch besonders gefaehrlich, weil ein Node, der laenger als der konfigurierte Timeout in einer Stop-the-World-Pause haengt, vom Cluster als nicht erreichbar eingestuft werden kann. Auf einem Data-Node fuehrt das zu Shard-Neuzuteilungen, auf einem Master-Node im schlimmsten Fall zu einem ungewollten Master-Wechsel. Die Log-Zeile [gc][young] mit ungewoehnlich hoher Dauer in den Elasticsearch-Logs ist meist das erste sichtbare Symptom.
Die haeufigste Ursache fuer lange GC-Pausen ist ein zu voller Heap durch Anfragen, die grosse Datenmengen im Speicher aufbauen, etwa Aggregationen mit hoher Kardinalitaet oder fielddata auf Textfeldern. Effektives JVM-Heap-Tuning bedeutet deshalb nicht nur, die richtige Heap-Groesse zu waehlen, sondern auch, problematische Query-Muster zu identifizieren, die den Heap unnoetig unter Druck setzen, bevor eine reine Groessenanpassung das eigentliche Problem verdeckt.
8. Circuit Breaker als Schutz vor Heap-Ueberlauf
Elasticsearch verwendet mehrere Circuit Breaker, die Operationen aktiv abbrechen, bevor sie den Heap zum Ueberlaufen bringen und einen OutOfMemoryError ausloesen. Der indices.breaker.total.limit begrenzt den insgesamt fuer Anfragen reservierbaren Heap-Anteil, waehrend spezifischere Breaker wie fielddata oder request gezielt einzelne Speicherbereiche schuetzen. Ein ausgeloester Circuit Breaker ist unangenehm fuer die betroffene Anfrage, aber deutlich besser als ein abgestuerzter Node.
Bei haeufig ausloesenden Circuit Breakern ist das kein Grund, sie einfach hoeher zu setzen, sondern ein Signal fuer echtes JVM-Heap-Tuning: entweder ist der Heap zu klein fuer die tatsaechliche Workload, oder bestimmte Anfragen sind ineffizient und sollten optimiert werden, etwa durch Nutzung von doc_values statt fielddata fuer Aggregationen und Sortierungen auf Textfeldern.
GET _nodes/stats/breaker
// Response excerpt for one data node, tripped_count above zero
// means a breaker actually rejected a request due to heap pressure
{
"nodes": {
"abc123": {
"breakers": {
"request": { "limit_size_in_bytes": 6442450944, "estimated_size_in_bytes": 1288490188, "tripped": 0 },
"fielddata": { "limit_size_in_bytes": 4295098368, "estimated_size_in_bytes": 4290000000, "tripped": 12 },
"parent": { "limit_size_in_bytes": 16106127360, "estimated_size_in_bytes": 8100000000, "tripped": 0 }
}
}
}
}
Mironsoft
Elasticsearch- und OpenSearch-Betrieb, Heap-Tuning und Performance-Analyse
Lange GC-Pausen und Heap-Fehler im Griff behalten?
Wir analysieren GC-Logs, identifizieren speicherhungrige Query-Muster und finden die passende Heap-Konfiguration fuer eure Data-Nodes, ohne pauschale Werte zu uebernehmen.
GC-Log-Analyse
Pausenzeiten auswerten und Heap-Groesse gegen tatsaechliche Auslastung pruefen
Query-Optimierung
Speicherhungrige Aggregationen und fielddata-Nutzung identifizieren und ersetzen
Sizing-Beratung
Heap- und RAM-Dimensionierung fuer neue oder bestehende Data-Nodes festlegen
9. Heap-Nutzung dauerhaft im Blick behalten
Einmaliges JVM-Heap-Tuning beim Cluster-Setup reicht nicht aus, weil sich Workloads und Datenvolumen im Laufe der Zeit veraendern. Die _nodes/stats/jvm API liefert detaillierte Heap-Metriken pro Node, inklusive der Aufteilung nach Speicherbereichen wie Young- und Old-Generation. Ein dauerhaftes Monitoring dieser Werte zeigt fruehzeitig, ob der Heap konstant nahe der Obergrenze laeuft, was ein klares Signal fuer Nachbesserung ist.
Als Faustregel gilt: eine Heap-Auslastung, die dauerhaft ueber fuenfundsiebzig Prozent liegt und nach jedem Garbage-Collection-Zyklus nicht deutlich absinkt, deutet auf zu wenig Heap fuer die tatsaechliche Last hin. Umgekehrt zeigt eine dauerhaft niedrige Auslastung unter dreissig Prozent, dass Speicher fuer den Dateicache abgezweigt werden koennte. Gutes JVM-Heap-Tuning ist ein iterativer Prozess, der sich an echten Metriken orientiert, nicht an einer einmal gesetzten Konfiguration.
GET _cat/nodes?v&h=name,heap.percent,heap.current,heap.max,ram.percent
// heap.percent sustained above 75 percent across GC cycles
// is the clearest signal that heap sizing needs another look
name heap.percent heap.current heap.max ram.percent
data-node-01 78 24.2gb 31gb 91
data-node-02 41 12.7gb 31gb 88
data-node-03 82 25.4gb 31gb 93
10. Zusammenfassung
Solides JVM-Heap-Tuning folgt zwei klaren Regeln: maximal fuenfzig Prozent des verfuegbaren RAM als Heap, und nie ueber die etwa zweiunddreissig Gigabyte Compressed-Oops-Grenze hinaus, weil darueber der effektive Speichergewinn durch groessere Pointer aufgezehrt wird. Der verbleibende RAM ist kein verschwendeter Speicher, sondern aktiv genutzter Lucene-Dateicache, der Suchanfragen direkt beschleunigt. G1GC als Standard-Collector sollte in den meisten Faellen nicht manuell nachjustiert werden.
Circuit Breaker schuetzen vor Heap-Ueberlauf, sind aber ein Symptom, kein Ziel: haeufig ausloesende Breaker zeigen entweder zu knappen Heap oder ineffiziente Anfragen. Wer GC-Logs und Heap-Metriken kontinuierlich beobachtet statt sich auf einmalig gesetzte Werte zu verlassen, erkennt Fehlkonfigurationen, bevor sie zu Node-Ausfaellen oder ungewollten Master-Wechseln fuehren.
JVM-Heap-Tuning: das Wichtigste auf einen Blick
50-Prozent-Regel
Nie mehr als die Haelfte des verfuegbaren RAM als Heap zuweisen, der Rest gehoert dem Dateicache.
32-GB-Grenze
Compressed Oops fallen oberhalb von rund zweiunddreissig Gigabyte weg, ein Heap knapp darunter ist meist effizienter.
Xms gleich Xmx
Minimale und maximale Heap-Groesse immer identisch setzen, verhindert Resizing-Pausen zur Laufzeit.
GC-Logs beobachten
Heap-Metriken und Pausenzeiten laufend pruefen, statt nach einmaliger Konfiguration nichts mehr nachzujustieren.