Elasticsearch
0-day streakReview

Your progress

Level 1

0 XP100 XP to level 2
0day streak
0best streak
0topics started
0cards reviewed
    Review cards

    Progress is stored only in this browser. No account is required.

    Layer 4 · Full breakdown · 20 min · 5 figures

    Elasticsearch

    What an interviewer expects you to know about Elasticsearch: documents and mappings, near-real-time guarantees, keeping the index in sync with your database, shard and replica arithmetic, typeahead and geo search, and when to pick something else.

    Elasticsearch is a distributed search and analytics engine built on Apache Lucene. It analyses the JSON documents you send into an inverted index plus structures for range and geo filters, and answers a query by asking every shard for its best hits and merging them. In an interview it is the default answer to “search, filter and rank millions of things by text, with facets, typeahead or distance, in milliseconds”, as long as you put it beside your database and say how the two stay in sync.

    This one-pager tracks Elasticsearch 9.5 (current docs, October 2026); the internals live in the search engines series, linked from each section.

    Picture it first

    A big mall’s office keeps the official register of its 300 shops. At the entrance, a help-desk clerk answers shoppers from a card cabinet: each shop has a card filed under every word that matters (“momos”, “veg”, “kids’ shoes”) and a pin on a floor map.

    When the office changes the register, a peon carries the change slip to the desk. The clerk writes the new card at once but drops it in a tray, emptied into the cabinet every second. Ask for “veg momos near gate 3” and she hands you five shop numbers, best first; you still walk to the shop for today’s price. The cabinet has four drawers, and a second-floor desk keeps a copy of each. If the cabinet burns, she rebuilds it from the register.

    Map it back: the register is the database, the source of truth; the peon is CDC and Kafka; a card is a document; filing under every word is the inverted index; the tray emptied each second is the refresh; the pin is a geo_point; drawers are shards, the second-floor copy a replica; walking to the shop is hydrating from the database; rebuilding is a reindex.

    Data model: indices, documents, mappings

    An index is a named collection of JSON documents sharing one mapping, the schema that says how each field is indexed. Each document has an _id and keeps its original JSON as _source. There are no tables or query-time joins: one document is one search result. The field types to name:

    • text: analysed into terms (split, lower-cased, optionally stemmed) for full-text matching and BM25 scoring (part 2).
    • keyword: the exact value, with column-oriented doc values for filters, sorting and facet counts.
    • Numbers, date, boolean: range filters and sorting.
    • geo_point, geo_shape: a location, or an area such as a delivery zone.
    • dense_vector: embeddings for kNN search, up to 4,096 dimensions (dense vector).

    The running example is a Swiggy-style food-delivery app, with one document per dish:

    PUT dishes_v1
    {
      "settings": { "number_of_shards": 3, "number_of_replicas": 1 },
      "mappings": {
        "dynamic": "strict",
        "properties": {
          "name":    { "type": "text", "fields": { "raw": { "type": "keyword" } } },
          "suggest": { "type": "search_as_you_type" },
          "cuisine": { "type": "keyword" },
          "veg":     { "type": "boolean" },
          "price":   { "type": "integer" },
          "restaurant": {
            "properties": {
              "id":       { "type": "keyword" },
              "name":     { "type": "text" },
              "location": { "type": "geo_point" },
              "rating":   { "type": "half_float" },
              "is_open":  { "type": "boolean" }
            }
          }
        }
      }
    }

    Three rules follow. You can add fields but not change a field’s type or analyser without a reindex, so applications talk to an alias (dishes) that you repoint atomically. Turn dynamic mapping off for anything sellers supply: every unseen key becomes a field, and the index rejects documents past index.mapping.total_fields.limit, 1,000 by default, objects and multi-fields included (mapping limits). And an update rewrites the whole document: segments are immutable, so a price change is a delete plus a re-add (part 3).

    Each index has primary shards, 1 by default and fixed at creation, each with replicas, 1 by default and changeable any time (index settings). A document goes to shard hash(_routing) % number_of_shards, where _routing defaults to _id. Timestamped, append-only data such as logs goes into a data stream, one name over a series of hidden backing indices. Part 5 covers masters, routing and replication.

    Interviewers probe this more than internals. The rule: model for the query, not the entity. Decide what one search result is, make that a document, and copy in everything you filter, rank or display.

    Denormalise, and pay at write time

    In Postgres a dish row points at its restaurant and its rating summary. The indexer joins them once, on write, so “paneer tikka near me, open now, rated 4+” reads one document per hit (Fig. 1, top).

    You gain query speed: no joins, every filter checked inside one document. You pay in write fan-out: when Dilli Darbar closes for the night, all 60 of its dish documents need updating. Fine for an is_open flag that flips twice a day; wrong for exact stock or rider ETAs, which you read for the 20 results you render (data modelling and denormalisation).

    Arrays of objects: object, nested or join

    Put the dishes inside one restaurant document instead, and the default object mapping flattens the array into parallel lists, losing which values belonged together (nested field type).

    Top: three normalised Postgres tables become one denormalised search document per dish. Bottom: the same restaurant indexed with its dishes as an object array and as nested objects, queried for “a veg dish under ₹150”.Notice The object mapping returns a restaurant that has no such dish, because it matched ‘veg’ from one dish and ‘₹120’ from another; nested fixes it by indexing each dish as a hidden document.
    Object array nested join (parent/child) One document per dish
    Fields from different dishes mix Yes No No No
    Lucene docs for 1 restaurant, 60 dishes 1 61 61, all on one shard 60
    Changing one dish Reindex the restaurant Reindex the restaurant and all 60 dishes Reindex that dish Reindex that dish
    Query cost Lowest nested query: moderate has_child: highest Low
    Use it for Arrays never matched in pairs Small arrays queried as units Few parents, many fast-changing children The thing users search for
    Four ways to model restaurants and their dishes. The last column usually wins for food search.

    Elastic’s join field page itself says to denormalise rather than replicate a relational model. Nested is capped by default at 100 nested mappings per index and 10,000 nested objects per document.

    text or keyword

    “Paneer tikka” as text matches “tikka paneer”; as keyword it matches only the exact string, but can be sorted and counted. Names need both, via a multi-field (name plus name.raw). IDs, enums and tags are keyword only: a facet like “North Indian (342)” on text needs fielddata, off by default.

    One index per tenant, or one shared index?

    When each B2B customer searches only its own documents, per-tenant indices give isolation and easy deletion, but cost shards. A node holds at most 1,000 non-frozen shards and masters need 1 GB of heap per 3,000 indices (size your shards), so 50,000 tenants × (1 primary + 1 replica) = 100,000 shards need at least 100 data nodes before any data. So share an index: add a tenant_id keyword, route with routing=tenant_id, and enforce the filter in a filtered alias or the API. Give the largest tenants their own index, the bridge model from tenancy models.

    Guarantees, precisely

    Near-real-time search. A write becomes searchable at the next refresh, which turns the in-memory buffer into a new segment every index.refresh_interval: 1 s by default, 5 s on Elastic Cloud Serverless. The catch: shards with no search or GET for 30 s (index.search.idle.after) skip background refreshes, so the next search triggers one and waits. An explicit refresh_interval, even 1s, opts out (index settings).

    Real-time GET. GET dishes/_doc/dish_9001 is real-time by default, “not affected by the refresh rate” (get API). Only search is near-real-time.

    Durability. With the default index.translog.durability: request, a write is acknowledged only after the translog is fsynced on the primary and every allocated replica (translog). async fsyncs every 5 s and can lose acknowledged writes: fine for logs you can resend.

    Replication. Primary-backup, not quorum: the primary forwards each operation to every in-sync copy and acknowledges when all have applied it; a failed copy is first dropped from the in-sync set by the master (reading and writing documents). Two copies survive one failure; one slow replica slows every write.

    Atomicity. One document is atomic; nothing larger is. A bulk request is a batch, not a transaction: each item succeeds or fails alone.

    Concurrency. For read-modify-write, send back the _seq_no and _primary_term you read as if_seq_no and if_primary_term; if the document changed since, you get 409 Conflict (optimistic concurrency control). An indexer copying from a database sends version_type=external with the row version instead, so a late or replayed event cannot overwrite newer data.

    Read-your-writes. A seller renames a dish and searches for it. Your options:

    • refresh=wait_for on that write: the response waits up to a second for the next refresh. Past 1,000 waiting requests per shard it forces one (refresh parameter).
    • refresh=true: refresh now. Instant, but it makes tiny segments that every search and merge pays for. Never on every write.
    • Read the edit from the database, or by GET, on the seller’s own screens. Usually the best answer.

    Approximate or surprising. Scores use per-shard statistics, terms facets are top-N per shard, and hits.total is exact only to 10,000 by default (part 5). A search can see a write before it is acknowledged, and an isolated old primary can briefly expose writes that never will be: dirty reads. A failed shard still yields 200 OK with partial results and _shards.failed above zero. Copies at different refresh points can reorder pages unless preference pins a session to the same copies.

    One write’s life on a shard refreshed every second. The write is acknowledged at 0.35 s; search finds it only after the refresh at 1.0 s, while a GET by ID finds it at once.Notice ‘acknowledged’, ‘searchable’ and ‘readable by ID’ are three different moments, and that wait_for buys read-your-writes by spending up to a second of latency.

    Not a system of record

    No multi-document transactions, no constraints, reindexing for mapping changes, and replicas that copy a bad bulk delete within milliseconds. Keep the database as the truth, take snapshots, and be able to rebuild the index from scratch.

    Keeping the index in sync with your database

    “How does data get into Elasticsearch, and how stale can it be?” comes up in almost every search design. The standard answer, from part 4:

    Where Elasticsearch sits in a food-delivery search design. Writes reach the index only through the database’s change stream; reads take IDs from the index and live facts from the database.Notice Nothing writes to Elasticsearch except the indexer, and that the user never sees a price or ‘open now’ that came only from the index.
    1. Write to the database only. The menu service never calls Elasticsearch.
    2. Publish what committed. Change data capture reads the database log, or the service writes a transactional outbox row in the same transaction, so the event exists if and only if the row committed. It goes to a Kafka topic keyed by dish ID, keeping each dish’s changes in order.
    3. Index idempotently. The indexer joins in restaurant fields, sends _bulk requests of a few MB, and passes the row version as an external version. A replayed event gets 409 and is skipped: an idempotent consumer for free. Deletes are events too.
    4. Query for IDs. The search API asks for the top 20 dish IDs, with text, geo and open-now filters.
    5. Hydrate from the truth. It reads price, stock and opening status for those 20 from the database; checkout re-checks.

    The consistency gap is CDC lag plus the indexer’s batch window plus up to a second of refresh: with Fig. 3’s assumed numbers, about 1–3 s when healthy, minutes when the indexer falls behind. Alert on consumer lag in seconds. This is eventual consistency by design, and hydration hides most of it.

    Why not dual-write? If the service saves to Postgres and then to Elasticsearch, a crash between the two leaves them disagreeing forever, and two concurrent updates can reach the index in the wrong order. CDC orders changes by commit; external versions reject the stale one.

    Rebuilds. To change an analyser or the shard count, build dishes_v2 from a database snapshot, replay Kafka from the snapshot’s position, compare, and repoint the alias atomically; keep v1 for rollback. A nightly count per restaurant against the database catches silent drift.

    How it scales

    Primaries spread data and write load; replicas add read capacity and failover. A search goes to one copy of every shard, each returns its top from + size hits on one thread, and the coordinating node merges them and fetches only the winners (part 5). So:

    • More primaries add per-query parallelism and fan-out overhead; the count is fixed without split, shrink or reindex.
    • More replicas add queries per second, paid for in disk and indexing on every copy. Change them any time.
    • Custom routing (routing=city_id) sends a query that names the city to one shard, at the risk of a hot shard for Mumbai.

    Sizing a cluster in an interview

    Elastic’s guidance is 10–50 GB and under 200 million documents per shard (size your shards). Walk the back-of-envelope arithmetic out loud:

    Capacity estimation for dish search: data size sets the number of primaries, peak traffic sets the number of copies, and a spike is met first with a cache, then with replicas.Notice The two questions have separate answers: 36 GB decides 3 primaries, while queries per second decide whether you need 6 or 9 copies of them.
    • Data. 400,000 restaurants × 60 dishes = 24 million documents. At an assumed 1.5 KB each on disk (measure yours), that is 36 GB per copy: three primaries of 12 GB and 8 million documents, inside the guidance, giving every query three threads.
    • Traffic. A dinner peak of 4,000 searches/s × 3 shards = 12,000 shard searches/s. If a benchmark shows one 16-vCPU node handling 2,000 a second at 60% CPU, you need 12,000 ÷ 2,000 = 6 copies: 3 primaries × (1 + 1 replica), one per node.
    • Memory. With 32 GB of RAM and a 16 GB heap, each node’s 12 GB copy fits in the page cache, keeping tail latency low.

    Surviving a spike

    An IPL final’s innings break or a Flipkart-style sale can triple traffic for twenty minutes. In order:

    • Cache at the API, keyed by normalised query, geohash cell and filters, for 30–60 s: “biryani” in one neighbourhood repeats thousands of times. At a 50% hit rate, 12,000 searches/s becomes 6,000 (caching strategies).
    • Add replicas before the event. 6,000 × 3 ÷ 2,000 = 9 copies: 2 replicas on 9 nodes. At the default recovery rate of 40 MiB/s per node, a 12 GB copy takes 12 × 1,024 ÷ 40 ≈ 307 s, about 5 minutes, so scale an hour ahead.
    • Set a query budget: a search timeout, capped size and page depth, and a degraded mode without fuzziness and facets. Shed load with a rate limiter before the cluster does it with 429s.
    • Protect writes. If menu updates spike too, raise refresh_interval to 5 s for the duration.

    Typeahead, geo, logs and vectors

    Typeahead: three options

    Elasticsearch offers three ways to complete a dish name as the user types:

    Three ways to suggest “Paneer Tikka Masala” while the user types: what each indexes, which prefixes it matches, and what it costs.Notice Only the completion suggester fails on “tikka pa”: it matches from the start of an input, which is exactly why it is the fastest.
    • The completion suggester stores each input and its weight in a finite-state structure built for speed. It matches only from the start of an input (add “tikka masala” as a second input), supports fuzzy prefixes, and filters only through contexts such as a category or geo cell. The structure is entirely heap-resident and fastest on a single-shard index (suggesters).
    • A search_as_you_type field creates shingle subfields and an edge-gram _index_prefix from one mapping line; a bool_prefix multi_match matches words in any order and combines with any filter (search_as_you_type).
    • An edge n-gram analyser is the do-it-yourself version; its default gram lengths of 1 and 2 are “almost entirely useless”, says the tokenizer page, so you tune them and pay for the extra terms.

    In an interview: completion over an index of popular queries; search_as_you_type for result typeahead that must respect location and opening hours. The typeahead design adds the in-memory trie.

    Geo: restaurants near me

    Map the location as geo_point and filter by distance; filters are not scored and can be cached:

    GET dishes/_search
    {
      "size": 20,
      "query": {
        "bool": {
          "must":   [ { "match": { "name": "paneer tikka" } } ],
          "filter": [
            { "term": { "restaurant.is_open": true } },
            { "geo_distance": { "distance": "5km",
                "restaurant.location": { "lat": 12.9716, "lon": 77.5946 } } }
          ]
        }
      }
    }

    geo_distance matches a radius, geo_bounding_box a map viewport, and a geo_shape query tests delivery-zone polygons against the customer’s point. Sort by _geo_distance, or blend distance into relevance with a gauss decay. geotile_grid and geohash_grid aggregations bucket hits into map tiles for “23 restaurants here” clusters. Geospatial indexes compares the cell schemes. For points that move every few seconds, like riders, a refresh-based index is the wrong tool: use Redis GEO or an in-memory grid (proximity search).

    Logs and observability

    Logs are append-only and time-ordered, so they go into a data stream with index lifecycle management (ILM): roll over at 50 GB per primary shard or daily, move older indices through warm, cold and frozen tiers, and delete whole indices, never documents. Since 9.0, new logs-*-* data streams default to the logsdb index mode: up to 60% less storage for 10–20% more indexing cost in Elastic’s benchmarks (logs data streams). Put Kafka in front to absorb bursts; part 5 sizes the tiers.

    A dense_vector field gets a quantised HNSW graph per segment, and a knn clause fused with a BM25 query by reciprocal rank fusion gives hybrid search in one engine. Choose it when you need keywords and filters beside semantics, and a dedicated vector database for billions of vectors and little else. Quote memory: part 4 puts 50 million 768-dimension embeddings at about 42 GB of page cache per copy with int8.

    Limits and numbers

    Defaults from the Elasticsearch 9.5 documentation (October 2026). Shard sizes are guidance; the rest are enforced limits or defaults.

    What Number Notes
    Shard size 10–50 GB, under 200 million docs Guidance; roll time series over at 50 GB
    Docs per shard, hard limit 2,147,483,519 (2³¹ − 129) Lucene’s; deleted and nested docs count
    Shards per node 1,000 non-frozen cluster.max_shards_per_node
    Primaries per index Default 1, maximum 1,024 Fixed at creation; replicas default 1
    Fields per index 1,000 index.mapping.total_fields.limit
    Nested 100 mappings; 10,000 objects per doc Each object is a Lucene doc
    Result window from + size ≤ 10,000 search_after with a point in time beyond
    Request body 100 MB http.max_content_length, bulk included
    Refresh 1 s (5 s on Serverless) Skipped on shards idle for 30 s
    Translog fsync per request; async every 5 s Flush at 10 GB
    Query clauses Derived from heap and thread pool, at least 1,024 max_clause_count ignored since 8.0
    Heap ≤ 50% of RAM, under ~26 GB Compressed oops; the rest is page cache
    Disk watermarks 85% / 90% / 95% At 95% (100 GB free on big disks), indices on the node go read-only
    dense_vector 4,096 dimensions Quantised HNSW by default
    Recovery 40 MiB/s per node 12 GB copy ≈ 5 minutes

    When to pick it, and when not

    Elasticsearch OpenSearch PostgreSQL full-text Typesense / Algolia Columnar log store (ClickHouse, Loki) Dedicated vector DB
    Model Lucene shards: inverted index, doc values, BKD, HNSW Same; forked from Elasticsearch 7.10.2 tsvector with a GIN index inside the database Typesense: RAM-first index; Algolia: hosted API ClickHouse: compressed columns; Loki: labels indexed, chunks in object storage ANN index plus metadata filters
    Freshness ~1 s refresh + pipeline lag Same Same transaction Near real time; Algolia async, ms to seconds Seconds; built for batched writes Varies
    Relevance BM25, function scores, learning to rank, RRF Same family, hybrid pipeline ts_rank, no IDF Typo-tolerant ranking rules Little: filters, scans, aggregations Vector distance
    Ops High: JVM, shards, upgrades High; AWS managed service None extra Low; none for Algolia Medium Low to medium
    Licence ELv2, SSPL or AGPLv3 Apache 2.0, Linux Foundation PostgreSQL (permissive) GPLv3; proprietary SaaS Apache 2.0; AGPLv3 Varies
    Pick it for Text + facets + geo + hybrid at tens of millions of docs; log search The same, with Apache 2.0 or Amazon’s service Up to a few million rows, simple ranking Small team; index fits RAM or the SaaS budget Cheap, huge log and metric volumes Billions of vectors, semantic only
    Elasticsearch against the usual alternatives (licences as of October 2026). The last row is the honest reason to pick each.

    On licences: Elastic left Apache 2.0 for SSPL and the Elastic License in 2021, AWS forked 7.10.2 as OpenSearch, and Elastic added AGPLv3 in 2024 (licensing FAQ). Running Elasticsearch behind your own app is unaffected.

    Pick Elasticsearch when users type words and expect ranked, typo-tolerant results with facets, when text meets geo or numeric filters, or when you search terabytes of logs.

    Pick something else when:

    • It would be the only copy. Keep a database underneath.
    • You need transactions: seat inventory, payments. Use PostgreSQL.
    • You only look up by key. Use a key-value store or Redis.
    • A few hundred thousand rows live in Postgres already. Its full-text search needs no pipeline.
    • Points move every few seconds, like riders. Use an in-memory geo index.
    • It is pure analytics over billions of events: a columnar store is far cheaper.

    Common interview usage

    Design problem Elasticsearch’s role Document and index design The follow-up to expect
    Product search for an online store Full-text, facets, ranking Doc per product; title text, brand keyword, coarse in_stock Price and stock churn; facet accuracy
    Nearby places (Yelp) Text plus geo filter and distance sort Doc per place; geo_point, categories, rating Dense city centres; relevance versus distance
    Food delivery (Swiggy) Dish search near the user Doc per dish, restaurant fields copied in; zones as geo_shape A restaurant closes: fan-out updates
    Post search (Twitter) Recent posts by keyword or hashtag Time-based indices; doc per post Viral write bursts; search only recent indices
    Typeahead Suggestions per keystroke Popular-queries index with a weighted completion field Why not an in-memory trie?
    Distributed logging Search and aggregate logs Data streams, ILM, logsdb, Kafka in front Cost at 1 TB a day; why not Loki
    Ticketmaster (BookMyShow) Event discovery by name, city, date Doc per event; venue geo_point, date, genre Seat availability never comes from the index
    Reddit / news aggregator Search posts or articles Doc per post; recency decay, votes Votes change constantly: batch updates
    Job search (LinkedIn-style) Jobs by skill, title, location Doc per job; skills keyword, geo_point, salary Personalised ranking
    RAG over company documents Hybrid passage retrieval Doc per chunk; text, dense_vector, ACL groups Permission filtering; re-embedding

    In the interview

    Give Elasticsearch’s job in one line, with the pipeline and the read path: “Dishes are indexed from Postgres through CDC and Kafka, one document per dish with restaurant fields copied in; the search API filters by distance and open-now, takes the top 20 IDs, and reads price and stock from Postgres.” That answers the first three follow-ups before they are asked.

    Going deeper: failure modes

    • Silent drift. A dropped event or indexer bug leaves the index wrong without an error. Reconcile counts nightly, alert on lag, rehearse the rebuild.
    • Expensive queries. A leading wildcard (*tikka) walks the term dictionary, scripts run per document, and a scraper at from=9,980 costs every shard 10,000 entries. Cap depth; use search_after (keyset pagination).
    • Hot shards. Routing by city or tenant puts Mumbai or the biggest customer on one shard. Give giants their own index.
    • Oversharding. Daily 5-shard indices kept a year are 1,825 small shards. Roll over by size.
    • Disk flood stage. At 95% disk, indices on that node turn read-only and the indexer fails. Alert before 85%.
    • Heap pressure. Huge terms aggregations, fielddata and big completion fields live on the heap; long GC pauses drop nodes.
    • Bulk rejections. A 429 means a full write queue: back off with jitter.

    Cheat sheet

    Concept Remember it as
    Index → shard → segment Documents → Lucene index → immutable files
    Mapping text to search; keyword to filter, sort, count; type changes need a reindex
    Modelling One document per search result; denormalise; nested for small arrays; avoid join
    Visibility Search after refresh (1 s; idle shards wait); GET by ID real-time
    Durability Acked = translog fsynced on primary and replicas
    Atomicity One document; bulk is not a transaction
    Conflicts if_seq_no + if_primary_term, or external versions; 409
    Read-your-writes refresh=wait_for, or read from the database
    Sync DB → CDC/outbox → Kafka → indexer → alias; IDs from the index, facts from the DB
    Sizing 10–50 GB per shard; primaries from data, replicas from QPS
    Typeahead, geo completion or search_as_you_type; geo_point + geo_distance filter
    Not for System of record, transactions, moving points, pure analytics

    How to say it in the interview

    “Postgres stays the source of truth; Elasticsearch is a rebuildable search index. Menu changes flow through CDC into Kafka, keyed by dish ID, and an indexer writes one document per dish with the restaurant’s name, location, rating and open flag copied in, using the row version as an external version so replays are harmless. The index has 3 primaries of about 12 GB and one replica, behind an alias. A search filters by geo_distance and open-now, ranks by BM25 with a rating boost, and returns 20 IDs; the API reads live price and availability from Postgres. Results lag writes by a second or two, which is fine for discovery; checkout re-checks everything.”

    Why not use Elasticsearch as the primary database? No multi-document transactions or constraints, near-real-time search, and reindexing for mapping changes. If it holds the only copy, a bad bulk delete or broken mapping is data loss.

    How stale can results be, and what do you do about it? CDC lag plus indexer batching plus up to a second of refresh: one to three seconds when healthy, minutes in a backlog. Hydrate volatile fields from the database, re-check at checkout, show sellers their own edits from the database, and alert on consumer lag.

    How many shards and replicas would you create? Primaries from data: 36 GB at 10–50 GB per shard gives 3. Replicas from traffic: 12,000 shard searches a second at 2,000 per node needs 6 copies, so one replica. Before a spike, cache at the API and add replicas an hour ahead.

    Restaurants with dishes: nested or separate documents? Separate dish documents with restaurant fields copied in: users search for dishes, and a dish update reindexes one small document. Object arrays cross-match fields; nested reindexes the whole restaurant on every dish change.

    How would you build autocomplete? A popular-queries index with a completion field weighted by frequency, rebuilt from logs every few hours, for suggestions. For results as you type, a search_as_you_type field with bool_prefix, so location and opening-hours filters still apply.

    Where to go next

    References and further reading

    1. Elasticsearch documentation: General index settingselastic.coDefaults for number_of_shards and number_of_replicas, the 1,024-shard cap per index, refresh_interval (1 s, 5 s on Serverless), search-idle shards after 30 s, and max_result_window.
    2. Elasticsearch documentation: The refresh parameterelastic.cofalse, wait_for and true; why refresh=true creates tiny segments; the 1,000 refresh listeners per shard before wait_for forces a refresh.
    3. Elasticsearch documentation: Translog settingselastic.corequest durability fsyncs on the primary and every allocated replica before acknowledging; async every 5 s; 10 GB flush threshold.
    4. Elasticsearch documentation: Reading and writing documentselastic.coPrimary-backup replication to in-sync copies, two copies by default, dirty reads, and search returning partial results with 200 OK.
    5. Elasticsearch documentation: Optimistic concurrency controlelastic.coSequence numbers and primary terms per operation, and conditional writes with if_seq_no and if_primary_term.
    6. Elasticsearch documentation: Size your shardselastic.co10–50 GB and under 200 million documents per shard, one thread per shard per search, 1,000 non-frozen shards per node, 1 GB of master heap per 3,000 indices, and the 2,147,483,519-document Lucene limit.
    7. Elasticsearch documentation: Nested field typeelastic.coHow object arrays are flattened and cross-match, nested objects as hidden documents, and the 100-mapping and 10,000-object limits.
    8. Elasticsearch documentation: Suggesters (completion suggester)elastic.coThe completion field, weights, fuzzy prefixes, category and geo contexts, the heap-resident structure and the single-shard advice.
    9. Elasticsearch documentation: Geo-distance queryelastic.coRadius filters on geo_point and geo_shape fields, accepted point formats, and arc versus plane distance.
    10. Elastic: Licensing FAQelastic.coThe 2021 move to SSPL and the Elastic License 2.0, and AGPLv3 added as a third option in 2024.