ELK Stack — JVM and System Tuning
JVM and System Tuning
Elasticsearch runs on the JVM, so garbage collection behavior, heap sizing, and OS-level settings directly affect cluster stability and performance. Getting these right prevents out-of-memory crashes, long GC pauses, and degraded throughput.
Heap Size
The 50% / 31 GB Rules
- Set heap to 50% of physical RAM — the other 50% is for the OS filesystem cache, which Lucene relies on heavily
- Never exceed 31 GB — above ~31 GB, the JVM cannot use compressed ordinary object pointers (compressed oops), wasting memory
| Node RAM | Recommended Heap | Filesystem Cache |
|---|---|---|
| 16 GB | 8 GB | 8 GB |
| 32 GB | 16 GB | 16 GB |
| 64 GB | 31 GB | 33 GB |
| 128 GB | 31 GB | 97 GB |
Warning: Setting heap above 31 GB is almost always worse than 31 GB because you lose compressed oops, effectively wasting ~4 GB of the increase.
Setting Heap Size
Edit the JVM options file:
# /etc/elasticsearch/jvm.options.d/heap.options
-Xms16g
-Xmx16g
Important: Always set
-Xmsand-Xmxto the same value. Different values cause the JVM to pause while resizing the heap.
Verify Heap Settings
curl -s localhost:9200/_nodes/stats/jvm?filter_path=nodes.*.jvm.mem | python3 -m json.tool
{
"nodes": {
"abc123": {
"jvm": {
"mem": {
"heap_used_in_bytes": 8589934592,
"heap_used_percent": 52,
"heap_committed_in_bytes": 17179869184,
"heap_max_in_bytes": 17179869184
}
}
}
}
}
Garbage Collection
G1GC (Default in ES 8.x)
Elasticsearch 8.x ships with G1GC as the default garbage collector. G1GC divides the heap into regions and collects the regions with the most garbage first.
# /etc/elasticsearch/jvm.options.d/gc.options
# G1GC is default — these are tuning knobs
-XX:+UseG1GC
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=40
-XX:G1ReservePercent=15
-XX:MaxGCPauseMillis=200
| Parameter | Default | Purpose |
|---|---|---|
G1HeapRegionSize |
Auto | Size of each G1 region (1–32 MB) |
InitiatingHeapOccupancyPercent |
45 | Start concurrent GC when heap is this full |
G1ReservePercent |
10 | Reserve heap to reduce promotion failures |
MaxGCPauseMillis |
200 | Target maximum GC pause time |
GC Logging
Enable GC logs to diagnose performance issues:
# /etc/elasticsearch/jvm.options.d/gc-logging.options
-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
Interpreting GC Logs
Watch for:
- Long pauses (> 500 ms) — indicates heap pressure or too many old-gen objects
- Frequent full GCs — heap is too small or there is a memory leak
- To-space exhaustion — G1 cannot find empty regions; increase
G1ReservePercent
Memory Pressure
Monitor JVM memory pressure via the nodes stats API:
curl -s "localhost:9200/_nodes/stats/jvm?filter_path=nodes.*.jvm.mem.heap_used_percent"
| Heap Used % | Status | Action |
|---|---|---|
| < 75% | Healthy | None |
| 75–85% | Warning | Investigate queries, reduce shard count |
| 85–95% | Critical | Add nodes, increase heap, enable circuit breakers |
| > 95% | Danger | Node may OOM; fix immediately |
Circuit Breakers
Circuit breakers prevent operations from consuming too much memory and crashing the node.
| Breaker | Default Limit | Purpose |
|---|---|---|
indices.breaker.fielddata.limit |
40% of heap | Field data (text field aggregations) |
indices.breaker.request.limit |
60% of heap | Per-request data structures |
indices.breaker.total.limit |
70% of heap | Combined limit across all breakers |
network.breaker.inflight_requests.limit |
100% of heap | In-flight network data |
Configuring Circuit Breakers
curl -X PUT "localhost:9200/_cluster/settings" \
-H 'Content-Type: application/json' -d'
{
"persistent": {
"indices.breaker.fielddata.limit": "35%",
"indices.breaker.request.limit": "55%",
"indices.breaker.total.limit": "65%"
}
}'
When Breakers Trip
You will see errors like:
{
"error": {
"type": "circuit_breaking_exception",
"reason": "[fielddata] Data too large, data for [user.keyword] would be [11512345600/10.7gb], which is larger than the limit of [10737418240/10gb]"
},
"status": 429
}
Fixes:
- Reduce fielddata usage — avoid text field aggregations
- Use
keywordfields ordoc_valuesfor aggregations - Increase heap (if under 31 GB)
- Add more data nodes to spread the load
OS-Level Settings
The operating system must be configured to support Elasticsearch’s requirements.
Disable Swapping
Swapping destroys Elasticsearch performance. Disable it completely:
# Option 1: Disable swap entirely (recommended)
sudo swapoff -a
# Remove swap entries from /etc/fstab
# Option 2: Set swappiness to 1
sudo sysctl vm.swappiness=1
echo "vm.swappiness=1" | sudo tee -a /etc/sysctl.conf
Or use memory locking in Elasticsearch:
# elasticsearch.yml
bootstrap.memory_lock: true
# Verify memory lock
curl -s localhost:9200/_nodes?filter_path=**.mlockall
{
"nodes": {
"abc123": {
"process": {
"mlockall": true
}
}
}
}
File Descriptors
Each shard opens many files. Set a high file descriptor limit:
# /etc/security/limits.conf
elasticsearch - nofile 65535
# Verify
curl -s localhost:9200/_nodes/stats/process?filter_path=**.max_file_descriptors
Virtual Memory
Lucene uses mmap to map segment files. Increase the vm.max_map_count:
sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
# Verify
sysctl vm.max_map_count
Thread Count
Ensure sufficient threads:
# /etc/security/limits.conf
elasticsearch - nproc 4096
Complete jvm.options Example
# /etc/elasticsearch/jvm.options.d/custom.options
## Heap — 50% of 64 GB RAM
-Xms31g
-Xmx31g
## GC — G1GC tuning
-XX:+UseG1GC
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=40
-XX:G1ReservePercent=15
-XX:MaxGCPauseMillis=200
## GC logging
-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
## Performance
-XX:+AlwaysPreTouch
-server
## Error handling
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/lib/elasticsearch
-XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log
Monitoring JVM Health
# JVM stats for all nodes
curl -s localhost:9200/_nodes/stats/jvm?pretty
# Specific node
curl -s localhost:9200/_nodes/hot-01/stats/jvm?pretty
# Compact view — heap percent per node
curl -s localhost:9200/_cat/nodes?v&h=name,heap.percent,heap.current,heap.max,ram.percent,cpu
name heap.percent heap.current heap.max ram.percent cpu
hot-01 52 16.1gb 31gb 78 12
hot-02 48 14.9gb 31gb 75 10
warm-01 35 5.4gb 16gb 45 4
Quick Reference
| Setting | Recommended Value |
|---|---|
| Heap size | 50% of RAM, max 31 GB |
-Xms = -Xmx |
Always equal |
| GC | G1GC (default in ES 8.x) |
bootstrap.memory_lock |
true |
vm.swappiness |
0 or 1 |
vm.max_map_count |
262144 |
nofile |
65535 |
nproc |
4096 |
| Fielddata breaker | 35–40% of heap |
| Total breaker | 65–70% of heap |
← Shard Strategy · Next → Indexing Performance