Elasticsearch vs PostgreSQL Search¶
1. Decision¶
Choose PostgreSQL Search (B-Tree, GIN, tsvector, pgvector) for simple full-text search, exact filtering, or vector similarity search when query volume is moderate and data already resides in PostgreSQL; choose Elasticsearch (or OpenSearch) when complex distributed full-text search, multi-field relevance scoring (BM25), autocomplete, aggregation analytics, or massive log search at petabyte scale are required.
2. 30-Second Answer¶
PostgreSQL provides native text search via tsvector and tsquery data types indexed with GIN (Generalized Inverted Index), alongside vector similarity search using pgvector (HNSW/IVFFlat). It avoids dual-write consistency issues and architectural overhead by keeping search strictly within ACID relational boundaries. Elasticsearch is a distributed, JSON-based search engine built on Apache Lucene, utilizing inverted indexes, BM25 relevance ranking, finite state transducers (FST) for autocomplete, and distributed shard sharding. Elasticsearch scales horizontally across dozens of nodes for high-throughput search and aggregations, but requires Change Data Capture (CDC) synchronization pipelines from the primary database.
3. Decision Matrix¶
| Option | Best For | Avoid When | Latency | Scalability | Consistency | Operational Complexity |
|---|---|---|---|---|---|---|
| PostgreSQL Search | E-commerce with simple search, exact text matching, zero architectural footprint addition | Complex search relevance scoring, fuzzy multi-language stemming, petabyte log analytics | Low (<10ms) | Scale-up; Read Replicas | Immediate ACID consistency | Low |
| Elasticsearch | Complex product search, fuzzy matching, faceted filtering, real-time log analytics | Simple exact lookup queries, primary transactional data storage | Very Low (<5ms point search) | Horizontal linear scale-out (Primary & Replica Shards) | Near Real-Time (NRT ~1s refresh interval); Eventual | High (JVM heap tuning, shard split management) |
4. Selection Rules¶
| Requirement | Best Choice | Rationale / Example |
|---|---|---|
| Zero Additional Infrastructure Overhead | PostgreSQL Search | Leverage existing Postgres DB using native tsvector + GIN indexing |
| Advanced Relevance Scoring (BM25 / TF-IDF) | Elasticsearch | Tunable BM25 scoring across custom field boosts, language stemmers, and decay functions |
| High Performance Faceted Aggregations | Elasticsearch | Lucene doc-values provide sub-second multi-dimensional aggregation queries |
| Strict Zero Data Stale Read Guarantee | PostgreSQL Search | Immediate visibility within single transaction without refresh delay |
| Fuzzy Matching / Typo Tolerance / Autocomplete | Elasticsearch | Edge-ngram and Finite State Transducers (FST) handle sub-string completion |
| Large-Scale Log Analytics & Telemetry | Elasticsearch | Logstash/Fluentd ingestion directly into daily time-partitioned indices |
5. Tradeoffs¶
- Transactional Simplicity vs Distributed Search Performance
- Gain (Postgres): No data sync lag, no split-brain search indexes, single system to back up.
- Lose (Postgres): CPU-intensive text matching degrades core transactional OLTP database performance.
- Apache Lucene Inverted Index vs PostgreSQL GIN Index
- Gain (Elasticsearch): Highly optimized per-segment inverted indexes with doc-values for fast sorting/aggregations.
- Lose (Elasticsearch): Significant memory footprint (JVM heap) and index build CPU overhead.
- Near Real-Time (NRT) vs Immediate ACID Visibility
- Gain (Elasticsearch): Decoupled search workload scales independently from database writes.
- Lose (Elasticsearch): Default 1-second segment
refresh_intervalmeans newly inserted data isn't immediately searchable. - Fuzzy Search Capabilities vs Basic Stemming
- Gain (Elasticsearch): Advanced analyzer pipelines (CharFilters, Tokenizers, TokenFilters, Phonetics).
- Lose (Elasticsearch): Complex index mapping configuration management.
- Operational Overhead
- Gain (Postgres): Single operational database engine.
- Lose (Postgres): Poor horizontal scalability for multi-terabyte search indexes.
6. Interview Counter Questions¶
- Why shouldn't you use Elasticsearch as your primary transactional database?
- How does an Inverted Index work in Apache Lucene under the hood?
- How do you keep PostgreSQL primary data synchronized with Elasticsearch in real-time?
- What is the difference between
matchquery andtermquery in Elasticsearch? - How does Elasticsearch handle Autocomplete efficiently (Edge-Ngram vs Completion Suggester FST)?
- What causes Elasticsearch JVM Garbage Collection pauses and split-brain scenarios?
- How does BM25 scoring algorithm improve upon traditional TF-IDF?
- What is the role of Lucene
doc_valuesin Elasticsearch sorting and aggregations? - When would you choose PostgreSQL
pgvectorover Elasticsearch for hybrid search? - How do you size and plan shard allocation for Elasticsearch indices to avoid over-sharding?
7. Gold Standard Answers¶
- Elasticsearch lacks strict ACID transactions, foreign keys, multi-document locking, and rollback capabilities. Writing directly to Elasticsearch risks data corruption during split-brain partitions, and data is only searchable after segment refresh intervals.
- An inverted index maps every unique word (term) to a posting list containing document IDs where the term appears, along with term frequency and positional offsets, enabling sub-millisecond full-text lookups (\(O(1)\) dictionary lookup).
- Use Change Data Capture (CDC via Debezium reading Postgres WAL) to stream change events to Kafka, consumed by an indexing worker that updates Elasticsearch asynchronously. Avoid dual-writing directly in application HTTP endpoints.
termquery looks for exact binary matches in the inverted index without analyzing the search term.matchquery passes the input string through the field's analyzer pipeline (lowercasing, stemming, tokenization) before searching.- Edge-Ngram tokenizes words into prefix fragments at index time (
"phone"\(\rightarrow\)"p","ph","pho"), allowing standard inverted index matching. Completion Suggester uses in-memory Finite State Transducers (FST) for ultra-fast, microsecond prefix matching. - Large aggregations or heap un-mapping fill the 32GB JVM heap limit, triggering Stop-The-World (STW) GC pauses. If node heartbeats fail during STW GC, master election misinterprets the pause as node death, causing cluster instability.
- BM25 incorporates term frequency saturation (preventing documents with 100 repeated keywords from unfairly dominating scores) and document length normalization (penalizing long documents containing accidental hits).
- Inverted indexes map terms to documents (fast search, slow sorting).
doc_valuesis an on-disk, column-oriented data structure built at index time that maps documents to terms, allowing fast, memory-efficient sorting and aggregations. - Choose
pgvectorwhen embedding dimensions fit in memory, dataset size is under ~10M vectors, and queries require strict joint filtering with relational SQL tables (WHERE tenant_id = X AND cosine_distance < Y). - Aim for primary shard sizes between 20GB and 50GB. Rule of thumb: keep total count of shards on a node below 20 per GB of allocated JVM heap. Over-sharding wastes heap memory on Lucene segment metadata.
8. Common Mistakes¶
- Writing directly to PostgreSQL and Elasticsearch inside an HTTP handler, creating inconsistent index state when Elasticsearch writes fail.
- Over-sharding Elasticsearch by creating dozens of shards for tiny indices (<1GB), exhausting JVM heap memory.
- Using PostgreSQL
LIKE '%keyword%'queries in production, causing full table scans and disk I/O thrashing. - Allocating more than 32GB JVM heap to Elasticsearch, disabling Compressed Ordinary Object Pointers (Compressed OOPs) and wasting RAM.
- Treating Elasticsearch as a primary data store without periodic snapshot backups to S3.
9. Keywords¶
Elasticsearch, PostgreSQL, Inverted Index, GIN Index, tsvector, Lucene, BM25, TF-IDF, doc_values, FST, Edge-Ngram, Completion Suggester, Debezium CDC, Near Real-Time (NRT), 32GB JVM Heap limit.
10. Cheat Sheet¶
- Core Difference: Postgres Search = Relational exact/text search in ACID boundary; Elasticsearch = Distributed Lucene inverted-index search engine.
- Index Engine: Postgres = GIN Index / B-Tree; Elasticsearch = Lucene Inverted Index + Columnar
doc_values. - Consistency: Postgres = Immediate ACID visibility; Elasticsearch = Near Real-Time (NRT, 1s refresh interval).
- Data Sync: Use CDC (Debezium + Kafka) to stream Postgres WAL changes to Elasticsearch. Never dual-write in application code.
- Autocomplete: Use Edge-Ngram tokenization or Completion Suggester (FST) in Elasticsearch.
- Heap Rule: Never set Elasticsearch JVM heap size above 32GB to maintain Compressed OOPs pointers.