Skip to main content
Metrics type: Key MetricsCategory: Capacity

At a glance

The percentage of each node’s Java heap currently in use, surfaced as a live gauge with the hottest node driving the headline. JVM heap is the single most load-bearing capacity signal on an Elasticsearch node. Above roughly 75% the garbage collector starts working hard, GC pauses lengthen, and the parent circuit breaker begins rejecting expensive requests to protect the node. Above 90% the node is one heavy aggregation away from an OutOfMemory crash. For a DBA, this gauge is the early-warning light for the most common cause of node instability.

Calculation

The card reads jvm.mem.heap_used_percent directly from each node’s JVM stats. Elasticsearch already computes the percentage as:
where heap_max_in_bytes is the configured -Xmx ceiling, not the physical RAM of the host. This distinction matters: a node with 64 GB of RAM but a 30 GB heap (the recommended cap to stay under the compressed-oops threshold) is at 100% heap when it has used 30 GB, even though 34 GB of RAM sits free for the OS page cache. The gauge is measuring the JVM ceiling, not the machine ceiling. The headline is the maximum across all nodes, because Elasticsearch stability is gated by its hottest node: a single node at 92% can OOM and leave the cluster even if the cluster average is a comfortable 60%. The 1-minute chart deliberately preserves the GC sawtooth. Heap rises as the node allocates, then drops sharply when a garbage collection reclaims old-generation space. A healthy node shows a steady sawtooth with the troughs (post-GC baseline) well below 75%. The danger sign is not the peaks but the troughs creeping upward: when post-GC heap no longer falls back down, the node is accumulating live data it cannot reclaim, and OOM is approaching.

Worked example

A platform team runs a 5-node Elasticsearch cluster (each node with a 31 GB heap) serving an analytics and product-search workload. A new dashboard ships on 18 Apr 26 that runs a heavy terms aggregation with a high size on a high-cardinality field. Snapshot at 14:20 BST: The gauge headline reads 94% (driven by es-data-3) outlined in red. The alert fired when the first node crossed 75%. The on-call DBA’s read:
The cause was the new aggregation loading a high-cardinality field into the request circuit breaker’s accounting and inflating fielddata on the hot shard that happened to live on es-data-3. Short-term mitigation: the team capped the aggregation size and added a search.max_buckets guard. Heap on es-data-3 dropped back to a 60% post-GC trough within minutes. Medium-term: they spread the hot shard and reviewed whether the field should be keyword with eager_global_ordinals to amortise the cost. Three takeaways for an ops team:
  1. Watch the troughs, not the peaks. Peaks above 75% are uncomfortable but survivable if GC reclaims them. Troughs that stop falling mean the node is holding live data it cannot release, and that is the true OOM precursor.
  2. One hot node is the whole cluster’s problem. The headline is the max for a reason. A node that OOMs leaves the cluster, its shards go unassigned, and you inherit a recovery storm on top of the original heap issue.
  3. Heap pressure has upstream causes, not just “add RAM”. Unbounded aggregations, large fielddata on text fields, oversized bulk requests, and too many shards per node all drive heap. Raising -Xmx past 31 GB is usually the wrong answer (it loses compressed oops); fixing the workload or adding nodes is right.

Sibling cards

Reconciling against the source

Where to look in Elasticsearch’s own tooling:
GET /_nodes/stats/jvm returns jvm.mem.heap_used_percent, heap_used_in_bytes, and heap_max_in_bytes per node; this is the authoritative source. GET /_cat/nodes?v&h=name,heap.percent,heap.current,heap.max gives a quick per-node heap table. GET /_nodes/stats/breaker shows circuit-breaker limits and tripped counts, the partner signal to heap. GET /_nodes/stats/jvm -> gc.collectors exposes GC counts and collection times for diagnosing pause behaviour. On Elastic Cloud, Stack Monitoring plots “JVM Heap” per node; on AWS OpenSearch, the CloudWatch metric is JVMMemoryPressure, which tracks the same percentage.
Why our number may legitimately differ from a raw stats read: Cross-connector reconciliation: a heap spike that coincides with a traffic burst on the storefront is capacity, not a leak. Compare with ES Search Pool Saturation vs Ecom Burst; if heap climbs only during ecom peaks, the cluster is undersized for peak search load rather than misconfigured.

Known limitations / FAQs

My heap regularly touches 80% but the cluster is fine. Is that a problem? Not necessarily. What matters is whether garbage collection reclaims it. If the post-GC trough falls back below 75%, the node is breathing normally and the peaks are just the GC sawtooth. The alarm at 75% is a watch threshold, not a crash threshold; combine it with GC Pause Time (5m total ms) to judge whether the pressure is harmful. Why is the gauge the maximum node and not the average? Because Elasticsearch stability is gated by its hottest node. A single node at 93% can OOM and leave the cluster even when the average is 60%. Showing the average would hide the node that is actually at risk. Should I just raise -Xmx to give the node more heap? Usually not. The recommended ceiling is around 30 to 32 GB to stay under the JVM’s compressed-ordinary-object-pointer threshold; above that, pointers become 64-bit and you lose memory efficiency, often making things worse. Fix the workload (cap aggregations, reduce fielddata, lower shard count per node) or add nodes instead. Heap is low but the node still feels memory-pressured. Why? Elasticsearch relies heavily on off-heap memory: the OS page cache holds Lucene segment data via memory-mapped files. That memory is not on the JVM heap and does not appear in this gauge. If the host is short on free RAM for the page cache, search slows even with healthy heap. Check host-level memory separately. What actually causes a heap spike? The usual culprits are unbounded terms aggregations on high-cardinality fields, loading fielddata on analysed text fields, very large bulk or search requests, too many shards per node (each shard carries overhead), and large scroll or PIT contexts left open. The breaker stats (GET /_nodes/stats/breaker) tell you which category is loaded. The circuit breaker tripped before heap hit 100%. Is that a bug? No, that is the breaker doing its job. The parent circuit breaker rejects requests once their projected allocation would push heap past its limit (default 95% of heap), specifically to prevent the OOM that hitting 100% would cause. A trip is a protective rejection, not a failure. See Circuit Breaker Trips (24h). One node sits much higher than the others. Why? Almost always an uneven shard layout: a hot shard (high write or query volume) or an oversized shard lives on that node. Check Shard Size Skew % and rebalance, or split the hot index so its load spreads across more nodes.

Tracked live in Vortex IQ Nerve Centre

JVM Heap Used % is one of hundreds of KPI pulses Vortex IQ tracks across Elasticsearch and 70+ other ecommerce connectors. Nerve Centre runs the detection layer; Vortex Mind investigates the cause when something moves; Ask Viq lets you interrogate any number in plain English. Start for free or book a demo to see this metric running on your own data.