Elasticsearch Search Performance Tuning

Search speed shapes how a product feels. Visitors leave pages that take more than a second or two to respond. Most slow searches trace back to a small set of causes: scoring work that nobody needs, expensive query types, oversized responses, and poor shard layouts. Each fix below targets one cause and takes minutes to apply.

Where Search Time Goes

Search request
     │
     ▼
Coordinating node  ──→  sends query to every shard
     │
     ▼
Each shard:  match → score → sort → collect top hits
     │
     ▼
Coordinating node  ──→  merges lists → fetches documents → responds

Slow spots:  heavy queries, many shards, large documents, deep result lists

Use Filters for Exact Conditions

Conditions such as status, category, and date range do not need relevance scores. Place them in the filter clause of a bool query. Elasticsearch skips scoring and caches the matching document sets for reuse.

Slower:   "must":   [ { "match": { "title": "laptop" } },
                      { "term":  { "status": "published" } } ]

Faster:   "must":   [ { "match": { "title": "laptop" } } ],
          "filter": [ { "term":  { "status": "published" } } ]

Avoid Expensive Query Types

Query HabitProblemBetter Choice
Wildcard starting with *Scans every term in the fieldEdge n-grams or prefix queries
Regular expressions on big fieldsHigh CPU costKeyword fields with exact values
Scripts in queries or sortsRuns code per documentPrecomputed fields at index time
Deep from and size valuesLarge merge listssearch_after
Fuzzy search on very short wordsMany candidate termsRaise prefix_length

Return Less Data

Large documents travel across the network on every hit. Request only the fields the page displays with source filtering, and skip the exact total count when the page does not show it.

GET /products/_search
{
  "_source": ["name", "price"],
  "track_total_hits": false,
  "size": 20,
  "query": { "match": { "name": "laptop" } }
}

The track_total_hits setting stops Elasticsearch from counting every match beyond ten thousand. Pages with infinite scroll or "next" buttons rarely need the exact number.

Understand the Caches

CacheStoresHelps With
Node query cacheFilter results per segmentRepeated filters
Shard request cacheFull results of size 0 requests, such as aggregationsDashboards that repeat
Operating system page cacheIndex files in memoryAlmost everything

The operating system cache matters most. Leave at least half of the machine's memory to the system instead of giving everything to the Elasticsearch heap. A heap of 30 GB or less keeps pointers compact, and the remaining memory holds index files for fast reads.

Server with 64 GB RAM:

  Elasticsearch heap:     30 GB
  OS file system cache:   34 GB   ← keeps hot index data in memory

Right-Size the Shards

Every search touches every shard of the target index. A hundred tiny shards create a hundred small tasks and a long merge. Aim for shards between 10 GB and 50 GB, and combine small indexes through lifecycle policies and the shrink action.

Index with 100 shards of 500 MB  → 100 tasks per search
Index with 2 shards of 25 GB     → 2 tasks per search

Merge Segments on Read-Only Indexes

An index stores data in segments. Fewer segments mean faster reads. An index that no longer receives writes benefits from a force merge down to one segment.

POST /logs-2026.09/_forcemerge?max_num_segments=1

Never force merge an index that still accepts writes. The operation creates a huge segment that future merges struggle to manage.

Map Fields for Their Real Use

Numbers used only for exact matching and ranges can map as keyword instead of integer in some cases, since keyword term lookups run quickly. Fields that never need searching can disable indexing with "index": false. Fields that never need sorting or aggregating skip doc values. Smaller mappings mean smaller files and faster reads.

Measure with the Profile API

Guessing wastes time. The profile API reports how long each part of a query took on each shard.

GET /products/_search
{
  "profile": true,
  "query": { "match": { "name": "laptop" } }
}

The response breaks timing down by query component. A single slow clause usually stands out immediately.

Find Slow Queries with the Slow Log

The search slow log records queries that exceed time limits. Set thresholds per index, and review the log files regularly.

PUT /products/_settings
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.query.info": "1s",
  "index.search.slowlog.threshold.fetch.warn": "1s"
}

Tuning Checklist

CheckAction
Exact conditions inside mustMove to filter
Wildcards with leading *Replace with n-grams
Full documents returnedLimit with _source
Too many small shardsCombine or shrink
Old indexes still searchedForce merge or archive
Unknown slow queriesEnable the slow log and profile