Core Concepts, Indexing, Search Queries, Aggregations, Mapping, Cluster Management, Performance Tuning — search engine mastery.
| Method | Endpoint | Description |
|---|---|---|
| GET | /_cat/indices?v | List all indices |
| PUT | /my-index | Create an index |
| POST | /my-index/_doc | Index a document (auto ID) |
| PUT | /my-index/_doc/1 | Index a document (explicit ID) |
| GET | /my-index/_doc/1 | Get a document by ID |
| POST | /my-index/_search | Search an index |
| PUT | /my-index/_mapping | Update index mapping |
| DELETE | /my-index | Delete an index |
| GET | /_cluster/health | Cluster health status |
| POST | /_aliases | Manage index aliases |
# ── Index Operations ──
# Create an index with settings and mapping
curl -X PUT "localhost:9200/products" -H 'Content-Type: application/json' -d '{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "1s"
},
"mappings": {
"properties": {
"name": { "type": "text", "analyzer": "english" },
"description": { "type": "text" },
"price": { "type": "float" },
"category": { "type": "keyword" },
"tags": { "type": "keyword" },
"created_at": { "type": "date", "format": "strict_date_optional_time||epoch_millis" },
"in_stock": { "type": "boolean" },
"rating": { "type": "float" }
}
}
}'
# Index a document
curl -X POST "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d '{
"name": "Wireless Headphones",
"description": "Noise-cancelling wireless headphones with 30hr battery",
"price": 199.99,
"category": "electronics",
"tags": ["audio", "wireless", "bluetooth"],
"created_at": "2025-01-15T10:30:00Z",
"in_stock": true,
"rating": 4.5
}'
# Bulk index
curl -X POST "localhost:9200/_bulk" -H 'Content-Type: application/json' -d '
{"index": {"_index": "products", "_id": "2"}}
{"name": "Mechanical Keyboard","price": 149.99,"category":"electronics","tags":["keyboard","mechanical"],"in_stock":true}
{"index": {"_index": "products", "_id": "3"}}
{"name": "USB-C Hub","price": 49.99,"category":"accessories","tags":["usb-c","hub"],"in_stock":false}
'
# Get document
curl -X GET "localhost:9200/products/_doc/1"
# Delete document
curl -X DELETE "localhost:9200/products/_doc/3"
# Update document (partial)
curl -X POST "localhost:9200/products/_update/1" -H 'Content-Type: application/json' -d '{
"doc": { "price": 179.99, "tags": ["audio", "wireless", "sale"] }
}'# ── Index Management ──
# List all indices
curl -X GET "localhost:9200/_cat/indices?v"
# Get index settings and mapping
curl -X GET "localhost:9200/products/_mapping?pretty"
curl -X GET "localhost:9200/products/_settings?pretty"
# Create alias
curl -X POST "localhost:9200/_aliases" -H 'Content-Type: application/json' -d '{
"actions": [
{ "add": { "index": "products-v2", "alias": "products" } },
{ "remove": { "index": "products-v1", "alias": "products" } }
]
}'
# Reindex data (zero-downtime migration)
curl -X POST "localhost:9200/_reindex" -H 'Content-Type: application/json' -d '{
"source": { "index": "products-v1" },
"dest": { "index": "products-v2" }
}'
# Clone index
curl -X POST "localhost:9200/products/_clone/products-backup"
# Force merge (optimize for read-heavy workloads)
curl -X POST "localhost:9200/products/_forcemerge?max_num_segments=1"
# Refresh index (make recent operations searchable)
curl -X POST "localhost:9200/products/_refresh"
# Flush index (write to disk)
curl -X POST "localhost:9200/products/_flush"_ or -, cannot contain \\, /, *, ?, ", <, >, |, (space), or ,. Must be under 255 bytes.{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "1s",
"analysis": {
"analyzer": {
"custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding", "my_stemmer", "my_stopwords"]
}
},
"filter": {
"my_stemmer": {
"type": "stemmer",
"name": "english"
},
"my_stopwords": {
"type": "stop",
"stopwords": "_english_"
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "custom_analyzer",
"fields": {
"keyword": { "type": "keyword" },
"autocomplete": {
"type": "search_as_you_type"
},
"english": {
"type": "text",
"analyzer": "english"
}
}
},
"content": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
},
"status": {
"type": "keyword"
},
"author": {
"type": "nested",
"properties": {
"name": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },
"email": { "type": "keyword" }
}
},
"location": { "type": "geo_point" },
"created_at": { "type": "date" },
"view_count": { "type": "integer" },
"metadata": {
"type": "object",
"enabled": false
},
"tags": {
"type": "keyword"
},
"suggest": {
"type": "completion"
}
}
}
}| Type | Use Case |
|---|---|
| text | Full-text search (analyzed, tokenized) |
| keyword | Exact matching, sorting, aggregations (not analyzed) |
| integer / long | Whole numbers |
| float / double | Decimal numbers |
| boolean | True/false values |
| date | Dates and timestamps |
| object | JSON objects (flattened internally) |
| nested | JSON objects preserving relationships |
| geo_point | Latitude/longitude for geo queries |
| ip | IPv4/IPv6 addresses |
| completion | Autocomplete/suggestions |
| binary | Base64-encoded binary data |
| join | Parent-child relationships |
| search_as_you_type | Edge n-gram autocomplete |
| Feature | text | keyword |
|---|---|---|
| Analysis | Analyzed (tokenized) | Not analyzed (exact) |
| Full-text search | Yes | No |
| Aggregations | No (use .keyword) | Yes |
| Sorting | No (use .keyword) | Yes |
| Wildcard/query | Supported | Not for search |
| Default analyzer | standard | None |
| Typical fields | title, description | status, category, ID |
# ── Bulk API ──
curl -X POST "localhost:9200/_bulk?pretty" -H 'Content-Type: application/x-ndjson' -d '
{"index": {"_index": "articles", "_id": "1"}}
{"title": "Getting Started with Elasticsearch", "content": "Learn the fundamentals of search", "status": "published", "tags": ["elasticsearch", "search", "tutorial"]}
{"index": {"_index": "articles", "_id": "2"}}
{"title": "Advanced Query DSL", "content": "Master bool queries, aggregations, and filters", "status": "published", "tags": ["elasticsearch", "advanced", "query"]}
{"delete": {"_index": "articles", "_id": "3"}}
{"update": {"_index": "articles", "_id": "1"}}
{"doc": {"status": "draft"}}
'
# ── Update by Query ──
curl -X POST "localhost:9200/articles/_update_by_query?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"term": { "tags": "deprecated" }
},
"script": {
"source": "ctx._source.status = \"archived\"",
"lang": "painless"
}
}'
# ── Delete by Query ──
curl -X POST "localhost:9200/articles/_delete_by_query?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"range": { "created_at": { "lt": "2020-01-01" } }
}
}'keyword for fields you need to sort, aggregate, or filter exactly. A text field is analyzed and tokenized — sorting on it would use a disabled fielddata. Always add a .keyword sub-field for text fields that also need exact matching.// ── Match Query (full-text search) ──
{
"query": {
"match": {
"title": {
"query": "wireless headphones",
"operator": "and",
"fuzziness": "AUTO",
"minimum_should_match": "75%"
}
}
}
}
// ── Term Query (exact match) ──
{
"query": {
"term": {
"status.keyword": "published"
}
}
}
// ── Terms Query (multiple exact values) ──
{
"query": {
"terms": {
"tags": ["elasticsearch", "search", "tutorial"]
}
}
}
// ── Range Query ──
{
"query": {
"range": {
"price": {
"gte": 50,
"lte": 200,
"boost": 2.0
}
}
}
}
// ── Bool Query (compound query) ──
{
"query": {
"bool": {
"must": [
{ "match": { "title": "wireless headphones" } }
],
"should": [
{ "term": { "category": "electronics" } },
{ "range": { "rating": { "gte": 4.0 } } }
],
"filter": [
{ "term": { "in_stock": true } },
{ "range": { "price": { "lte": 200 } } }
],
"must_not": [
{ "term": { "status": "discontinued" } }
],
"minimum_should_match": 1
}
}
}// ── Multi-Match Query (search across multiple fields) ──
{
"query": {
"multi_match": {
"query": "elasticsearch tutorial",
"fields": ["title^3", "description", "content"],
"type": "best_fields",
"fuzziness": "AUTO"
}
}
}
// ── Match Phrase Query (exact phrase matching) ──
{
"query": {
"match_phrase": {
"description": {
"query": "noise cancelling headphones",
"slop": 2
}
}
}
// ── Wildcard Query ──
{
"query": {
"wildcard": {
"title.keyword": "wireless*"
}
}
}
// ── Exists Query ──
{
"query": {
"exists": {
"field": "description"
}
}
}
// ── Nested Query ──
{
"query": {
"nested": {
"path": "author",
"query": {
"bool": {
"must": [
{ "match": { "author.name": "John" } },
{ "term": { "author.email": "john@example.com" } }
]
}
}
}
}
}| Clause | Score Affects | Behavior | Use For |
|---|---|---|---|
| must | Yes | Must match (AND) | Required matches |
| should | Yes (if must is empty, at least 1) | Boosts relevance | Optional scoring |
| filter | No (cached) | Must match, no scoring | Exact filters |
| must_not | No | Must NOT match | Exclusions |
| Query | Description | Best For |
|---|---|---|
| match | Standard full-text | General search |
| match_phrase | Exact phrase | "exact phrase" search |
| match_phrase_prefix | Phrase prefix | Autocomplete |
| multi_match | Search multiple fields | Cross-field search |
| query_string | Lucene syntax | Complex queries with operators |
| simple_query_string | Safe query string | User input parsing |
| match_bool_prefix | Bool prefix | Search-as-you-type |
// ── Search with Pagination, Sorting, and Source Filtering ──
{
"query": {
"bool": {
"must": [{ "match": { "title": "headphones" } }],
"filter": [{ "range": { "price": { "lte": 200 } } }]
}
},
"sort": [
{ "price": { "order": "asc" } },
{ "_score": { "order": "desc" } },
"created_at"
],
"from": 0,
"size": 20,
"_source": ["title", "price", "category", "rating"],
"highlight": {
"fields": {
"title": { "pre_tags": ["<em>"], "post_tags": ["</em>"] },
"description": {}
},
"fragment_size": 150,
"number_of_fragments": 3
},
"aggs": {
"categories": {
"terms": { "field": "category", "size": 10 }
},
"price_stats": {
"stats": { "field": "price" }
}
}
}
// ── Search After (deep pagination) ──
{
"query": { "match_all": {} },
"sort": [{ "created_at": "desc" }, { "_id": "asc" }],
"size": 20,
"search_after": ["2025-01-15T10:30:00Z", "abc123"]
}filter instead of must for exact matches (term, range, exists). Filter clauses do not calculate relevance scores and can be cached by Elasticsearch, resulting in faster queries and less memory usage.// ── Terms Aggregation (bucket) ──
{
"size": 0,
"aggs": {
"by_category": {
"terms": {
"field": "category",
"size": 10,
"order": { "_count": "desc" },
"missing": "uncategorized"
},
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"max_price": { "max": { "field": "price" } }
}
}
}
}
// ── Date Histogram Aggregation ──
{
"size": 0,
"aggs": {
"articles_per_month": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month",
"format": "yyyy-MM",
"min_doc_count": 1
},
"aggs": {
"total_views": { "sum": { "field": "view_count" } },
"unique_authors": {
"cardinality": { "field": "author.name.keyword" }
}
}
}
}
}// ── Metric Aggregations ──
{
"size": 0,
"aggs": {
"avg_rating": { "avg": { "field": "rating" } },
"max_price": { "max": { "field": "price" } },
"min_price": { "min": { "field": "price" } },
"total_revenue": { "sum": { "field": "price" } },
"price_stats": {
"stats": { "field": "price" }
},
"percentiles_price": {
"percentiles": {
"field": "price",
"percents": [1, 5, 25, 50, 75, 95, 99]
}
}
}
}
// ── Range Aggregation ──
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "key": "budget", "to": 50 },
{ "key": "mid-range", "from": 50, "to": 200 },
{ "key": "premium", "from": 200 }
]
}
}
}
}
// ── Composite Aggregation (pagination-friendly) ──
{
"size": 0,
"aggs": {
"my_buckets": {
"composite": {
"sources": [
{ "category": { "terms": { "field": "category" } } },
{ "brand": { "terms": { "field": "brand.keyword" } } }
],
"size": 10
}
}
}
}| Type | Aggregations | Description |
|---|---|---|
| Bucket | terms, date_histogram, range, histogram, filter, composite | Group documents into buckets |
| Metric | avg, sum, min, max, stats, cardinality, value_count | Calculate metrics over fields |
| Pipeline | derivative, moving_avg, bucket_sort, max_bucket | Transform outputs of other aggs |
| Matrix | matrix_stats | Stats across multiple numeric fields |
| Feature | cardinality | value_count |
|---|---|---|
| Purpose | Unique count (approximate) | Total count including duplicates |
| Algorithm | HyperLogLog++ | Exact count |
| Memory | Fixed (configurable precision) | O(distinct values) |
| Accuracy | ~98% (default) | 100% exact |
| Use Case | "How many unique users?" | "How many total page views?" |
precision_threshold to trade memory for accuracy. For exact unique counts on small datasets, use a scripted metric or terms with a large size, then count buckets.// ── Dynamic Mapping ──
// Elasticsearch auto-detects field types on first document:
// "string" → text + keyword sub-field
// "number" → long or float
// "boolean" → boolean
// "date" → date (if matches configured date formats)
// "object" → object (nested properties)
// ── Explicit Mapping with Templates ──
{
"mappings": {
"dynamic": "strict",
"dynamic_templates": [
{
"strings_as_keywords": {
"match_mapping_type": "string",
"match": "*_id",
"mapping": { "type": "keyword" }
}
},
{
"string_fields": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
}
}
}
],
"properties": {
"id": { "type": "keyword" },
"title": {
"type": "text",
"analyzer": "standard",
"search_analyzer": "standard",
"eager_global_ordinals": true
},
"status": {
"type": "keyword",
"doc_values": true,
"ignore_above": 256
},
"created_at": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
}| Mode | Behavior |
|---|---|
| true (default) | New fields are auto-detected and added to the mapping |
| false | New fields are ignored (stored in _source but not indexed) |
| strict | New fields cause an error (rejects unknown fields) |
| runtime | New fields are added as runtime fields (queryable, not indexed) |
| Parameter | Description |
|---|---|
| index | Whether the field is searchable (true/false) |
| doc_values | Columnar storage for aggregations/sorting (default true) |
| store | Store field separately in index (default false) |
| coerce | Allow type coercion (e.g., string "5" to int 5) |
| ignore_above | Ignore strings longer than N characters (keyword only) |
| ignore_malformed | Skip malformed documents instead of error |
| null_value | Replace explicit null with a value |
| copy_to | Copy field content to another field |
| norms | Disable norms to save memory for scoring (false = no length norm) |
| index_options | What info to index: docs, freqs, positions, offsets |
// ── Analyzers ──
// Built-in analyzers:
// standard, simple, whitespace, stop, keyword, pattern,
// english, fingerprint, snowball, custom
{
"settings": {
"analysis": {
"char_filter": {
"html_strip_filter": {
"type": "html_strip"
},
"my_mapping": {
"type": "mapping",
"mappings": ["& => and", "| => or"]
}
},
"tokenizer": {
"my_ngram_tokenizer": {
"type": "ngram",
"min_gram": 3,
"max_gram": 4,
"token_chars": ["letter", "digit"]
},
"edge_ngram_tokenizer": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20,
"token_chars": ["letter", "digit"]
}
},
"filter": {
"my_synonym": {
"type": "synonym",
"synonyms": ["headphones, earphones, cans"]
},
"my_ngram": {
"type": "ngram",
"min_gram": 3,
"max_gram": 4
}
},
"analyzer": {
"autocomplete_index": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "my_ngram"]
},
"autocomplete_search": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase"]
},
"html_analyzer": {
"type": "custom",
"char_filter": ["html_strip_filter"],
"tokenizer": "standard",
"filter": ["lowercase", "stop", "snowball"]
}
}
}
}
}dynamic: "strict" to catch typos early.# ── Cluster Health ──
curl -X GET "localhost:9200/_cluster/health?pretty"
curl -X GET "localhost:9200/_cluster/health?level=shards&pretty"
# Status: green (all shards allocated), yellow (replicas unassigned), red (primary shards unassigned)
curl -X GET "localhost:9200/_cluster/state?pretty" # Full cluster state
curl -X GET "localhost:9200/_cluster/stats?pretty" # Cluster statistics
curl -X GET "localhost:9200/_cluster/pending_tasks" # Pending master tasks
# ── Node Management ──
curl -X GET "localhost:9200/_cat/nodes?v"
curl -X GET "localhost:9200/_nodes/stats?pretty"
curl -X GET "localhost:9200/_nodes/{nodeId}/stats/jvm,os,process"
# ── Shard Management ──
curl -X GET "localhost:9200/_cat/shards?v"
curl -X GET "localhost:9200/_cat/shards/products?v&h=index,shard,prirep,state,docs,store"
curl -X POST "localhost:9200/products/_shrink/products-small" # Shrink shards
curl -X POST "localhost:9200/products/_split/products-large" # Split shards
curl -X POST "localhost:9200/_cluster/reroute?pretty" -H 'Content-Type: application/json' -d '{
"commands": [
{
"move": {
"index": "products",
"shard": 0,
"from_node": "node-1",
"to_node": "node-2"
}
}
]
}'
# ── Allocation Settings ──
curl -X PUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d '{
"transient": {
"cluster.routing.allocation.enable": "all",
"cluster.routing.allocation.exclude._name": "node-3",
"cluster.routing.allocation.disk.threshold_enabled": true,
"cluster.routing.allocation.disk.watermark.low": "85%",
"cluster.routing.allocation.disk.watermark.high": "90%",
"cluster.routing.allocation.disk.watermark.flood_stage": "95%"
}
}'
# ── Index Lifecycle Management (ILM) ──
curl -X PUT "localhost:9200/_ilm/policy/logs-policy" -H 'Content-Type: application/json' -d '{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "7d"
}
}
},
"warm": {
"min_age": "30d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "90d",
"actions": { "delete": {} }
}
}
}
}'| Role | Description |
|---|---|
| master-eligible | Cluster management, index creation, shard allocation (default true) |
| data | Stores data shards and handles CRUD/search (default true) |
| ingest | Pre-processes documents before indexing (pipeline nodes) |
| coordinating-only | Distributes queries, merges results (no data, no master) |
| ml | Runs machine learning jobs |
| transform | Transforms data continuously |
| remote | Cross-cluster replication client |
| Scope | Persists Across | Use For |
|---|---|---|
| persistent | Full cluster restarts | Permanent configuration changes |
| transient | Full cluster restarts | Temporary overrides (clear with null) |
| default (none) | Not at all | Cannot be set via API (elasticsearch.yml only) |
// ── Use filter context (no scoring, cached) ──
{
"query": {
"bool": {
"must": [{ "match": { "title": "search query" } }],
"filter": [
{ "term": { "status": "published" } },
{ "range": { "price": { "gte": 10, "lte": 100 } } }
]
}
}
}
// ── Track total hits (avoids expensive count) ──
{
"track_total_hits": false,
"query": { "match_all": {} },
"size": 20
}
// ── KNN Vector Search (approximate) ──
{
"query": {
"knn": {
"field": "embedding",
"query_vector": [0.1, 0.2, 0.3, 0.4],
"k": 10,
"num_candidates": 100
}
}
}
// ── Index Sorting (sort at index time) ──
// Set when creating index:
{
"settings": {
"index.sort.field": "created_at",
"index.sort.order": "desc"
}
}# ── Profile a query ──
curl -X GET "localhost:9200/products/_search?pretty" -H 'Content-Type: application/json' -d '{
"profile": true,
"query": {
"match": { "title": "headphones" }
}
}'
# ── Slow log queries ──
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.indexing.slowlog.threshold.index.warn": "5s",
"index.indexing.slowlog.threshold.index.info": "2s"
}'
# ── Check slow logs
curl -X GET "localhost:9200/products/_slowlog"
# ── Optimize bulk indexing ──
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index": {
"refresh_interval": "-1",
"number_of_replicas": 0
}
}'
# ... do bulk indexing ...
curl -X POST "localhost:9200/products/_forcemerge?max_num_segments=1"
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
"index": {
"refresh_interval": "1s",
"number_of_replicas": 1
}
}'refresh_interval to -1, disable replicas, and use the bulk API with batches of 1000-5000 documents. After loading, run _forcemerge?max_num_segments=1 and restore replicas.An inverted index maps each unique term to the list of documents containing it. For example, if Doc1 contains "hello world" and Doc2 contains "hello there", the inverted index would map "hello" to [Doc1, Doc2], "world" to [Doc1], and "there" to [Doc2]. This is what makes full-text search fast — instead of scanning every document, ES looks up the term directly. The index also stores term frequency, positions (for phrase queries), and offsets (for highlighting).
text fields are analyzed (tokenized, lowercased, stemmed) — use for full-text search like titles or descriptions. keyword fields are stored as-is — use for exact matching, sorting, aggregations, and filtering (e.g., status codes, categories, IDs). A common pattern is to use both: {"field": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}
Through sharding (splitting data across nodes) and replication (copying shards). Each index has primary shards (split data horizontally) and replica shards (copies). If a node fails, its replicas on other nodes can serve requests. The cluster continues operating in yellow status (replicas unassigned) or green (all replicas active). Elasticsearch also supports cross-cluster replication (CCR) for disaster recovery.
A Bool query combines multiple queries. must — documents must match, contributes to score. should — if no must clause exists, at least one should must match; if must exists, should only boosts relevance. filter — must match like must, but does NOT calculate relevance score and IS cached. must_not — documents must NOT match. Always use filter for exact matches to improve performance.
1) Create a new index with the updated mapping. 2) Use the _reindex API with wait_for_completion=false for async execution. 3) Use aliases: create the new index with an alias, then atomically switch the alias from old to new with zero downtime. 4) Monitor progress with the _tasks API. 5) For very large datasets, use a custom script or Logstash with scroll API. 6) After reindexing, verify document counts and run sanity queries before deleting the old index.
Using from/size for deep pagination (e.g., page 10,000) is expensive because ES must sort and collect all documents from from to from + size across all shards, then merge results. This is O(n*m) where n is the page offset and m is the number of shards. Solutions: use search_after for real-time scrolling (sort + after key), scroll API for bulk export (keeps context alive), or point in time (PIT) with search_after for consistent snapshots.
Elasticsearch uses optimistic concurrency control. Each document has a _seq_no and _primary_term. When updating, you can supply if_seq_no and if_primary_term — ES rejects the update if the document was modified in between. For read-heavy workloads with rare updates, this is efficient. For write-heavy workloads, consider using external versioning or routing to avoid hotspots.
ILM automates index management through phases: Hot — actively indexed, full replicas. Warm — read-only, fewer replicas, force-merged, possibly on cheaper nodes. Cold — minimal resources, searchable but infrequently accessed. Delete — auto-deleted after retention period. ILM policies can be bound to index templates, so new indices automatically get lifecycle management.