Full-Text Search in PostgreSQL vs. Reaching for Elasticsearch: A Pragmatic Decision Guide
Learn when PostgreSQL's native full-text search suffices and when Elasticsearch becomes necessary. A senior engineer's pragmatic guide to avoiding premature infrastructure complexity.
Full-Text Search in PostgreSQL vs. Reaching for Elasticsearch: A Pragmatic Decision Guide
You don't need Elasticsearch. Not yet. That's the honest starting point I give engineers with search problems, because the reflex to reach for a dedicated search cluster is almost always premature—and it carries real operational weight.
Let's work through the decision properly.
What PostgreSQL Full-Text Search Actually Gives You
PostgreSQL's full-text search is genuinely capable. Most engineers underestimate it. The core machinery rests on two types: tsvector (a sorted list of lexemes) and tsquery (a structured search query). Combine them with the @@ match operator and you get ranked, stemmed, stopword-aware search without leaving your stack.
A basic example:
Keep search_vector fresh with a trigger:
Add a GIN index:
This setup works for any product under a few million rows with straightforward search requirements. One migration, zero new infrastructure, and search results live within your transaction boundary.
Where PostgreSQL Full-Text Search Strains
Real limits exist. Knowing them separates good architecture decisions from cargo-culted ones.
Fuzzy matching and typo tolerance are painful in vanilla PostgreSQL. The pg_trgm extension provides trigram similarity for basic typos, but it's not the relevance tuning class of Elasticsearch's BM25 scoring.
Multi-language stemming at scale gets complicated. PostgreSQL supports configurable text search configurations per language, but managing dozens of locales is friction.
Faceting and aggregations (filtering by category, date range, price band simultaneously, then counting hits per bucket) push PostgreSQL into ugly query planning territory.
Write-heavy indexing is where GIN index overhead becomes measurable. Under heavy concurrent writes, you'll tune gin_pending_list_limit or switch to partial index strategies—now something you're managing.
Cross-field relevance tuning with custom boosts, synonyms, and stopwords is possible but significantly less ergonomic than Elasticsearch's mapping DSL.
When Elasticsearch Earns Its Place
Elasticsearch is right when search requirements are themselves a product feature, not just a utility:
- Autocomplete with sub-100ms latency over large vocabularies (Elasticsearch's completion suggester is built for this)
- Log/event analytics at volume—Elasticsearch's home territory
- Relevance tuning based on user signals fed back into ranking
- Dedicated ops capacity for heap tuning, shard management, snapshot policies, and index lifecycle
That last point is non-negotiable. If no one on your team has run Elasticsearch, you're adopting a new on-call rotation.
The Decision Framework
| Signal | PostgreSQL FTS | Elasticsearch |
|---|---|---|
| Rows in primary table | < 5–10M | > 10M with growth |
| Search is core feature | No | Yes |
| Typo tolerance required | Trigrams sufficient | Yes |
| Faceting + aggregations | Simple cases | Complex, high-volume |
| Ops capacity | Minimal | Dedicated |
| Already on AWS | RDS works | OpenSearch Service available |
AWS users: Amazon OpenSearch Service reduces operational burden but doesn't eliminate it. You still own index mappings and query logic.
Default Recommendation
Start with PostgreSQL full-text search. Build the tsvector column, GIN index, and trigger. Ship it. Instrument query times for three months.
If you hit genuine pain—not theoretical pain—then evaluate Elasticsearch with concrete requirements. You'll make a much better decision because you'll know exactly what operational complexity you're trading for.
The engineers who regret early Elasticsearch adoption rarely regret waiting.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.