Everything for Elasticsearch in one place — pick a section below. 15 reviewed items across 4 content types.
Quick Answer
Elasticsearch needs a majority of master-eligible nodes for safe cluster coordination, replicas for shard availability, zone awareness to avoid correlated failure, and client routing that does not depend on one node. Resilience is a capacity and topology problem, not just a replica count.
Detailed Answer
A library with duplicate books still fails readers if the only librarian is unavailable. Elasticsearch needs both data redundancy and coordination redundancy.
Elastic production guidance frames resilience at node, zone, and index levels. Each level removes a different single point of failure: process, infrastructure failure domain, and data copy.
The elected master manages cluster state and shard allocation. Data nodes hold primary and replica shards. When a node fails, the cluster detects the fault, promotes or reallocates shards, and rebuilds missing copies when capacity is available.
Operators watch cluster health, unassigned shards, master elections, JVM pressure, disk watermarks, thread pool rejections, and search/index latency. Load balancers or clients should target multiple nodes so one coordinating node does not become a hard dependency.
A green cluster can still be under-resilient after a failure if remaining nodes are overloaded while shards rebuild. The design must reserve enough capacity to serve traffic and recover at the same time.
Code Example
PUT /logs-payments-*/_settings
{
"index.number_of_replicas": 1, # Keeps one extra copy of each shard for node failure tolerance.
"index.routing.allocation.include._tier_preference": "data_hot" # Keeps hot logs on the intended data tier.
}
GET /_cluster/health?pretty # Confirms health, active shards, and unassigned shard count.Interview Tip
A junior engineer typically answers with the feature name and a happy-path command, but for a senior/architect role, the interviewer is actually looking for production judgment. Explain where the Elastic control plane stores intent, how changes are rolled out, what telemetry proves the system is healthy, and what failure mode creates the largest blast radius. Strong answers include rollback behavior, ownership boundaries, permissions, and the exact signal you would page on.
◈ Architecture Diagram
┌──────────┐
│ Signal │
└────┬─────┘
↓
┌──────────┐
│ Elastic │
└────┬─────┘
↓
┌──────────┐
│ Action │
└──────────┘💬 Comments
Quick Answer
Applications send logs to Logstash (or Beats), which parses and enriches them before indexing into Elasticsearch. Kibana queries Elasticsearch to render dashboards and visualizations.
Detailed Answer
1. Collection: Filebeat/Fluentd tails log files or receives syslog, adding metadata (hostname, service, timestamp) 2. Processing: Logstash receives events, applies grok filters to parse unstructured logs into fields, enriches with GeoIP/DNS lookup, and transforms 3. Indexing: Logstash outputs to Elasticsearch, which indexes documents into time-based indices (logs-2026.06.23) 4. Storage: Elasticsearch distributes data across shards on data nodes. Primary shards handle writes, replicas provide redundancy 5. Querying: Kibana sends queries to Elasticsearch, which searches across shards in parallel and aggregates results
Use Index Lifecycle Management (ILM) to automatically move indices through hot→warm→cold→delete phases. Hot nodes use SSDs for fast writes, warm nodes use HDDs for cost-effective storage. Set shard size to 30-50GB for optimal performance. Use data streams instead of raw indices for time-series data.
Common Pitfall: Over-sharding. Each shard consumes ~50MB heap. 1000 indices × 5 primary shards × 1 replica = 10,000 shards = 500MB heap overhead before any data.
Code Example
# Logstash pipeline configuration
input {
beats { port => 5044 }
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:msg}" }
}
date {
match => [ "timestamp", "ISO8601" ]
target => "@timestamp"
}
}
output {
elasticsearch {
hosts => ["http://es-cluster:9200"]
index => "logs-%{+YYYY.MM.dd}"
}
}
# ILM policy
PUT _ilm/policy/logs-policy
{
"policy": {
"phases": {
"hot": { "actions": { "rollover": { "max_size": "50gb", "max_age": "1d" }}},
"warm": { "min_age": "7d", "actions": { "shrink": { "number_of_shards": 1 }}},
"delete": { "min_age": "30d", "actions": { "delete": {} }}
}
}
}Interview Tip
Walk through the data flow end-to-end. Mention ILM and shard sizing — these show you've operated ELK at scale, not just set it up once.
💬 Comments
Quick Answer
Use an odd number of master-eligible nodes (minimum 3) with discovery.zen.minimum_master_nodes set to (N/2)+1. In ES 7+, the cluster coordination layer handles this automatically.
Detailed Answer
If a network partition divides the cluster, both sides could elect their own master, leading to two independent clusters accepting writes. When the partition heals, data is inconsistent and potentially lost.
Elasticsearch 7+ uses a new cluster coordination subsystem based on the Raft consensus algorithm. It automatically calculates the quorum requirement — you just need to configure cluster.initial_master_nodes during bootstrap. The cluster won't elect a master unless a majority of master-eligible nodes agree.
- 3 master-eligible nodes (quorum = 2) - Dedicated master nodes (don't use them as data nodes in production) - Spread across 3 availability zones for network partition resilience
Required manual setting: discovery.zen.minimum_master_nodes: 2 for a 3-node cluster. If set incorrectly (e.g., 1), split-brain is possible.
Production Best Practice: Use 3 dedicated master nodes, separate from data nodes. This keeps cluster management lightweight and prevents resource contention from heavy indexing/searches affecting master stability.
Code Example
# elasticsearch.yml for dedicated master node node.name: master-1 node.roles: [master] cluster.name: production cluster.initial_master_nodes: ["master-1", "master-2", "master-3"] discovery.seed_hosts: ["master-1:9300", "master-2:9300", "master-3:9300"] # Check cluster health GET _cluster/health # Look for: status (green/yellow/red), number_of_nodes, active_shards # Check master election GET _cat/master?v # Shows current master node # Check node roles GET _cat/nodes?v&h=name,node.role,master
Interview Tip
Mention the Raft-based coordination in ES 7+ — it shows you know the modern approach, not just the legacy minimum_master_nodes setting.
💬 Comments
Quick Answer
Hot nodes use SSDs for recent, frequently queried data. Warm nodes use HDDs for older, less-queried data. Cold nodes use cheapest storage for compliance/archival data. ILM policies automate transitions.
Detailed Answer
Hot tier: Recent data (last 1-7 days), fast SSDs, high CPU/RAM. All writes go here. Handles real-time queries and dashboards.
Warm tier: Older data (7-30 days), HDDs acceptable, moderate resources. Read-only (force merge to 1 segment for efficiency). Still queryable but slower.
Cold tier: Historical data (30-90+ days), cheapest storage. Searchable snapshots (ES 7.13+) allow querying data stored in S3/GCS without local replicas — massive cost savings.
Frozen tier (ES 7.12+): Data stays entirely in object storage. Searched on-demand with caching. Cheapest option for rarely accessed data.
Implementation: Assign node roles via node.roles: [data_hot], create ILM policies with rollover conditions, and let Elasticsearch automatically migrate indices between tiers.
Cost Impact: A typical production deployment sees 60-70% cost reduction compared to keeping all data on hot-tier SSDs.
Code Example
# Node configuration for each tier
# Hot node
node.roles: [data_hot, data_content]
path.data: /mnt/nvme/elasticsearch
# Warm node
node.roles: [data_warm]
path.data: /mnt/hdd/elasticsearch
# ILM policy with hot-warm-cold
PUT _ilm/policy/logs-tiered
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_size": "50gb", "max_age": "1d" },
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"allocate": { "require": { "data": "warm" }},
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": { "snapshot_repository": "s3-repo" }
}
},
"delete": { "min_age": "90d", "actions": { "delete": {} }}
}
}
}Interview Tip
Mention searchable snapshots — they're a game-changer for cost. Data in S3 costs ~$0.023/GB/month vs ~$0.10/GB/month for EBS SSDs. Also mention the frozen tier for compliance data.
💬 Comments
Quick Answer
Use dedicated node roles (master/data_hot/data_warm/ingest), multiple ingest pipelines, time-based data streams with ILM, and size for ~86TB of daily data with appropriate shard count.
Detailed Answer
- 1M events/sec × 1KB avg = 1GB/sec raw ingest - Daily: ~86TB raw, ~43TB after compression (2:1) - 30 days retention: ~1.3PB compressed storage
- 3 dedicated master nodes (16GB RAM, no data) - 20 hot data nodes (64GB RAM, NVMe SSDs, 10TB each) - 40 warm data nodes (32GB RAM, HDDs, 40TB each) - 5 ingest nodes (32GB RAM, CPU-optimized for parsing) - 3 coordinating-only nodes for query load balancing
- Data streams with daily rollover - 20 primary shards per index (1 per hot data node) - 1 replica (doubles hot storage need but provides redundancy) - Target shard size: 30-50GB
- Bulk indexing with 5-15MB bulk size - Increase index.refresh_interval to 30s (default 1s) - Use index.translog.durability: async for higher throughput (accept risk of losing last 5s of data on crash) - Disable _source if you don't need re-indexing capability
- Warm nodes: force merge to 1 segment per shard - Use index sorting for common query patterns - Pre-built Kibana dashboards with date filters default to last 24h
Code Example
# Data stream template
PUT _index_template/logs-template
{
"index_patterns": ["logs-*"],
"data_stream": {},
"template": {
"settings": {
"number_of_shards": 20,
"number_of_replicas": 1,
"index.refresh_interval": "30s",
"index.translog.durability": "async",
"index.translog.sync_interval": "5s",
"index.lifecycle.name": "logs-tiered"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" },
"level": { "type": "keyword" },
"service": { "type": "keyword" }
}
}
}
}
# Bulk insert optimization
curl -XPOST 'es:9200/logs/_bulk' -H 'Content-Type: application/x-ndjson' --data-binary @bulk.ndjsonInterview Tip
Show the math: 1M events/sec → daily volume → 30-day retention → total storage. Then map to node count and shard strategy. This demonstrates you can design systems, not just configure them.
💬 Comments
Quick Answer
Elasticsearch: full-text search, complex queries, rich ecosystem. Loki: cost-effective, label-based, Grafana-native. ClickHouse: SQL analytics, high compression, columnar storage for structured logs.
Detailed Answer
- Full-text indexing of all log content - Rich query language (KQL, Lucene, aggregations) - Mature ecosystem (Beats, Logstash, Kibana) - Best for: Teams needing full-text search across all log fields, complex aggregations, existing ELK investment - Drawback: Expensive — indexes every field, high memory/CPU/storage costs
- Only indexes labels (metadata), not log content - Log content is compressed and stored in object storage (S3/GCS) - Uses LogQL (PromQL-like) for querying - Best for: Cost-sensitive environments, Kubernetes-native logging, teams already using Grafana/Prometheus - Drawback: Grep-style search on content is slower than Elasticsearch's inverted index. Not suitable for complex analytics.
- Columnar storage with extreme compression (10:1 to 50:1) - SQL interface for analytics - Blazing fast aggregation queries - Best for: Structured/semi-structured logs, analytics-heavy use cases, very high volume (billions of rows), teams comfortable with SQL - Drawback: Less mature log ecosystem, requires more custom pipeline work
- Elasticsearch: ~$15K-25K/month (hot-warm architecture) - Loki: ~$2K-5K/month (object storage + minimal compute) - ClickHouse: ~$3K-8K/month (high compression reduces storage)
Code Example
# Loki LogQL query (equivalent to ES full-text search)
{namespace="production", app="api-gateway"}
|= "error"
| json
| status_code >= 500
| line_format "{{.timestamp}} {{.message}}"
# ClickHouse log table
CREATE TABLE logs (
timestamp DateTime64(3),
service LowCardinality(String),
level LowCardinality(String),
message String,
trace_id String
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (service, timestamp)
TTL timestamp + INTERVAL 30 DAY;
# Elasticsearch equivalent query
GET logs-*/_search
{
"query": {
"bool": {
"must": [
{ "match": { "message": "error" }},
{ "range": { "status_code": { "gte": 500 }}}
]
}
}
}Interview Tip
Lead with the trade-off triangle: search capability vs cost vs query speed. Show you can make a recommendation based on the team's specific needs rather than just listing features.
💬 Comments
Context
A security analytics team migrated 400 million historical log documents into a new Elastic cluster while production search stayed online. The cluster ran 18 data nodes across hot and warm tiers.
Problem
Initial migration jobs saturated write thread pools and caused analyst dashboards to time out. Increasing worker count made throughput worse because the cluster spent more time rejecting writes and merging small segments.
Solution
They created migration-only indices, increased refresh interval, used measured bulk batch sizes, throttled clients based on rejection rate, and restored replica and refresh settings after each index completed. Search users were routed away from backfill indices until migration finished.
Commands
GET /_cat/thread_pool/write?v # Watches write pressure and rejections
PUT /security-backfill-2025/_settings {"index":{"refresh_interval":"30s"}} # Reduces refresh overhead during backfillPOST /security-backfill-2025/_refresh # Makes documents searchable after controlled indexing window
Outcome
Sustained indexing throughput increased from 22k docs/sec to 71k docs/sec, and dashboard timeout incidents stopped during the migration.
Lessons Learned
Bulk indexing is a feedback-control problem. More workers are not better once the cluster starts rejecting or merging heavily.
◈ Architecture Diagram
┌──────────┐
│ Learn │
└────┬─────┘
↓
┌──────────┐
│ UseCase │
└────┬─────┘
↓
┌──────────┐
│ Operate │
└──────────┘💬 Comments
Context
A 40-node uniform Elasticsearch logging cluster where every log line — from yesterday's incident to an 11-month-old debug entry — sat on the same NVMe-backed hot nodes, because that's how the cluster grew.
Problem
Storage cost scaled linearly with retention while query traffic concentrated overwhelmingly on the last 72 hours; finance flagged the cluster as the third-largest infra line item; and the naive fix (cutting retention) conflicted with a 12-month compliance requirement for audit-relevant indices.
Solution
Implemented hot-warm-cold with ILM: hot tier (NVMe, 6 nodes) ingests and serves 0-3 days via rollover-based indices (50GB/1d conditions); warm tier (dense HDD nodes) holds 3-30 days with force-merged, shrunk, read-only indices; cold tier uses searchable snapshots backed by S3 for 30-365 days (fully mounted only on query). Index templates route per log class: app logs follow the default curve, audit logs get a compliance policy (12-month cold minimum), debug logs die at 7 days. Dashboards and alerting queries were audited to confirm they stay within hot-tier windows.
Commands
PUT _ilm/policy/logs-default {hot: {rollover: {max_size: 50gb, max_age: 1d}}, warm: {min_age: 3d, actions: {shrink: {number_of_shards: 1}, forcemerge: {max_num_segments: 1}}}, cold: {min_age: 30d, actions: {searchable_snapshot: {snapshot_repository: s3-logs}}}}PUT _index_template/logs-app {template: {settings: {index.lifecycle.name: logs-default}}}GET _cat/nodes?h=name,node.role,disk.used_percent # tier balance check
Outcome
Cluster cost fell 55% (hot fleet shrank to 6 nodes, cold storage is S3-priced); p95 query latency on the last-72h window improved (hot nodes stopped carrying cold data); compliance retention extended from 9 to 12 months while total spend dropped. The rare year-old query takes ~30s from searchable snapshots — accepted with eyes open.
Lessons Learned
The per-log-class policy split was the real win — uniform retention had been both over-retaining debug noise and under-retaining audit data. Force-merge before the warm move matters: segment count, not just disk tier, drives warm query cost.
💬 Comments
Context
A shared logging cluster ingesting JSON logs from 80 services with dynamic mapping enabled — every new key any developer ever logged became a field in the index mapping, forever.
Problem
Indices had accumulated 40K+ fields (user IDs as keys, per-request UUIDs as keys, exploded stack traces): cluster state grew huge and slow to propagate, new-field mapping updates caused write rejections at peak ('Limit of total fields exceeded' after the ceiling was raised twice), and Kibana's field pickers became unusable.
Solution
Declared an ingest contract: a strict ECS-based mapping template with ~200 blessed fields; everything else routes into a single flattened-type field (searchable as keywords, no per-key mapping cost) via an ingest pipeline that moves unknown keys under `labels.*`/`extra` and enforces types. Rollout: new indices under the strict template with dual-write comparison for two weeks; offending services got automated Slack reports of their dropped/renamed keys with fix-by dates; the worst offenders (logging user IDs as keys) were fixed at the source. Old fat indices aged out through ILM naturally.
Commands
PUT _index_template/logs-strict {template: {settings: {index.mapping.total_fields.limit: 400}, mappings: {dynamic: 'strict', properties: {...ecs..., extra: {type: flattened}}}}}PUT _ingest/pipeline/normalize {processors: [{script: 'move unknown keys -> ctx.extra'}]}GET logs-app-*/_mapping | jq '[.[].mappings.properties | keys] | flatten | unique | length'
Outcome
Field count stabilized at ~220; cluster-state propagation and master stability issues stopped; peak-hour write rejections ended; Kibana became navigable again. Two services discovered they'd been logging PII as field names — an unplanned compliance win from the audit.
Lessons Learned
Dynamic mapping is a schema decision made by whoever logs the messiest JSON — strict mappings move that decision back to the platform. The flattened escape hatch is what made strictness adoptable: teams lose per-key aggregations on unblessed fields, not the data.
💬 Comments
Symptom
A log backfill job started at 1:00 AM. Indexing throughput dropped by 65%, search latency rose, and the hot tier showed sustained merge pressure.
Error Message
es_rejected_execution_exception: rejected execution of coordinating operation
Root Cause
The team bulk-indexed historical logs into active indices while normal refresh behavior continued. Elasticsearch kept making documents searchable frequently, which is useful for live search but expensive during large backfills. Segment creation, merges, and search load competed with indexing. The underlying issue was a combination of factors that individually seemed harmless but together created a cascading failure. The monitoring that should have caught this early was either missing or configured with thresholds too high to trigger before user impact. The on-call engineer initially pursued the wrong hypothesis because the symptoms resembled a different, more common failure mode. By the time the real root cause was identified, the blast radius had expanded beyond the original service to affect downstream dependencies. This incident exposed a gap in the team's runbooks and highlighted the need for better correlation between metrics, logs, and traces during diagnosis.
Diagnosis Steps
Solution
Create dedicated backfill indices, temporarily increase refresh interval or disable refresh where acceptable, tune bulk batch size, and restore normal settings after indexing completes. Do not simply add more client concurrency; that can increase rejection and merge pressure.
Commands
PUT /logs-backfill/_settings {"index":{"refresh_interval":"30s"}}POST /_bulk # Send controlled bulk batches from the backfill worker
PUT /logs-backfill/_settings {"index":{"refresh_interval":"1s"}}Prevention
Run backfills against isolated indices. Monitor write rejections, merge time, heap pressure, and disk watermarks. Document safe bulk indexing settings.
◈ Architecture Diagram
┌──────────┐
│ Learn │
└────┬─────┘
↓
┌──────────┐
│ Incident │
└────┬─────┘
↓
┌──────────┐
│ Operate │
└──────────┘💬 Comments
Symptom
Ingestion stops cluster-wide; Kibana errors on writes; several indices are read-only. The on-call, following an outdated runbook, deletes 'old' indices — including one that ILM was mid-migration on — and the cluster stays red while a shard recovery storm begins.
Error Message
ClusterBlockException: index [logs-app-000412] blocked by: [TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block]
Root Cause
A upstream log-level misconfiguration (debug enabled fleet-wide by a bad deploy) tripled ingest volume overnight; disks crossed the 95% flood-stage watermark, which sets index.blocks.read_only_allow_delete on affected indices. The runbook predated ILM: manual deletion of indices that ILM was managing created recovery churn, and nobody addressed the actual inflow (the debug logging) for another hour, so freed space refilled immediately.
Diagnosis Steps
Solution
Stopped the bleed first: reverted the upstream debug-logging deploy (inflow back to normal), then freed space (deleted genuinely expendable debug indices via ILM delete-step trigger, not raw DELETE), waited for disk to drop below the high watermark, and removed the read-only blocks. Post-incident: watermark alerting at 80/85% (well before the 95% cliff), the runbook rewritten around 'find and stop the inflow first', and per-source ingest quotas so one service's log storm can't fill the cluster.
Commands
GET _cat/allocation?v&h=node,disk.percent,disk.used,disk.avail
GET _cat/indices?v&s=store.size:desc | head -20
PUT logs-*/_settings {'index.blocks.read_only_allow_delete': null} # only after space freedPrevention
Alert on disk trajectory (time-to-flood at current ingest rate), not just levels. Ingest quotas/circuit breakers per source. Runbooks must treat 'disk full' as 'inflow problem' first, 'space problem' second — and never manually delete ILM-managed indices.
💬 Comments
Symptom
Two data nodes drop from the cluster within a minute of each other (heap OOM), shards go unassigned, search latency cluster-wide spikes, and the node restarts trigger a recovery storm that degrades ingestion for an hour. It happens again the next week — same time, same day.
Error Message
java.lang.OutOfMemoryError: Java heap space — preceded by 'CircuitBreakingException: [parent] Data too large, data for [<agg>] would be [31.9gb], which is larger than the limit' on earlier, smaller variants of the query.
Root Cause
A Kibana dashboard built for a weekly marketing review aggregated terms on user_id (tens of millions of unique values) across 30 days — a high-cardinality terms aggregation whose global ordinals and buckets exceeded heap. Circuit breakers caught smaller versions, but a panel edit (raising the bucket size and adding a sub-aggregation) found a path where memory was consumed before the breaker tripped on the older ES version in use. The weekly recurrence matched the meeting schedule.
Diagnosis Steps
Solution
Immediate: identified and fixed the dashboard (cardinality aggregation for the count they actually wanted; sampler aggregation for the exploratory view; time range cut to 7 days). Platform: upgraded to a version with the real-memory circuit breaker, set search.max_buckets and per-request breaker limits tighter, enabled slow-query logging with weekly review, and restricted marketing's Kibana space to a data view with runtime-field guardrails.
Commands
GET _nodes/stats/breaker | jq '.nodes[].breakers.parent'
PUT _cluster/settings {'persistent': {'search.max_buckets': 10000}}slow log: index.search.slowlog.threshold.query.warn: 10s
Prevention
High-cardinality terms aggregations are the classic ES self-DoS: prefer cardinality/sampler/composite aggregations, keep real-memory circuit breakers current and tuned, cap max_buckets, and review slow/expensive queries on a schedule that catches recurring meeting-driven load before it becomes an outage calendar.
💬 Comments