Originally published at kunalganglani.com — read it there for inline code, hero image, and live links.
Postgres full text search vs Elasticsearch is rarely a philosophical debate. It’s an engineering trade. You’re deciding how much operational complexity you’re willing to buy in exchange for better relevance tooling and cleaner horizontal scale.
My take in 2026 is simple: start with PostgreSQL full‑text search if search is a feature inside a transactional product. Bring in Elasticsearch only when you can prove, with a relevance eval and a load test, that you need advanced analyzers, aggressive freshness under write load, or independent horizontal scaling.
Key takeaways
- If you can’t measure relevance (NDCG/MRR) and p95 latency, you’re not choosing a search engine. You’re choosing a religion.
- Postgres FTS wins by deleting a moving part. Elasticsearch wins by being built for search-first workloads.
- Indexing cost matters as much as query speed. GIN is light and fast to build. RUM can make ranking faster but writes heavier.
- Elasticsearch freshness is “near real-time” (typically ~1s refresh), not transactional. That’s either fine or a deal-breaker.
- The simplest hybrid architecture in 2026 is still lexical in your primary store + vectors where you need them, not “everything everywhere”.
What is Postgres full-text search vs Elasticsearch?
Postgres full text search vs Elasticsearch is the decision between using PostgreSQL’s built-in inverted-index search (via tsvector/tsquery plus indexes like GIN/RUM) versus running a separate distributed search engine (Elasticsearch, built on Lucene) optimized for BM25 relevance, analyzers, and horizontal scaling.
Postgres FTS vs Elasticsearch: the 2026 decision table
If Google only pulled one thing from this post, I’d want it to be this table.
[YOUTUBE:XEiQV4zRC-U|Postgres Just Killed Elasticsearch]
| Dimension | PostgreSQL full-text search | Elasticsearch |
|---|---|---|
| Best fit | Product search inside an OLTP app | Search-first systems (logs, observability, large catalogs) |
| Relevance baseline |
ts_rank / ts_rank_cd over lexemes |
BM25 by default |
| Freshness model | Transactional updates are immediately visible | Near real-time; default refresh ~1s (and only on “recently searched” indices) |
| Typical small/med scale latency | p50 single-digit ms to low 10s ms on warm cache | p50 low 10s ms; p95 depends heavily on shards/merges |
| Scaling | Vertical + read replicas; partitioning helps | Horizontal by design (shards/replicas) |
| Index choices | GIN (common), GiST (niche), RUM (heavier but can speed ranking/highlight) | Inverted index + segment merges; analyzer-driven |
| Fuzzy / typo tolerance |
pg_trgm is great for names/typos |
Fuzziness, ngrams, analyzers; very flexible |
| Ops overhead | Low if you already run Postgres | Higher: cluster, shards, upgrades, mappings, ILM, DR |
| Cost drivers | CPU + RAM for Postgres; index bloat/vacuum | Data nodes + replicas + heap; shard overhead; storage for segments |
| Hybrid lexical+vector | Easy: add pgvector, keep one DB |
Common: BM25 + vectors + reranking in one stack |
A practical rule: if you expect to have meetings about analyzers, token filters, scoring profiles, or multi-tenant shard strategies, you’re already in Elasticsearch territory.
When is PostgreSQL full-text search sufficient (and when is Elasticsearch clearly better)?
Most teams asking “can we drop Elasticsearch?” are not actually running a search company. They’re running a product that happens to have search.
Here’s where I draw the line.
PostgreSQL FTS is sufficient when
- Search lives on the same rows you already transact on. Tickets, notes, documents, help center articles.
- You need correctness and simple freshness. A Postgres update is visible immediately in the same transaction boundary.
- Your search UX is basic. Prefix, phrase-ish matching, field weighting, filters, sorting.
- You’re under ~10–50 million docs and moderate QPS, and you can scale Postgres vertically or with read replicas.
If this describes you, running a second datastore is usually self-inflicted pain.
Elasticsearch is clearly better when
- You are a logs/metrics/events product, or you’re ingesting like one.
- You need serious relevance tooling. Multi-field analyzers, per-field similarity configs, synonym graphs, language-specific pipelines.
- You need independent horizontal scaling without tying search capacity to your primary DB.
- You do heavy highlighting, aggregations, faceting, and you want it to stay fast under load.
- Write throughput is high and you can tolerate near real-time visibility instead of transactional visibility.
A non-obvious case: “search for logs” isn’t a cute add-on feature. It’s the whole reason Elasticsearch exists.
How do Postgres ranking and Elasticsearch BM25 differ in practice?
Elasticsearch’s default similarity is BM25. Elastic documents it explicitly: BM25 is the default, with parameters like k1 (default 1.2) and b (default 0.75) you can tune per field.
Postgres full-text search is a different mental model. The primitives matter more than the math: you build a tsvector (the “document”), parse a tsquery (the “query”), and score with functions like ts_rank and ts_rank_cd.
Here’s what actually changes in practice.
1) Tokenization and normalization are where relevance is won
In Postgres, “relevance tuning” usually means:
- picking the right text search configuration (language, dictionaries)
- controlling what goes into your
tsvector - weighting fields (title vs body)
In Elasticsearch, “relevance tuning” often means all that plus:
- analyzer selection per field
- synonyms and synonym_graph filters
- multi-fields (e.g.,
titleanalyzed,title.keywordexact)
The boring answer is the right one. You can get good relevance in both. Elasticsearch just makes iteration feel like a first-class workflow instead of a set of conventions you have to enforce yourself.
2) Postgres ranking cost shows up earlier than people expect
I’ve watched teams get “fast enough” search in Postgres, and then accidentally blow it up by bolting on ranking, highlighting, and fancy snippets without thinking about cost. That’s usually the moment RUM enters the chat.
RUM is an alternative access method for inverted indexes that stores additional info in posting lists. It can speed up ranking/highlighting workloads versus GIN, but you pay for it on writes.
3) Length normalization differences will surprise you
BM25’s length normalization behaves differently on short vs long fields. Postgres’s ranking functions have different knobs, and most teams never touch them. If you have short titles and long bodies, you’ll see different top-k results under the same query corpus.
If you’re arguing about this in Slack, you’re doing it wrong. Run a relevance eval.
Latency in the real world: p50/p95 expectations (small to medium scale)
I’m going to be annoyingly specific here. Not because these numbers are universal. Because you need a baseline mental model before you run your own tests.
PostgreSQL FTS latency model
On warm cache with a decent GIN index, it’s common to see p50 in single-digit milliseconds for simple queries and filters. p95 tends to be driven by:
- ranking complexity (
ts_rank_cdoften costs more) - highlight generation
- table bloat and cache misses
- concurrency: you’re sharing CPU, IO, and locks with OLTP
You can also get nasty p99 cliffs if your Postgres host is vacuuming, doing large writes, or hitting checkpoints at the wrong time. This is why I wrote about PostgreSQL performance cliffs.
Elasticsearch latency model
Elasticsearch can be extremely fast, but the tail is where the tax shows up:
- shard fan-out (every query is “query many shards, then merge”)
- heap pressure and GC
- background merges
- refresh behavior
Elastic’s own docs describe the near real-time model: documents become searchable within ~1 second after a refresh by default.
If your UX needs “type something, hit save, immediately find it in search,” Postgres has an advantage. If your UX can tolerate “it shows up in a second,” Elasticsearch is fine and buys you scaling headroom.
Indexing and ingestion cost: the part most comparisons skip
Most “Postgres FTS vs Elasticsearch performance” posts only talk about query speed. That’s lazy engineering.
Indexing cost is often what decides the architecture, because it shows up as write amplification, storage bills, and background maintenance that messes with tail latency.
You should measure:
- index build time
- steady-state write amplification
- disk footprint
- operational blast radius when something goes sideways
Postgres: GIN vs RUM vs trigram
-
GIN is the default workhorse. It’s commonly used to accelerate search over composite types like
tsvector. - RUM can help when ranking/highlighting is the bottleneck, at the cost of heavier indexing.
-
pg_trgmgives you trigram similarity for fuzzy/typo-tolerant matching. It’s often a complement to FTS, not a replacement.
One stat that matters for your harness: trigram similarity returns values in the 0 to 1 range. That makes it easy to turn into a feature signal in a hybrid scorer.
Elasticsearch: analyzers, shards, refresh interval
Elasticsearch performance is dominated by configuration decisions you don’t get to hand-wave away:
- shard count: too many shards means overhead and fan-out. Too few shards can cap parallelism.
- refresh interval: 1s is the default, but you can trade freshness for throughput.
- analyzers: your tokenization pipeline is half your relevance.
The painful part: “we’ll fix it later” often means a reindex. On big datasets, reindexing is not a weekend project.
A benchmark harness you can reproduce (relevance + latency + cost)
This is the differentiator. I don’t want you to trust my opinion. I want you to be able to prove it on your own data, on your own hardware, with your own constraints.
I run agent evals a lot. The same mindset applies here: define tasks, define ground truth, and ship a harness. If you’re doing this kind of work for LLM systems too, my mental model is in AI in production and the mechanics are in AI agents.
1) Define the dataset
Pick something representative, not convenient.
- 100k docs is enough to shake out basic issues.
- 1–10 million docs is where you start learning about tails and operational cost.
Record:
- total documents
- average doc length (characters)
- fields searched
2) Publish a query set (and lock it)
You need at least:
- 50 “head” queries (high frequency)
- 200 “torso” queries
- 500 “tail” queries (weird, misspelled, verbose)
If you want to test typo tolerance, inject controlled edits. For example: 1 deletion + 1 substitution on 20% of the tail queries.
3) Create qrels (relevance judgments)
This is the unsexy work.
- For each query, label at least 10–20 candidate docs.
- Use graded labels (0,1,2,3), not just relevant/irrelevant.
You can bootstrap candidates by unioning the top 20 from both systems, then judging.
4) Evaluate relevance with NDCG@k, MRR, Recall@k
Pick k = 10 or 20.
- NDCG@10: best for graded relevance.
- MRR: punishes “the right result is #7.”
- Recall@20: good for “did we miss anything?”
The goal isn’t a single magic score. It’s to see how tuning moves the curve.
5) Measure latency correctly
Minimum:
- p50 and p95 at concurrency 1, 10, 50
- warm-cache and cold-cache runs
- “read-only” and “writes in the background” runs
And break down stages where you can:
- query parsing
- retrieval
- ranking
- fetching/highlighting
6) Measure indexing + footprint
For each engine/config:
- initial index build time (minutes)
- index size on disk (GB)
- sustained ingest throughput (docs/sec)
This is where a lot of “Postgres is slower” narratives go to die.
Fairness rules (so the benchmark isn’t propaganda)
- Same stemming/stopwords behavior as much as possible.
- Same hardware.
- Same freshness target. If Elasticsearch is on 1s refresh, don’t compare it to Postgres “immediate.” Decide what the product requires.
- Warm caches before measuring p50/p95.
If you’re already doing evaluation programs for LLM systems, this will feel familiar. The process is basically the same as what I describe in AI engineering evals and agent evaluation harnesses.
Ops overhead in 2026: the scorecard that decides stacks
This is the part I care about most as an engineering leader. Latency is fun. Ops overhead is what wakes you up at 2 a.m.
The 2026 reality check
- Managed Postgres is everywhere. It’s boring. That’s a feature.
- Elasticsearch vs OpenSearch is still a real ecosystem split, and licensing can be a constraint depending on your company.
- DR expectations are higher. Multi-AZ is table stakes.
If you want a Postgres-first backup posture, you should read my guide on backing up PostgreSQL with pgBackRest.
Ops tasks (time + risk)
My rough estimates for a well-run team:
-
Postgres FTS
- Add a new search field + reindex: 1–3 hours (DDL + backfill + verify)
- Restore test (with the right tooling): half-day
- Scaling event: usually vertical, planned maintenance window if needed
-
Elasticsearch
- Mapping/analyzer change that needs reindex: half-day to multiple days depending on data size
- Shard rebalancing incident: can be hours of babysitting if you’re not disciplined
- Version upgrade: more moving parts (nodes, rolling restarts, client compatibility)
If you’ve never been the person staring at shard allocation at 1 a.m., this will sound dramatic. It isn’t.
A simple ops overhead score (0–10)
Score each category 0–2:
- backups + restore testing
- scaling events
- schema/mapping change friction
- monitoring + alerting surface area
- oncall blast radius
If your Elasticsearch score is 8–10 and your Postgres score is 3–4, you need an extremely good reason to keep Elasticsearch.
Write-heavy workloads: transactional vs near real-time freshness
Elastic is explicit: indexing is near real-time, and refresh makes changes visible to search. By default it refreshes every 1 second, and only on indices that have seen a search in the last 30 seconds.
That’s a design choice, not a bug.
In Postgres, updates are visible immediately after commit. The tradeoff is that your search index maintenance competes with OLTP. You’ll feel it as:
- write amplification when you update
tsvector - vacuum/autovacuum pressure
- index bloat if you churn text fields
If your product needs strict “write then immediately search” semantics, Postgres is simpler. If your product needs firehose ingestion, Elasticsearch is more at home.
Hybrid search in 2026: BM25 + vectors without a science project
Hybrid search is the new expectation. Users type vague queries and still expect semantic matching.
The simplest architecture that actually ships
- Use lexical search as your fast, explainable first-pass.
- Use vectors only where they add value.
In Postgres, that often means: Postgres FTS + pgvector (and maybe pg_trgm for typos). In Elasticsearch, it often means BM25 + vector search in the same cluster.
Here’s the part people miss: hybrid isn’t “add vectors.” Hybrid is “add evaluation.” The moment you add semantic retrieval, you need to start thinking like RAG teams do. If you’re building internal knowledge search for AI agents, you also need to care about prompt injection and retrieval safety.
Hybrid also changes your cost model. If you want to reason about cost systematically, I’ve written about LLM cost. Same FinOps muscle.
My stance (and a prediction)
If you’re building a typical SaaS product in 2026 and you still default to Elasticsearch for “search,” you’re probably over-engineering.
Start with Postgres full-text search. Build the eval harness. Measure relevance (NDCG/MRR) and p95 under load. Then, and only then, earn the right to run another distributed system.
My prediction: by 2027, the default stack for product search will be Postgres FTS + pg_trgm + vectors. Elasticsearch will keep winning where it’s always won: logs, observability, and search-first platforms. The teams that win are the ones who stop debating and start publishing their harness.
Originally published on kunalganglani.com
![Postgres Full Text Search vs Elasticsearch [2026]: Pick Right](https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbe1ucelylab1cyk8ryxt.png)











