Elasticsearch and opensearch tuning
Skill hamzabellouch/agent-skills/Search and Knowledge Graphs/elasticsearch-and-opensearch-tuning
Comprehensive collection of 380+ production-ready Agent Skills (26 domains) conforming to the Agent Skills Standard, featuring native auto-discovery for Antigravity, Gemini CLI, Claude Code, Cursor, and Codex.
npx -y skills add hamzabellouch/agent-skills --skill elasticsearch-and-opensearch-tuningAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 21 days oldThe repository was created 21 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Production guidance and performance tuning strategies for Elasticsearch and OpenSearch clusters, covering indexing, query optimization, mapping design, memory management, and scaling.
SKILL.md
9.9 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Elasticsearch & OpenSearch Tuning Guide
This skill provides comprehensive instructions, architectural pattern guidelines, and production-ready code examples for optimizing Elasticsearch and OpenSearch for high-throughput indexing, ultra-low latency search, and cost-effective cluster scaling.
1. Indexing & Mapping Strategies
1.1 Field Mapping Optimization
- Disable
_sourceselectively only when raw document retrieval is unnecessary and storage budget is tight; otherwise keep_sourceenabled for reindexing capabilities. - Set
index: falseon fields used only for storage/retrieval (e.g., metadata blobs, raw HTML strings) to prevent inverted index overhead. - Explicit Mappings over Dynamic Mappings: Never rely on dynamic mapping in production. Dynamic mapping introduces field explosion risks (
keyword+textdual fields by default). - Use
doc_values: falsefor high-cardinality text or keyword fields that will never be used for aggregations, sorting, or scripting. - Optimize Text Analysis:
- Disable norm storage (
norms: false) on text fields where relevance scoring is not required (e.g., exact matches, filter-only text). - Use
index_options: docsfor fields where positional or phrase queries are not needed (saves term frequency and position offset storage).
- Disable norm storage (
Optimized Mapping Definition (DSL)
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"translog": {
"durability": "async",
"sync_interval": "5s",
"flush_threshold_size": "1gb"
},
"codec": "best_compression"
}
},
"mappings": {
"dynamic": "strict",
"properties": {
"id": {
"type": "keyword",
"doc_values": true
},
"title": {
"type": "text",
"analyzer": "standard",
"fields": {
"raw": {
"type": "keyword",
"ignore_above": 256
}
}
},
"body": {
"type": "text",
"analyzer": "standard",
"index_options": "positions",
"norms": true
},
"category_id": {
"type": "keyword",
"doc_values": true,
"norms": false
},
"status": {
"type": "keyword",
"doc_values": true
},
"created_at": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"payload_metadata": {
"type": "keyword",
"index": false,
"doc_values": false
}
}
}
}
1.2 Bulk Ingestion & Shard Sizing
- Bulk Batch Size: Ideal bulk request payload is typically between 5MB to 15MB per request (or 2,000–5,000 documents depending on doc size). Tune based on CPU and throughput benchmarks rather than fixed doc counts.
- Shard Sizing Standard:
- Search-heavy indices: Shard size should be 10GB to 30GB.
- Logging / Time-series indices: Shard size should be 30GB to 50GB.
- Target total shards per GB of JVM heap: Keep under 20 shards per GB of heap.
- Indexing Throughput Tuning:
- Increase
refresh_intervalfrom1sto30sor60sduring heavy bulk ingestion. - Set
number_of_replicas: 0during initial bulk loading, then restore to1or2after load completes. - Configure
translog.durability: asyncfor non-critical logging workloads to eliminate synchronous disk commits on every indexing request.
- Increase
2. Query Tuning & Performance Optimization
2.1 Query Execution: Filter vs. Query Context
- Filter Context (
filter,must_not): Does not compute relevance scores (_score). Results are cached in the Node Query Cache (LRU bitsets). Always place non-scoring exact-match conditions insidefilter. - Query Context (
must,should): Computes relevance scores using BM25. Use exclusively for text relevance matching.
Optimized Multi-Clause Search Request
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "active" } },
{ "range": { "created_at": { "gte": "now-7d/d" } } }
],
"must": [
{
"match": {
"title": {
"query": "distributed graph database",
"operator": "and",
"fuzziness": "AUTO:4,8"
}
}
}
],
"should": [
{
"match_phrase": {
"body": {
"query": "distributed graph database",
"slop": 2,
"boost": 2.5
}
}
}
]
}
},
"timeout": "500ms",
"terminate_after": 10000,
"_source": ["id", "title", "created_at"]
}
2.2 Deep Pagination Mitigation
- Avoid
from+sizefor deep page iteration (from + size > 10,000causes Memory Overflow and high JVM CPU utilization). - Use
search_afterwith Point In Time (PIT) for deterministic stateless pagination over large result sets:
Python Production Example: search_after with PIT
import logging
from typing import List, Dict, Any, Generator
from opensearchpy import OpenSearch, NotFoundError
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DeepPaginationClient:
def __init__(self, client: OpenSearch, index_name: str):
self.client = client
self.index_name = index_name
def iterate_all_documents(
self, query: Dict[str, Any], page_size: int = 1000
) -> Generator[Dict[str, Any], None, None]:
"""
Executes deep pagination efficiently using Point In Time (PIT) / search_after.
"""
# Create Point In Time (PIT) context (OpenSearch / ES 7.10+)
try:
pit_resp = self.client.create_point_in_time(
index=self.index_name, keep_alive="2m"
)
pit_id = pit_resp["pit_id"]
except Exception as e:
logger.error("Failed to create Point In Time context: %s", str(e))
raise
search_after_mark = None
total_fetched = 0
try:
while True:
body: Dict[str, Any] = {
"size": page_size,
"query": query,
"sort": [
{"created_at": {"order": "desc"}},
{"_shard_doc": {"order": "asc"}} # Tie-breaker
],
"pit": {
"id": pit_id,
"keep_alive": "2m"
}
}
if search_after_mark:
body["search_after"] = search_after_mark
response = self.client.search(body=body)
hits = response["hits"]["hits"]
if not hits:
break
for hit in hits:
yield hit["_source"]
total_fetched += 1
search_after_mark = hits[-1]["sort"]
logger.debug("Fetched batch of %d docs. Total: %d", len(hits), total_fetched)
finally:
# Always release PIT resources
try:
self.client.delete_point_in_time(body={"pit_id": pit_id})
except Exception as e:
logger.warning("Failed to delete PIT context: %s", str(e))
3. Memory & Infrastructure Management
3.1 JVM Heap Allocation
- Set
-Xmsand-Xmxto the exact same value to prevent dynamic heap resizing runtime pauses. - Never exceed 32GB heap: Keep JVM heap under the Compressed OOPs threshold (typically 30GB–31GB depending on JDK version) to ensure 32-bit pointer efficiency.
- Allocate 50% of total system RAM to JVM Heap and leave the remaining 50% for OS Filesystem Page Cache (crucial for Doc Values, Lucene segment caching, and bloom filters).
3.2 Circuit Breakers & Thread Pools
- Parent Circuit Breaker: Set
indices.breaker.total.use_real_memory: truewith limit70%to prevent OutOfMemoryError (OOM). - Fielddata Breaker: Set
indices.breaker.fielddata.limit: 40%. Avoid usingfielddata: trueontextfields; usekeywordfields withdoc_values. - Search Thread Pool Queue:
thread_pool.search.queue_size: 1000. Do not set arbitrarily high; larger queues cause task latency accumulation and high garbage collection pressure.
4. Production Anti-Patterns & Solutions
| Anti-Pattern | Operational Impact | Recommended Solution |
|---|---|---|
| Dynamic Mapping Explosion | Unbounded field creation leads to cluster state degradation and OOM. | Set "dynamic": "strict" in index mappings. |
Using wildcard with leading asterisk (*foo) | Forces full inverted index scanning; severe CPU consumption. | Use wildcard field type or n-gram tokenizer (edge_ngram). |
| Over-Sharding | Thousands of small shards (<1GB) exhaust JVM heap on metadata. | Reindex/Shrink indices to maintain 10GB–50GB per shard. |
Deep from + size Pagination | Linear RAM and CPU scaling ($O(N)$ memory cost per request). | Use search_after with PIT or scroll API (for batch export). |
Heavy script_score Without Caching | High CPU overhead per doc evaluation during query phase. | Pre-compute values at ingestion or use painless optimized scripts. |
5. Diagnostic & Monitoring Commands
Checking Cluster Health & Shard Distribution
# Cluster health summary
GET /_cluster/health?v
# Shard count and size per index
GET /_cat/shards?v&s=index,shard&h=index,shard,prirep,state,docs,store,node
# Circuit breaker states and memory usage
GET /_nodes/stats/breaker
# Fielddata and Query Cache memory usage
GET /_nodes/stats/indices/fielddata,query_cache,request_cache?human=true
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.