Skip to main content

Full-Text Search & the Inverted Index

Level 3: Level 3: Scaling the Data Tierhard30 minsearchinverted-indexelasticsearch

A dedicated search tier: inverted index plus analysis pipeline, BM25 with boosting, cached filters, shards and replicas, CDC-fed eventual consistency, search_after pagination.

Why LIKE cannot power search

A relational WHERE description LIKE '%wireless headphone%' cannot power real search: it does a full scan, cannot rank by relevance, cannot handle typos or word stems, and dies at scale. That is why a dedicated search tier exists. Its core data structure is the inverted index: instead of mapping a document to its words, it maps each word (term) to a posting list of the documents that contain it. Query "wireless headphones" and the engine intersects the posting lists for wireless and headphone in milliseconds, no scan required.

Terms do not go into the index raw; they pass through an analysis pipeline. Tokenize the text into words, lowercase them, stem ("running", "ran", "runs" all collapse to "run"), drop stopwords, and expand synonyms ("tv" also indexes as "television"). The same analyzer must run at index time and query time so the terms match. Typo tolerance comes from fuzzy matching (edit distance) or n-gram indexing, so "hedphones" still finds "headphones".

From documents to a queryable inverted index
Step 1 / 3

Stage 1 of 3: Terms never enter the index raw: the analysis pipeline tokenizes, lowercases, stems ('running' collapses to 'run'), drops stopwords, and expands synonyms.

The build path (docs -> analyzer -> posting lists) and the query path meet at the inverted index; both sides must share one analyzer.

Ranking, queries, and filters

The default ranking is BM25 (a refined TF-IDF): a term matters more when it is rare across the corpus (high IDF) and appears often in a short document. On top you apply boosting (title matches worth more than description, in-stock and popular items lifted) and filters. Crucial distinction: a query contributes to the relevance score; a filter is a yes/no constraint (brand = Sony, price < 100) that does not score and, because it is deterministic, is cached as a bitset and reused cheaply across requests. Facets and highlighting come from the same index.

At scale you run Elasticsearch or OpenSearch, which shards the index. A shard is a self-contained inverted index (a Lucene index); documents are routed to a primary shard by hash of the id, and each primary has replica shards for read throughput and failover. A 50M-document catalog might use 10 primaries; size shards to the tens-of-GB range because oversharding wastes memory.

Keeping the index in sync

Search is not a system of record. The truth lives in your primary DB; the index is a derived, rebuildable store. Feed it with a CDC / indexing pipeline: capture DB changes (Debezium on the binlog, or an application event) onto a stream, and an indexer applies them to Elasticsearch. This is eventually consistent, so a product edit shows in search a second or two later, which is fine. Because it is derivable, plan for full reindexing: mapping changes require building a fresh index and switching an alias over atomically, with zero downtime.

Check yourself
A crawler requests results 100,000 through 100,010 using a large 'from' offset. Compared to page one, what does this cost the cluster?

Interview nuance: the classic trap is deep pagination. from: 100000, size: 10 forces every shard to sort 100,010 docs and is O(offset). Use search_after (a cursor on the last sort value) for deep result sets, and cap the max page. Also be ready to say why you would not make Elasticsearch your primary DB: weaker durability and consistency guarantees, and no transactions.

Recap: search runs on a dedicated tier built on an inverted index plus an analysis pipeline, ranks with BM25 and boosting, separates scoring queries from cached filters, shards across primaries and replicas, stays in sync as an eventually-consistent derived store fed by CDC, and paginates deep sets with search_after, never large from offsets.

Check yourself

Final gut check before you design the search tier: sort each decision.

Feed the index by CDC from the primary DB and accept a second or two of lag
Make Elasticsearch the system of record to remove a moving part
Express brand and price constraints as filters, not queries
Serve page 10,000 with a large 'from' offset
Run one analyzer at index time and a different one at query time
Plan for full reindexing with an atomic alias switch

Apply

Your turn

The task this lesson builds to.

Design search for an e-commerce catalog of 50M products with typo tolerance, filters, and relevance-ranked results, including how the index stays in sync with the product database.

Think about

  1. What is the analysis pipeline (tokenize, stem, synonyms) and inverted index?
  2. How do you keep the index in sync with the DB?
  3. Why is search not a system of record?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design log and event search for an observability platform like Datadog or Elastic Observability ingesting 2M log lines per second across thousands of customers, where engineers run ad-hoc keyword and field queries over the last 15 minutes constantly and over the last 30 days occasionally. Lead with the index layout that makes recent data fast and old data cheap.

Think about

  1. What does time-based index rollover buy for both queries and retention?
  2. How do hot-warm-cold tiers match the access pattern to hardware cost?
  3. Where does multi-tenancy shape routing and shard placement?