SQL LIKE vs Elasticsearch/OpenSearch¶
1. Decision¶
Choose SQL LIKE / ILIKE (or Postgres tsvector) for simple exact substring matching, low query volume, or small relational datasets where adding search infrastructure is unnecessary; choose Elasticsearch / OpenSearch for multi-field full-text search, fuzzy search, relevance scoring (BM25), faceted search filtering, and petabyte-scale real-time log analytics.
2. 30-Second Answer¶
SQL LIKE '%keyword%' executes an un-indexed full-table scan, reading every row tuple sequentially from disk (\(O(N)\) query complexity). Even leading-wildcard indexes fail B-Tree lookups, leading to severe disk I/O thrashing and database crashes under concurrent search traffic. Elasticsearch and OpenSearch are distributed, Lucene-based search engines utilizing Inverted Indexes. An inverted index maps every unique word to its containing document IDs (\(O(1)\) dictionary lookup). They provide advanced text analysis (stemming, tokenization, synonym expansion), BM25 relevance scoring, fuzzy typo tolerance, and sub-second faceted aggregations over billions of documents, but require asynchronous Change Data Capture (CDC) synchronization from the primary database.
3. Decision Matrix¶
| Option | Best For | Avoid When | Latency | Scalability | Consistency | Operational Complexity |
|---|---|---|---|---|---|---|
SQL LIKE / ILIKE |
Simple exact substring matches (WHERE email LIKE '%@gmail.com'), small tables (<50k rows), zero infrastructure overhead |
Full-text search, multi-field search, fuzzy matching, high-concurrency search traffic | High (\(O(N)\) full table scan; seconds over large tables) | Extremely Poor (Degrades exponentially with row count and concurrent requests) | Immediate ACID consistency | Low |
| Elasticsearch / OpenSearch | E-commerce product search, fuzzy typo matching, autocomplete, faceted filtering, log analytics | Simple exact key lookups, transactional system of record data storage | Ultra-Low (<10ms inverted index lookup) | Extreme Horizontal Scale (Distributed Shards & Replica Nodes) | Near Real-Time (NRT ~1s segment refresh interval); Eventual | High (JVM tuning, shard management, CDC pipelines) |
4. Selection Rules¶
| Requirement | Best Choice | Rationale / Example |
|---|---|---|
| Simple Exact Prefix Match on Small Table | SQL LIKE 'admin%' |
B-Tree index handles trailing wildcards ('admin%') cleanly via index range scan |
| Complex Full-Text Search Across 10 Fields | Elasticsearch / OpenSearch | Search title, description, tags, and reviews in 1 request with BM25 relevance scoring |
| Fuzzy Search / Typo Tolerance ("iphone" \(\rightarrow\) "ifone") | Elasticsearch / OpenSearch | Levenshtein Edit Distance algorithms handle typos natively in Lucene analyzers |
| Zero Additional Infrastructure Overhead | Postgres tsvector + GIN |
Native inverted index inside PostgreSQL handles full-text without external engines |
| Sub-Second Faceted Aggregation Filters | Elasticsearch / OpenSearch | Lucene doc_values compute multi-dimensional faceted counts (brand, price, size) in ms |
5. Tradeoffs¶
- Full Table Disk Scan vs Inverted Index Lookups
- Gain (Search Engine): \(O(1)\) dictionary lookup instantly returns document IDs.
- Lose (Search Engine): High RAM consumption (JVM heap) storing inverted indexes and FST dictionaries.
- Transactional Integrity vs Dual-Write Synchronization Lag
- Gain (SQL): Immediate visibility within single ACID database transactions.
- Lose (SQL): Heavy
LIKEqueries starve OLTP CPU/RAM connection pools. - Advanced Relevance Scoring (BM25) vs Boolean Matches
- Gain (Search Engine): Ranks results intelligently based on term frequency, inverse document frequency, and field boosts.
- Lose (Search Engine): Requires tuning custom analyzers, tokenizers, and field weights.
- Near Real-Time (NRT) Refresh vs Instant Commit
- Gain (Search Engine): Decouples heavy search workloads away from primary relational databases.
- Lose (Search Engine): Default 1-second
refresh_intervalmeans inserted items have a 1s index visibility delay. - Operational Maintenance Complexity
- Gain (SQL): Single database engine to maintain.
- Lose (SQL): Cannot scale search throughput horizontally independently from the primary DB.
6. Interview Counter Questions¶
- Why does SQL
LIKE '%keyword%'bypass B-Tree indexes whileLIKE 'keyword%'can use them? - How does an Inverted Index in Apache Lucene work under the hood?
- How do you synchronize data between PostgreSQL and Elasticsearch without dual-writing in application code?
- What is the difference between
tsvector+ GIN index in PostgreSQL versus Elasticsearch? - How does the Levenshtein Edit Distance algorithm implement Fuzzy Search in Lucene?
- Why is
doc_valuescritical for sorting and aggregations in Elasticsearch? - What is Near Real-Time (NRT) in Elasticsearch and what happens during a segment
refresh_interval? - How does BM25 scoring rank search results better than simple term counts?
- When would you choose PostgreSQL
pgvectorhybrid search over Elasticsearch? - How do you handle schema changes (Mapping updates) in Elasticsearch without downtime?
7. Gold Standard Answers¶
- B-Tree indexes sort text from left-to-right. A trailing wildcard (
'keyword%') performs a B-Tree range scan. A leading wildcard ('%keyword%') obscures the prefix, forcing a full table scan of all disk pages. - An Inverted Index extracts words (terms) from documents via an Analyzer pipeline and maps each term to a Posting List containing document IDs, term frequencies, and byte offsets.
- Use Change Data Capture (CDC via Debezium reading Postgres WAL) streaming to Kafka, consumed by an indexing worker. Avoid dual-writing in app code due to partial failure risks.
- Postgres
tsvector+ GIN provides native in-database full-text search. Elasticsearch adds distributed auto-sharding, BM25 scoring, fuzzy matching, autocomplete FSTs, and faceted aggregations at petabyte scale. - Levenshtein Distance calculates the minimum single-character edits (insertions, deletions, substitutions) to transform word A to word B. Lucene uses Automata over FSTs for fuzzy matching up to distance 2.
- Inverted indexes map terms to documents (fast search, slow sorting).
doc_valuesis an on-disk columnar structure mapping documents to terms, allowing fast, memory-efficient sorting and aggregations. - Documents written to memory buffer are periodically flushed to OS page cache as new Lucene segments every 1s (
refresh_interval). Data becomes searchable (NRT) only after segment refresh. - BM25 incorporates Term Frequency saturation (prevents repeated keyword spam from artificially boosting rank) and Document Length Normalization (penalizes long documents with accidental hits).
- Use
pgvectorfor small-to-medium vector datasets (<10M vectors) requiring joint relational SQL queries (WHERE tenant_id = X AND cosine_distance < Y) inside single ACID transactions. - Create a new index with updated mappings (
products_v2), run the_reindexAPI in the background, and switch a zero-downtime Index Alias (products_live) fromv1tov2.
8. Common Mistakes¶
- Running
LIKE '%keyword%'queries on multi-million row production SQL databases under heavy concurrent traffic. - Dual-writing directly to Postgres and Elasticsearch inside HTTP handlers without CDC or outbox patterns.
- Failing to use Index Aliases in Elasticsearch, forcing application code updates during index mapping reindexing.
- Allocating more than 32GB JVM heap to Elasticsearch, disabling Compressed OOPs pointers and wasting RAM.
- Expecting instant sub-millisecond visibility of newly inserted records in Elasticsearch without accounting for the 1s
refresh_interval.
9. Keywords¶
SQL LIKE, ILIKE, Elasticsearch, OpenSearch, Inverted Index, Posting List, BM25, TF-IDF, Levenshtein Distance, Fuzzy Search, doc_values, Near Real-Time (NRT), refresh_interval, Debezium CDC, Index Alias, Reindex.
10. Cheat Sheet¶
- Core Rule: Simple substring / prefix (
'admin%') \(\rightarrow\) SQL; Multi-field full-text, fuzzy, faceted search \(\rightarrow\) Elasticsearch / OpenSearch. - Index Engine: SQL B-Tree (Row-oriented); Elasticsearch Inverted Index + Columnar
doc_values. - Leading Wildcard Penalty:
LIKE '%keyword%'forces an \(O(N)\) full table scan, thrashing DB disk I/O. - Sync Strategy: PostgreSQL WAL \(\rightarrow\) Debezium CDC \(\rightarrow\) Kafka \(\rightarrow\) Elasticsearch Indexer.
- Zero-Downtime Reindexing: Always point app code to an Index Alias (
products_live), never raw index names. - 32GB Heap Limit: Never allocate \(>32\text{GB}\) JVM heap to Elasticsearch to preserve Compressed OOPs pointers.