DH
4 min read

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:

SELECT title, ts_rank(search_vector, query) AS rank
FROM articles, to_tsquery('english', 'postgres & search') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;

Keep search_vector fresh with a trigger:

CREATE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('english', coalesce(NEW.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(NEW.body, '')), 'B');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER articles_search_vector_update
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW EXECUTE FUNCTION update_search_vector();

Add a GIN index:

CREATE INDEX articles_search_idx ON articles USING GIN(search_vector);

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

SignalPostgreSQL FTSElasticsearch
Rows in primary table< 5–10M> 10M with growth
Search is core featureNoYes
Typo tolerance requiredTrigrams sufficientYes
Faceting + aggregationsSimple casesComplex, high-volume
Ops capacityMinimalDedicated
Already on AWSRDS worksOpenSearch 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

Damian Hodgkiss

Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.

Creating Freedom

Join me on the journey from engineer to solopreneur. Learn how to build profitable SaaS products while keeping your technical edge.

    Proven strategies

    Learn the counterintuitive ways to find and validate SaaS ideas

    Technical insights

    From choosing tech stacks to building your MVP efficiently

    Founder mindset

    Transform from engineer to entrepreneur with practical steps