Anatomy of a search

The Elasticsearch essays · August 2026

Somewhere behind almost every search box you have ever typed into, a curious machine is at work. It takes in text, chops it into pieces, files every piece away in several filing systems at once, and then answers questions in milliseconds, best answers first. This essay follows one document into Elasticsearch and one query back out. Every figure is interactive; the essay works best if you play with all of them.

1 · A document arrives

Everything begins with a document: a piece of JSON. Here is a log line from an imaginary payment service. It looks like one thing, but Elasticsearch will not store it as one thing. Guided by a schema called the mapping, each field's value is copied into one or more specialized structures, each built to answer a different kind of question. Hover over the fields:

{
  "@timestamp": "2026-08-25T03:47:12Z",
  "message": "payment timeout after 3 retries",
  "service": "payment-api",
  "duration_ms": 4823,
  "message_vector": [0.13, -0.48, …],
  "client_ip": "10.2.14.8"
}
Inverted index
every token → the documents containing it; powers full-text search
Doc values
each field as an on-disk column; powers sorting and aggregations
BKD tree
numbers, dates and IPs arranged for fast range queries
HNSW graph
vectors linked to their neighbors, for search by similarity
_source
the original JSON, stored whole, returned in results
Hover a field to see where its value goes; hover a structure to see which fields feed it.
A text field is analyzed into the inverted index; a keyword like service is indexed whole and also columnar; numbers, dates and IPs get range trees; vectors join the neighbor graph.

This is the first big idea, and it explains half of everything downstream: Elasticsearch pays generously at write time to make read time almost free. One incoming document might be written five different ways. That sounds wasteful until you remember the asymmetry of the workload: a log line is written once and queried, aggregated, sorted, and dashboarded thousands of times.

2 · From text to tokens

Let's follow the message field, mapped as text. Computers are poor readers; to a computer, payment, Payment, and payments are three unrelated byte sequences. So before text is indexed it passes through analysis, an assembly line that normalizes raw text into tokens. Edit the sentence:

1 · split into words
2 · lowercase
3 · drop stop words
4 · stem to root form
Edit the sentence. Struck-out tokens are discarded; green tokens were changed by a stage.

The stages are humble: split on punctuation, lowercase, discard glue words, and trim words to a root so payments, payment and paying collapse together. Crucially, queries pass through the same pipeline, so document and query meet in the middle, in normalized token space. Elasticsearch ships dozens of analyzers, for English, Japanese, file paths, email addresses, and lets you compose your own from tokenizers and filters, including synonym filters that make error and failure meet in the middle too.

3 · The inverted index

Tokens now need to be stored so they can be found without reading everything. Elasticsearch borrows the oldest trick in publishing, the index at the back of a book: precompute, for every token, the list of documents that contain it, called a postings list. Below, four documents and the real index built from them. Try the search box, and hover the index rows:

Inverted index
Type a query: its tokens are looked up and the posting lists intersected. Try payment timeout, then checkout error, then Payments.

Watch what the query actually does: it never reads a document. It looks up tokens, walks posting lists, intersects them. The documents are touched only at the very end, for display. The cost of search was paid when the index was built.

4 · Composing queries: match and filter

Real questions are rarely one word. They look like: messages matching "timeout", from the checkout service, slower than two seconds. Elasticsearch composes such questions with a bool query, and it draws a distinction that shapes everything about how the machine spends its effort: some clauses score, and some merely filter.

A must clause asks "how well does this match?" and contributes to relevance. A filter clause asks a yes/no question, contributes nothing to the score, and, because yes/no answers never change for a given segment, its result is cached as a bitset and reused by every future query that asks it. Toggle the clauses:

mustmatch message:
filterservice =
filterduration_ms ≥ 0
Filters cut documents out entirely (dimmed, with the reason); the match clause ranks what survives. Filters are cached and cost almost nothing on repeat; scoring is the expensive part, so the machine filters first.

This ordering, cheap cached filters first, expensive scoring on the survivors, is the quiet workhorse optimization of nearly every production query. The range filter, incidentally, is answered by the BKD tree from chapter one: numbers organized so that "≥ 2000" is a walk down a tree, not a scan.

5 · Forgiving search: typos and half-typed words

Humans misspell. A search engine that returns nothing for paymnet has failed at its one job, so Elasticsearch can match terms approximately, within a budget of single-character edits, insertions, deletions, substitutions, called edit distance. Type a misspelling:

1
The dictionary of indexed terms, colored by edit distance from your input (green = exact, blue = 1 edit, amber = 2). The engine doesn't compare your word against each term one by one; it compiles your word into a small automaton and walks the term dictionary with it.

The other human habit is not finishing words. Search-as-you-type works by indexing prefixes: at write time the term payment is also indexed as p, pa, pay, and so on, the familiar bargain of paying at write time, so that each keystroke is an ordinary exact lookup:

edge n-grams indexed for "payment"
Type one letter at a time. The lit chips show which stored prefix your keystroke hits; below, the suggestions it unlocks.

6 · Ranking: the best answer first

Matching is half the job; the other half is deciding what comes first. Elasticsearch scores matches with BM25, which encodes three intuitions: repetition matters, but with diminishing returns; rare terms matter more than common ones; and matches in short documents beat matches diluted in long ones. First, the shape of "diminishing returns":

1.2
0.75
1.0
Score contribution of one term as its occurrences grow. Repetition can never push past the k₁ + 1 ceiling, which is why keyword-stuffing doesn't work.

Now the whole formula against a real corpus. Type any query; the five log lines re-rank live, each bar split by term so you can see which word earned the score:

Try payment, then payment timeout, then cache. The rarer word earns the taller bar: rarity is worth more.

Searching is only half the machine. Segments explains how all of this gets written fast, and One machine from many spreads it across computers.