Elasticsearch
A distributed search and analytics engine built on Lucene — the default answer for full-text search, faceted filtering, and log/observability analytics when SQL LIKE and a B-tree index fall over.
Also worth naming: OpenSearch (open-source fork) · Elastic Cloud · Amazon OpenSearch Service · the ELK / Elastic Stack
Elasticsearch turns "find the relevant documents" from a full-table scan into an inverted-index lookup with real relevance ranking. Treat it as a fast, derived view of your data — never as the source of truth.
What it is
Elasticsearch is a distributed, document-oriented search engine built on Apache Lucene. Its job is the thing relational databases are bad at: full-text search — find the documents most relevant to a set of words, ranked by relevance, fast, across millions or billions of documents. It also does faceted filtering (filter by category, price, date and show counts), aggregations (analytics over the matched set), and geo queries, which is why it underpins both product search and log/observability stacks.
The engine at the core is the inverted index: instead of scanning each document for a word, Elasticsearch precomputes a map from every term to the list of documents containing it. A search becomes "look up a few short posting lists and intersect them," which is independent of corpus size — the opposite of WHERE body LIKE '%term%', which scans the whole table and dies at scale.
The critical framing for interviews: Elasticsearch is a derived index, not a database of record. You keep the authoritative data in Postgres/DynamoDB/etc. and project it into Elasticsearch (via CDC or an outbox), because the index can be lost, corrupted, or need a full rebuild when you change how text is analysed. Reach for it when search, faceting, or log analytics are first-class requirements; do not reach for it as your primary store.
When to reach for it
Reach for this when…
- Full-text search is a real feature — relevance ranking, typo tolerance, autocomplete
- Faceted filtering and aggregations over a large result set (e-commerce, catalogs)
- Log, metrics, and observability analytics at scale (the ELK stack)
- You have outgrown Postgres full-text search (roughly past ~10M docs or heavy query volume)
Not really this pattern when…
- You only need exact-match lookups or simple filters — your primary DB already does that
- Search is light and the data is small — Postgres full-text (tsvector + GIN) avoids a new system
- You need it as a system of record with transactions (it is a derived index, not a database)
- Strong write-read consistency on the search path (indexing is near-real-time, not instant)
How it works
Four ideas explain how to use Elasticsearch well:
1. The inverted index is why search is fast. Lucene tokenises each document into terms and stores, per term, a posting list of the documents containing it. Querying intersects/unions a few short lists rather than scanning the corpus — so search time scales with the number of matching terms, not the number of documents.
Instead of scanning every document for a word, Elasticsearch keeps a map from each term to a posting list of document ids. A search for "quick fox" looks up two short lists and intersects them — fast regardless of corpus size.
2. Analyzers decide what "matches". Before indexing, text runs through an analyzer: tokenise → lowercase → remove stop words → stem ("running" → "run") → language-specific handling. The query is analysed the same way so terms line up. Choosing analyzers (and re-indexing when you change them) is most of the work of getting search quality right — and is why a field is also stored as a non-analysed keyword for exact-match filtering and sorting.
3. Relevance is BM25, then re-ranking. Matches are scored by BM25 (term frequency, inverse document frequency, length normalisation) so the most relevant documents sort first. Real products then layer business signals on top — popularity, recency, personalisation — via function_score, never throwing away the text relevance.
4. It scales by sharding, and queries scatter-gather. An index is split into shards (each a self-contained Lucene index), with replicas for read throughput and failover. A search fans out to every shard, each returns its local top-K, and a coordinating node merges them into the final ranking.
Documents are routed to a primary shard by hashing the document id. Each primary has replica shards for read scale and failover. A query scatters to every shard, each returns its top hits, and the coordinator gathers and merges them.
And the operational truth: the primary database is the source of truth; Elasticsearch is kept in sync via CDC and is rebuildable from the database.
Elasticsearch is not a system of record. Writes go to the primary database; a change-data-capture stream projects them into the index. If the index is lost or its mapping changes, you rebuild it from the database.
Performance envelope
Elasticsearch performance envelope — the numbers to quote.
| Dimension | Number | Why it matters |
|---|---|---|
| Search latency | Tens of ms over millions of docs | Inverted index, not a scan — the whole point |
| Scale | Billions of docs across sharded nodes | Add shards/nodes; queries scatter-gather |
| Indexing freshness | Near-real-time (~1s refresh) | Not instant — a write is searchable after refresh |
| Throughput | ~1K–10K queries/sec per node (workload-dependent) | Replicas scale reads; heavy aggregations cost more |
| Relevance | BM25 default + function_score re-rank | Tune ranking without replacing text scoring |
| Durability | Derived index — rebuild from source DB | Never the only copy of the data |
Capabilities in interviews
Full-text search & relevance
Rank documents by how well they match a query, with typo tolerance and field boosting.
The core use. Analyse the text, search the inverted index, rank by BM25, boost important fields:
{ "multi_match": {
"query": "wireless headphones",
"fields": ["name^3", "description"],
"fuzziness": "AUTO" // typo tolerance
}}name^3 weights title matches 3× over description; fuzziness tolerates misspellings via edit distance. This is product search, document search, anything where "most relevant first" beats exact match — and where a SQL LIKE would both miss relevance and scan the table.
Choose this variant when
- Product / catalog / document search
- Relevance ranking matters, not just matching
- Typo tolerance and synonyms are expected
Faceted filtering & aggregations
Filter the result set by attributes and return live counts and analytics in one query.
The e-commerce sidebar — "Brand (Sony 42, Bose 18), Price, Rating" — is an aggregation over the matched documents:
{ "query": { ... },
"aggs": { "by_brand": { "terms": { "field": "brand" } } } }Filters (must-clauses) restrict the set without affecting relevance score; aggregations compute counts and stats over what matched. Doing this in SQL means many slow GROUP BY queries; Elasticsearch returns search results and all facet counts in a single round trip.
Choose this variant when
- E-commerce / catalog filtering with counts
- Dashboards over a searchable dataset
- Drill-down analytics on matched results
Log & observability analytics
Ingest huge volumes of logs and metrics and query them interactively (the ELK stack).
Elasticsearch + Logstash/Beats + Kibana is the canonical observability stack. Ship logs in, index by time, and slice them interactively in Kibana — error rates by service, latency percentiles, full-text grep across billions of lines:
app logs → Beats → Elasticsearch (time-based indices) → Kibana dashboardsUse time-based indices (logs-2026-06-06) with an ILM policy so old data rolls to cheaper storage and eventually deletes. This is a different shape from product search but the same engine — high-volume ingest, ad-hoc query.
Choose this variant when
- Centralised logging and metrics
- Interactive debugging across services
- Security / audit log analytics
Geo & vector search
Filter by distance, and increasingly, search by semantic similarity (kNN vectors).
Elasticsearch supports geo_point/geo_distance for "within 5 km of here" filters combined with text relevance, and modern versions add dense vector / kNN search for semantic and hybrid retrieval:
{ "knn": { "field": "embedding", "query_vector": [...], "k": 10 } }That makes it a credible option for hybrid search — combine BM25 keyword relevance with vector similarity — though dedicated vector databases may win at very large embedding scale. Knowing it can do both is the interview-relevant point.
Choose this variant when
- Location-filtered search (stores, listings near me)
- Hybrid keyword + semantic retrieval
- RAG retrieval where you already run Elasticsearch
Operating knobs
Analyzer & mapping design
The biggest quality lever. Pick analyzers per field (language stemming, edge n-grams for autocomplete), and store a keyword sub-field for exact filtering/sorting. Define the mapping explicitly rather than relying on dynamic mapping. Changing an analyzer means re-indexing — so design it deliberately up front.
Shard count & sizing
Too few shards caps parallelism; too many wastes overhead (each is a Lucene index with fixed cost) and slows scatter-gather. Aim for shards in the tens-of-GB range and size to your data and query concurrency. Over-sharding a small index is a classic mistake; you cannot change a shard count without re-indexing.
Relevance tuning (function_score)
Start with BM25, then multiply by business signals: score × log1p(popularity) for popular-first, a recency decay for fresh-first. Wrap in function_score so you boost rather than replace text relevance, and validate changes with A/B tests on click-through and conversion — ranking is an empirical loop.
Index lifecycle & refresh
For logs, use time-based indices + an ILM policy (hot → warm → cold → delete) to control cost. The refresh interval controls how quickly new documents become searchable (default ~1s); raise it during bulk indexing for throughput, lower it where freshness matters.
Versus the alternatives
Elasticsearch vs the alternatives.
| Dimension | Elasticsearch | Postgres full-text | A vector DB |
|---|---|---|---|
| Scale | Billions of docs, sharded | Up to ~10M docs comfortably | Billions of embeddings |
| Strength | Relevance, facets, aggregations, logs | Search consistent with your data | Semantic similarity (ANN) |
| Ops cost | A cluster to run (JVM, shards) | Just a Postgres feature | Another specialised system |
| Source of truth? | No — derived, rebuildable | Yes (it is your DB) | No — derived |
| Best for | Search + faceting + log analytics at scale | Modest search without new infra | Pure semantic / RAG retrieval |
Failure modes & gotchas
Elasticsearch can lose data on misconfiguration, and mapping/analyzer changes require a full rebuild. Keep the authoritative copy in a real database and treat the index as a derived, rebuildable view fed by CDC — never the only home for the data.
Indexing into Elasticsearch inside the user write coupling it to search-cluster latency and availability. Index asynchronously via CDC or a queue and accept ~1s of search lag; the write path should not block on the search cluster.
Each shard is a Lucene index with fixed memory and file-handle overhead; hundreds of tiny shards waste resources and slow every scatter-gather query. Size shards to tens of GB and keep the count proportional to data and concurrency — and remember you must re-index to change it.
Jumping to page 1,000 with from/size forces every shard to collect and sort that many results — O(from) work that blows up memory. Use search_after (cursor) for deep pages and reserve from/size for the first handful of pages.
Aggregating over a field with millions of distinct values (e.g. user id) is memory-hungry and can OOM a node. Bound cardinality, use approximate aggregations where acceptable, and push truly heavy analytics to a warehouse instead of the live search cluster.
In production
Uber
Sub-second geospatial + text search across the platform
Uber runs Elasticsearch at large scale for search and analytics — powering features like finding places and restaurants, internal observability, and real-time operational dashboards. Their write-ups describe clusters serving high query volumes with sub-second latency, combining full-text relevance with geo_distance filtering ("relevant restaurants near me") in a single query — exactly the hybrid that a keyword-only or geo-only system can't do alone.
The operational lessons map to the interview answer: Elasticsearch is fed asynchronously from the systems of record (never the source of truth), indices are sharded and reindexed behind aliases as mappings evolve, and heavy aggregations are watched carefully because they are memory-hungry. It's the reference example of "search engine as a derived, query-optimized view fed from your data."
Netflix
The ELK stack for billions of log events a day
Netflix is a flagship user of Elasticsearch for observability — ingesting billions of log and event records per day across thousands of microservices into the Elastic (ELK) stack, where engineers search and visualize them in near-real-time to debug incidents and watch system health. This is the second canonical Elasticsearch use beyond product search: high-volume, time-stamped data queried interactively.
The architecture choices are textbook log-analytics: time-based indices (one per day) with an ILM policy that rolls older data to cheaper storage and eventually deletes it, controlling cost; the inverted index makes "grep across billions of lines for this error in the last hour" fast; and the cluster is sized for ingest throughput plus query concurrency. It shows the same engine serving a completely different shape of problem from product search.
Good vs bad answer
Interviewer probe
“An e-commerce site needs product search over 50M items with typo tolerance and brand/price filters. How do you build it?”
Weak answer
"Store products in Postgres and run SELECT * FROM products WHERE name LIKE '%query%', plus WHERE brand = ? for the filters. Add an index on name to speed it up."
Strong answer
"Elasticsearch — LIKE '%query%' is a full-table scan that dies well before 50M rows, and a B-tree index can't do relevance, typos, or facet counts. Postgres stays the source of truth; I project products into Elasticsearch via CDC so the index is rebuildable. The mapping analyses name and description (English stemming) for relevance, with brand, category, price as keyword/numeric for exact filtering and sorting. Search is a multi_match over name^3 and description with fuzziness: AUTO for typos; brand/price filters are must-clauses that restrict without affecting score; and the sidebar counts come from a terms aggregation — search results and all facet counts in one round trip. Base ranking is BM25, then function_score boosts by popularity and recency so good-but-boring matches don't outrank what people actually buy. For reindexing after analyzer changes I build into a new index and flip an alias atomically. LIKE gives me none of this."
Why it wins: Rejects LIKE with the scan reasoning, keeps the DB as source of truth with CDC, designs the mapping (analysed vs keyword fields), uses fuzziness + facet aggregations + BM25/function_score ranking, and plans alias-based reindexing — a complete, scale-aware search design.
Interview playbook
When it comes up
- A feature needs full-text search, autocomplete, or relevance ranking
- Faceted filtering with live counts (e-commerce, catalogs)
- Centralised logging / observability / security analytics
- Someone proposes a LIKE query on the primary database
Order of reveal
- 11. Reject scan-based search. LIKE is a full scan with no relevance; real search means an inverted index, so I name Elasticsearch (or Postgres FTS if small).
- 22. Keep the DB as truth. The primary DB is the source of truth; I project into Elasticsearch via CDC so the index is rebuildable.
- 33. Design the mapping. Analysed text fields for relevance, keyword fields for exact filter/sort; analyzers chosen per language.
- 44. Relevance. BM25 base score, then function_score to boost by popularity and recency.
- 55. Reindex safely. Build into a new index and flip an alias atomically when the mapping changes, keeping the old one for rollback.
Signature phrases
- “Search is an inverted-index lookup, not a table scan.” — The line that justifies a search engine existing.
- “The DB is the source of truth; the index is a derived, rebuildable view.” — The single most important operational fact.
- “Analysed fields for relevance, keyword fields for exact filtering.” — Shows real mapping design, not hand-waving.
- “BM25 first, then boost by popularity and recency.” — Separates textual match from business ranking.
Likely follow-ups
?“How do you keep the index in sync with the database?”Reveal
Asynchronously, via change-data-capture. Every committed change in the source DB (Debezium tailing the WAL, or an outbox topic) becomes an event that an indexer consumes and upserts into Elasticsearch. The write path never blocks on the search cluster, and I accept ~1 second of indexing lag (near-real-time refresh). Because the index is derived, if a consumer bug corrupts it I fix the code and replay from the stream, or bulk-rebuild from the database — the authoritative data is never at risk.
?“Is Postgres full-text search ever enough instead?”Reveal
Yes — up to roughly 10M documents with modest query volume and no heavy faceting, a tsvector column with a GIN index and ts_rank is genuinely sufficient, and it keeps search transactionally consistent with the data while avoiding a whole separate cluster to operate. I graduate to Elasticsearch when scale, faceted aggregations, typo tolerance, or fine relevance tuning exceed what FTS offers. Reaching for an Elasticsearch cluster on a 100k-row catalog is over-engineering.
?“A user reports their just-created product is not searchable yet. Why?”Reveal
Indexing is near-real-time, not instant. A newly indexed document becomes searchable only after the next refresh (default ~1s), and if I index asynchronously via CDC there is additional pipeline lag of perhaps a second or two. That is almost always an acceptable trade for not coupling writes to the search cluster. If a specific flow truly needs read-your-write search immediately, I can force a refresh for that document or serve that one view from the source DB — but I would not lower the global refresh interval, which hurts indexing throughput.
Worked example
Setup. Add product search to a marketplace with 50M listings: typo-tolerant full-text search over titles and descriptions, brand/price/category filters with live counts in the sidebar, and results ranked so popular items surface — at thousands of queries/sec.
The move. Postgres stays the source of truth; I project listings into Elasticsearch via CDC so the index is a derived, rebuildable view (a Debezium stream off the products table → an indexer that upserts documents). The mapping is the real design work: title and description are analyzed text (English analyzer, stemming) for relevance; brand, category are keyword for exact filtering and facet counts; price, rating, sales_30d are numeric for filtering and boosting.
Query shape. A search is a multi_match over title^3 and description with fuzziness: AUTO for typos. Brand/price filters are must-clauses that restrict the set without touching the score; the sidebar counts come from a terms aggregation in the same request — so one round trip returns results and all facet counts. Base relevance is BM25, then function_score multiplies by log1p(sales_30d) and a recency decay so good-but-boring matches don't outrank what people actually buy.
Scale. I shard the index sized to ~tens of GB per shard with replicas for read throughput and HA; queries scatter to every shard and the coordinator merges the top-k. At 50M docs this is a modest cluster.
What breaks. Two things. Deep pagination — jumping to page 1,000 with from/size forces every shard to collect and sort that many hits, so I use search_after (cursor) for deep pages. And reindexing when I change an analyzer: I build into products-v2, bulk-reindex from the source, then flip an alias atomically so traffic cuts over with zero downtime and I keep v1 for one-command rollback. I never put indexing on the synchronous write path — it's async via CDC, ~1s lag.
The result. Typo-tolerant relevance over 50M listings in tens of ms, search results and all facet counts in one query, business-aware ranking, and a rebuildable index that stays in sync with Postgres — none of which a SQL LIKE could deliver.
Cheat sheet
- •Distributed search engine on Lucene: inverted index → fast full-text search with BM25 relevance.
- •Derived index, NOT a system of record. Keep truth in the DB; sync via CDC; rebuildable.
- •Analyzers (tokenise/lowercase/stem) decide matches; keyword sub-fields for exact filter/sort.
- •Scales by sharding; queries scatter-gather across shards; replicas for read scale + HA.
- •Facets/aggregations return filtered counts in one query — the e-commerce sidebar.
- •Relevance: BM25 base, function_score to boost popularity/recency. Tune via A/B tests.
- •Indexing is near-real-time (~1s refresh), async off the write path.
- •Reindex behind an alias (atomic flip + rollback). Use search_after, not deep from/size.
Drills
Why is an inverted index faster than a SQL LIKE for search?Reveal
LIKE '%term%' cannot use a normal B-tree index (the leading wildcard defeats it), so it scans every row and tests the predicate — O(rows). An inverted index precomputes, for each term, the list of documents containing it, so a search looks up a few short posting lists and intersects them — work proportional to the matching terms and result size, not the corpus. It also enables relevance ranking, stemming, and typo tolerance, none of which LIKE can express.
Interviewer: "results are textually correct but popular items rank low. Fix it."Reveal
Layer business signals on top of BM25 with function_score rather than replacing text relevance: multiply the base score by a popularity factor like log1p(sales_30d) and apply a recency decay so fresh, popular items rise. Keep the text match as the foundation so irrelevant-but-popular items do not flood the top. Then validate with A/B tests on click-through and conversion — relevance tuning is an empirical loop, not a one-shot formula.
You changed an analyzer and existing documents no longer match correctly. What is the process?Reveal
Analyzer changes require re-indexing, because documents were tokenised under the old rules. Create a new index with the updated mapping/analyzer, bulk-reindex from the source database (or the old index via the reindex API), verify results, then flip the index alias from old to new atomically so live traffic cuts over with zero downtime — keeping the old index briefly for one-command rollback. This alias pattern is why reindexing is routine rather than scary.
What it is