JVM & Performance Tuning

Elasticsearch JVM heap sizing, garbage collection tuning, memory lock, and system-level performance optimizations.

25m15m reading10m lab

Heap Sizing Rules

The Golden Rules

  1. 1 Set Xms = Xmx — Always. No dynamic resizing.
  2. 2 Never exceed 50% of RAM — The OS needs the other 50% for filesystem cache.
  3. 3 Never exceed 31GB — Beyond this, the JVM disables compressed ordinary object pointers (compressed oops), which wastes memory.
  4. 4 Minimum 1GB — Elasticsearch won't start with less.

Sizing by Server RAM

Server RAMHeap (-Xms/-Xmx)OS CacheNotes
4GB2g2GBDevelopment only
8GB4g4GBSmall production
16GB8g8GBMedium production
32GB16g16GBRecommended production
64GB31g33GBMaximum useful heap
128GB31g97GBExtra RAM goes to cache

Setting Heap in Docker

environment:
  - "ES_JAVA_OPTS=-Xms8g -Xmx8g"

Or in jvm.options:
-Xms8g
-Xmx8g

Memory Lock

Swapping is the enemy of Elasticsearch. When the OS swaps heap memory to disk, GC pauses become minutes instead of milliseconds.

Enable Memory Lock

In elasticsearch.yml:

bootstrap.memory_lock: true

In Docker Compose:

services:
  elasticsearch:
    ulimits:
      memlock:
        soft: -1
        hard: -1

Verify Memory Lock

curl -s http://localhost:9200/_nodes?filter_path=**.mlockall

Expected response:

{
  "nodes": {
    "node-id": {
      "process": {
        "mlockall": true
      }
    }
  }
}

If mlockall is false, check:
  • Docker memlock ulimits
  • LimitMEMLOCK=infinity in systemd service file
  • Sufficient RAM available

Garbage Collection

Elasticsearch uses G1GC by default (since ES 7.x). The defaults work well for most cases.

GC Monitoring

Watch GC activity in the logs:

# Check GC stats
curl -s http://localhost:9200/_nodes/stats/jvm?filter_path=**.gc

GC Warning Signs

SymptomCauseFix
Frequent young GCHeap too smallIncrease heap
Long old GC pausesHeap too large (>31GB)Reduce to 31g max
OutOfMemoryErrorHeap exhaustedIncrease heap or reduce load
High GC overheadToo many small objectsCheck field data, aggregations

GC Tuning Options

In jvm.options (rarely needed):

# G1GC settings (defaults are usually fine)
-XX:+UseG1GC
-XX:G1HeapRegionSize=4m
-XX:InitiatingHeapOccupancyPercent=75

# GC logging (enabled by default in ES)
-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m

Virtual Memory (vm.max_map_count)

Elasticsearch uses mmap for efficient file access. The default OS limit is too low.

# Check current value
sysctl vm.max_map_count

# Set for current session
sudo sysctl -w vm.max_map_count=262144

# Set permanently
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
Docker Desktop: On macOS/Windows with Docker Desktop, this is already set. On Linux, you must set it manually.

File Descriptors

Elasticsearch opens many files. The default limit (1024) is far too low.

# Check current limit
ulimit -n

# Set for current session
ulimit -n 65535

In Docker Compose:

services:
  elasticsearch:
    ulimits:
      nofile:
        soft: 65535
        hard: 65535

Disk I/O Optimization

Use SSDs

Elasticsearch is I/O-intensive. SSDs provide 10-100x better random read performance than HDDs.

I/O Scheduler

For SSDs, use noop or none scheduler:

echo noop | sudo tee /sys/block/sda/queue/scheduler

Disable Swappiness

sudo sysctl -w vm.swappiness=1

Network Buffer Tuning

For high-throughput clusters:

sudo sysctl -w net.core.somaxconn=65535
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=65535

Performance Checklist

Use this checklist before going to production:

SettingValueVerified
Heap size50% of RAM, max 31GB
Xms = XmxSame value
memory_locktrue
vm.max_map_count262144
File descriptors65535+
SwapDisabled or swappiness=1
DiskSSD with noop scheduler
GCG1GC (default)

Diagnosing Performance Issues

Hot Threads API

Find what Elasticsearch is spending time on:

curl -s http://localhost:9200/_nodes/hot_threads

Node Stats

Check memory and GC pressure:

# JVM memory usage
curl -s 'http://localhost:9200/_nodes/stats/jvm?pretty' | \
  grep -A 5 '"heap_used"'

# Thread pool rejections
curl -s 'http://localhost:9200/_cat/thread_pool?v&h=name,active,rejected,completed'

Pending Tasks

Check for cluster bottlenecks:

curl -s http://localhost:9200/_cluster/pending_tasks?pretty

Lab: Tune JVM Performance

  1. 1 Check current heap settings with GET _nodes/stats/jvm?pretty
  2. 2 Modify ES_JAVA_OPTS in Docker Compose to set heap to 2GB
  3. 3 Verify memory lock is enabled with GET _nodes?filter_path=**.mlockall
  4. 4 Monitor GC activity in Elasticsearch logs
  5. 5 Run the performance checklist against your cluster

Next Steps

Discussion