Performance Troubleshooting: A Solid Checklist
AI generated
_doc
_index
Elasticsearch · OpenSearch · Performance · Diagnostics
Performance Troubleshooting
a solid checklist for a real incident under time pressure

When an Elasticsearch or OpenSearch cluster suddenly slows down, aimless trial and error rarely helps. This checklist combines slow log, hot threads API, pending tasks and queue rejections into a systematic diagnostic path that finds the actual cause of a performance problem instead of just treating symptoms.

19 min read Slow Log · Hot Threads · Pending Tasks · Rejections Elasticsearch 8.x · OpenSearch 2.x

1. Why systematic diagnosis beats aimless trial and error

A slow Elasticsearch cluster rarely has a single, obvious cause. Usually it is a combination of several factors: an unusually expensive query, a node with too little heap, an unfavorable shard distribution, or a temporary load spike overloading a thread pool. Anyone who randomly changes settings during a performance problem, for example increasing heap size or reducing replicas, without having understood the actual cause, risks briefly relieving the symptom while the real problem reappears at the next load increase.

A systematic performance diagnosis instead follows a fixed order: first a coarse overview of cluster health and resource usage, then a targeted search for slow operations via slow log and the hot threads API, followed by checking for structural bottlenecks such as a backed-up pending tasks queue or rejected requests in overloaded queues. This order ensures obvious, coarse problems are ruled out first, before time is invested analyzing more subtle causes.

This article combines the most important diagnostic tools into a practical checklist that can be worked through directly during a real incident, supplemented with concrete API calls and typical interpretations of the results.

2. Step one: checking cluster health and resource usage

The first thing to check for any performance problem is the cluster health status and the basic resource usage across all nodes. A status of yellow or red points to missing or unassigned shards, which often directly correlates with a performance drop, because affected requests have to fall back to fewer copies of the data or fail outright. The node statistics additionally show CPU usage, heap usage, disk I/O, and free disk space per node; often this coarse overview alone is enough to identify a single overloaded node as the cause.

A particularly important thing to check is disk watermarks: once a node reaches the high watermark for used disk space, Elasticsearch automatically blocks further shard allocation on that node, which can lead to an unfavorable, unbalanced load distribution across the entire cluster. This state is easy to miss because the cluster health status often still stays green, even though the actual resource distribution is already heavily unbalanced and individual nodes are significantly more loaded than others.


# Overall cluster health
curl -s "https://localhost:9200/_cluster/health?pretty" -u elastic:changeme

# Per-node resource usage: CPU, heap, disk
curl -s "https://localhost:9200/_cat/nodes?v&h=name,cpu,heap.percent,ram.percent,disk.used_percent" \
  -u elastic:changeme

# Check disk watermark thresholds currently configured
curl -s "https://localhost:9200/_cluster/settings?include_defaults=true&filter_path=**.disk.watermark*" \
  -u elastic:changeme

3. Slow log: identifying slow queries and indexing operations

The slow log records queries and indexing operations that exceed a configurable time threshold, separated into search and indexing slow logs, and further split by the query and fetch phases for search operations. Without an enabled slow log, it stays unclear which specific requests are responsible for a high performance load, since general cluster metrics only show the overall load, not which individual query is unusually expensive.

A sensible threshold for the warn level is often one to two seconds for the query phase, depending on the latency requirements of the specific application. Thresholds set too low flood the log with entries and make analysis harder, thresholds set too high leave relevant but not yet extremely slow queries undetected. A proven approach is to start with a conservative threshold and lower it step by step until the slow log delivers a manageable but meaningful volume of entries.


// Enable slow log thresholds for an index
PUT /products/_settings
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.query.info": "1s",
  "index.search.slowlog.threshold.fetch.warn": "500ms",
  "index.indexing.slowlog.threshold.index.warn": "2s",
  "index.indexing.slowlog.level": "info"
}

# Tail the slow log directly on a data node to see live entries
tail -f /var/log/elasticsearch/production_index_search_slowlog.log

# Grep for the slowest recent queries above a threshold
grep "took\[" production_index_search_slowlog.log | sort -t'[' -k2 -rn | head -20

4. Hot threads API: understanding CPU load at thread level

While the slow log shows which queries are slow, the hot threads API shows which internal threads of a node are currently consuming the most CPU time, including a stack trace excerpt that reveals the operation currently being executed. This is especially valuable when CPU load on a node is high but it stays unclear whether the cause is search requests, indexing, merges, or internal management tasks such as cluster state updates.

The output of the hot threads API shows threads sorted by CPU share, with labels such as search, write, or [Lucene Merge Thread] that point directly to the cause. A high share of merge threads points to a write load that generates more segment merges than the node can comfortably process, while a high share of search threads points to expensive or too frequent search requests. The API can be run against individual nodes or all nodes and typically delivers a meaningful picture of the current CPU load within a few seconds.


# Hot threads across all nodes, sorted by CPU usage
curl -s "https://localhost:9200/_nodes/hot_threads?threads=5" -u elastic:changeme

# Hot threads for a single, specific node only
curl -s "https://localhost:9200/_nodes/node-data-03/hot_threads?threads=5" \
  -u elastic:changeme

5. Pending tasks: detecting a backlog on the master node

The master node processes cluster state updates such as creating indices, mapping changes, or shard assignments sequentially in an internal queue. Under normal conditions, this queue is empty or contains only a few entries processed within milliseconds. When pending tasks accumulate, it points to an overloaded master node, often caused by very many simultaneous index creations, an overly large cluster state object due to a very high number of indices and shards, or a master node that is additionally overloaded because it also serves as a data node.

A backed-up master node indirectly affects overall cluster performance, because every operation requiring a cluster state change, such as creating a new daily log index, gets stuck in the queue until the master node processes it. On very large clusters with thousands of indices, it is therefore a proven practice to run master nodes as dedicated, pure master nodes without a data role, so they are not simultaneously burdened by search requests or indexing.


# List pending cluster state tasks, if any are queued up
curl -s "https://localhost:9200/_cluster/pending_tasks?pretty" -u elastic:changeme

# Cluster state size can indirectly explain a slow master node
curl -s "https://localhost:9200/_cluster/state/_all?pretty" -u elastic:changeme | wc -c

6. Queue rejections: tracking down overloaded thread pools

Elasticsearch processes different operation types, such as search, write, and bulk indexing, in separate thread pools, each with a limited size and a limited queue. When more requests arrive than the thread pool and its queue can absorb, Elasticsearch rejects further requests with a TOO_MANY_REQUESTS error instead of buffering them indefinitely. These rejections, visible in the thread pool statistics as the rejected counter, are a direct signal of a structural capacity limit, not just a temporary slowdown.

A rising rejection counter in the bulk thread pool typically indicates that indexing clients are writing against the cluster with too large a bulk size or too much parallelism. The solution rarely lies in simply increasing the thread pool size, since that only shifts the underlying resource shortage elsewhere, but usually in reducing the bulk size, limiting the number of parallel indexing clients, or providing additional capacity through more nodes if the underlying load has permanently increased.


# Thread pool statistics with rejection counters per pool
curl -s "https://localhost:9200/_cat/thread_pool/write,search,bulk?v&h=node_name,name,active,queue,rejected" \
  -u elastic:changeme

# Watch rejections increase over time (run repeatedly during load)
watch -n 5 'curl -s "https://localhost:9200/_cat/thread_pool/write?v&h=node_name,rejected" -u elastic:changeme'

7. Shard size and count as a structural root cause

Beyond acute symptoms, an unfavorable shard size and shard count is one of the most common structural causes of chronic performance problems. Too many small shards create unnecessary management overhead, since every shard occupies its own Lucene file handles, segment metadata, and its own share of the cluster state. Overly large shards, on the other hand, slow down recovery after a node failure and make individual search requests slower, because a single shard can no longer be parallelized within one thread.

A common rule of thumb recommends shard sizes between 20 and 50 gigabytes for most use cases, though the actually optimal size depends on the specific query and indexing pattern. If a structural planning mistake in shard size is identified as the cause, the solution is usually a reindex into a newly created index with an adjusted number_of_shards setting, combined with index lifecycle management for time-based indices that automatically rotates new indices with an appropriate size.

Symptom Diagnostic tool Typical cause
Individual queries slow Slow log Inefficient query structure, missing filter cache
High CPU, cause unclear Hot threads API Merges, expensive aggregations, scripting
Index creation hangs Pending tasks Overloaded or dual-role master node
Requests being rejected Thread pool statistics Bulk requests too large, too much parallelism
Chronically slow despite resources Shard size analysis Too many small or too few large shards

8. Garbage collection and heap pressure as a special case

A frequently overlooked special case is performance degradation caused by frequent or long garbage collection pauses in the JVM. If a node's heap is chronically utilized above 75 to 85 percent, the garbage collector kicks in more and more often and with longer pauses, during which the affected node cannot process requests. These GC pauses show up in cluster performance as sporadic, hard-to-reproduce latency spikes that do not directly correlate with a specific query, which makes diagnosis harder without a targeted look at the GC logs.

A node's GC logs directly show the duration and frequency of garbage collection cycles, and long young-generation or even full GC pauses of several seconds are a clear signal of chronic heap pressure. The solution rarely lies in a blanket heap increase beyond the recommended 50 percent of available RAM or beyond 30 to 32 gigabytes, since that disables the JVM's compressed oops optimization and can paradoxically worsen performance, but usually in reducing the actual heap load through smaller aggregation windows, fewer simultaneous expensive queries, or additional nodes to distribute load.

9. The complete checklist at a glance

Combined, a fixed sequence emerges for every performance diagnosis: check cluster health and coarse resource usage, search the slow log for concrete slow operations, query the hot threads API under high CPU load, check pending tasks for a master node backlog, examine thread pool statistics for rejections, and, for chronic problems, consider shard size and GC behavior as structural root causes. This order covers both acute incidents and gradual, structural degradation.

The decisive advantage of this checklist over ad hoc diagnosis is reproducibility: every team member who follows the same sequence arrives at the same intermediate results and can document them in a way the team can follow, instead of improvising anew with every incident. A documented runbook containing this checklist as a concrete sequence of commands significantly reduces the time to root cause in a real incident, especially when it occurs under time pressure and possibly outside regular working hours.

Mironsoft

Elasticsearch and OpenSearch performance analysis and cluster tuning

Cluster running slow and the cause stays unclear?

We diagnose performance problems systematically using slow log, hot threads and thread pool analysis, find the structural root cause, and build a runbook for your team.

Performance audit

Systematic diagnosis using slow log, hot threads and thread pool analysis

Shard and heap tuning

Fixing structural causes instead of treating symptoms

Runbook creation

A documented checklist for fast reaction during a real incident

10. Summary

Systematic performance troubleshooting for Elasticsearch and OpenSearch follows a fixed chain: cluster health and resources first, then the slow log for concrete slow operations, the hot threads API for CPU load at thread level, pending tasks for a master node backlog, thread pool rejections for capacity limits, and for chronic problems shard size and garbage collection behavior as structural root causes. This order reliably covers both acute and gradual problems.

The biggest lever is making this checklist available as a documented runbook before the next incident, instead of assembling it for the first time during a real one. A team that knows these tools and practices them regularly finds the cause of a performance problem in minutes instead of hours, which makes the decisive difference in production environments with direct business impact.

Performance Troubleshooting Checklist, the essentials at a glance

Coarse first, then fine

Check cluster health and resource usage first, before investing time in detailed analysis.

Combine slow log and hot threads

Slow log shows which query, hot threads shows which internal process causes the CPU load.

Take rejections seriously

Rising rejection counters signal a structural capacity limit, not a temporary problem.

Check structural causes

Do not overlook shard size and GC behavior for chronic, recurring performance problems.

11. FAQ: Performance Troubleshooting Checklist

1Where to start with a slow cluster?
With cluster health and coarse resource usage. Quickly rules out or confirms obvious causes.
2What does the slow log show?
Queries and indexing operations above a configurable time threshold, separated by phase and type.
3When to use the hot threads API?
Under high CPU load with an unclear cause. Shows threads sorted by CPU share with a stack trace hint.
4What do accumulating pending tasks mean?
A backlog processing cluster state changes on the master node, often from many simultaneous index creations.
5What to do about rising rejections?
Reduce bulk size, limit parallelism, or add nodes if the load has permanently increased.
6What shard size is optimal?
Rule of thumb 20 to 50 gigabytes per shard, depending on the specific query and indexing pattern.
7How to recognize GC-related problems?
Sporadic latency spikes with no query correlation. GC logs directly show duration and frequency of cycles.
8Just increase heap size on GC pauses?
Not beyond 30 to 32 gigabytes, that disables compressed oops. Better to reduce the actual heap load.
9Does this apply to OpenSearch too?
Yes, both systems share the Lucene foundation and similar diagnostic APIs.
10How to make this reusable across a team?
As a documented runbook with concrete commands, instead of improvising anew every incident.