Overview
Search is what databases are bad at: ranked, fuzzy, language-aware matching over text. The engine underneath (Lucene, powering Elasticsearch/OpenSearch/Solr) is built on one data structure — the inverted index — plus an analysis pipeline that decides what a "term" is, and a scoring model that decides what "relevant" means. Operating one is a second copy of your data with its own consistency, mapping, and capacity story; knowing the internals is what keeps that copy useful.
Key points
- Inverted index: term → posting list of documents (plus positions for phrases); queries intersect/union posting lists instead of scanning documents. Immutable segments, merged in the background — a very LSM-shaped design, and the reason updates are really delete + reinsert and search is near-real-time (refresh interval), not transactional.
- Analysis decides everything: tokenization → lowercasing → stemming/lemmatization, stop words, synonyms, n-grams (for autocomplete), language-specific analyzers. Most "search returns garbage" bugs are analyzer mismatches between index time and query time, not scoring problems.
- Relevance: TF-IDF's successor BM25 as the standard lexical score (term frequency, saturating; rarity; length normalization), combined with field boosts and function scores (recency, popularity). Measure with click-through and judged sets — relevance tuning without measurement is superstition.
- Beyond lexical: vector/semantic search (embeddings + ANN indexes like HNSW) catches meaning where BM25 catches words; production systems increasingly run hybrid (BM25 + vector, fused) — also the retrieval layer under RAG (agents & RAG).
- Operating it: the search cluster is a derived store — feed it via CDC or the outbox pattern, plan for full reindex (mappings are mostly immutable; use index aliases for zero-downtime swaps), size shards deliberately (over-sharding is the classic self-inflicted wound), and don't use it as a primary database.
- Query-side vocabulary: filters (cacheable, unscored) vs. queries (scored), aggregations/facets, pagination pitfalls (deep offset is O(n) — use search-after), highlighting.
- To explore: Postgres full-text search as the "good enough" tier (tsvector/GIN), typo-tolerant engines (Typesense, Meilisearch), learning-to-rank, RRF for hybrid fusion.
Practice
- Analyzer archaeology (source) —
feed the
_analyzeAPI a handful of real queries and watch tokenization, stemming, and n-grams reshape them; most "search returns garbage" bugs become visible right here. - Full-text search engine from scratch (source) — inverted index over a real corpus (Wikipedia abstracts work well): tokenize, build posting lists, intersect them for queries, then add BM25 ranking and n-gram autocomplete.
- Hybrid retrieval (source) — bolt vector search onto the lexical engine: embed the corpus, build an HNSW index, fuse BM25 + ANN results, and measure whether hybrid actually wins on your queries.
Related
- Databases and other storage systems — the primary store this system derives from; LSM kinship.
- Asynchronous and distributed system patterns — CDC/outbox as the feeding mechanism.
- Agents, RAG & tool use — retrieval as the R in RAG.
- Cache management — filter caches and why they matter.