Postgres Full-Text Search in 2026: Still Worth It?
Postgres full-text search has been around forever. In a 2026 world of pgvector and external search engines, when does it still make sense? Benchmarks and trade-offs.
Postgres has had full-text search since version 8.3 (2008). For most of that time the answer to "should I use FTS or an external engine?" was "use Elasticsearch". In 2026, with pgvector mature and FTS performance steadily improving, the landscape is different.
What Postgres FTS does
Postgres FTS tokenises text into a tsvector, supports stemming and stop-words for major languages, and matches it against a tsquery. With a GIN index, queries are fast.
-- Add a generated tsvector column
ALTER TABLE posts
ADD COLUMN search_tsv tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(content, '')), 'B')
) STORED;
-- GIN index on it
CREATE INDEX posts_search_idx ON posts USING gin (search_tsv);
-- Query
SELECT id, title, ts_rank(search_tsv, q) AS rank
FROM posts, to_tsquery('english', 'postgres & search') q
WHERE search_tsv @@ q
ORDER BY rank DESC
LIMIT 10;That's it. No second system. No sync pipeline. The query runs in tens of milliseconds against millions of rows.
When FTS wins
- Exact-keyword and prefix matching is the dominant query type. Tags, product names, SKUs, technical content.
- The text fits in the same Postgres database as the rest of your data. Joining search results with business data is free.
- Multilingual stemming is enough; you don't need ML-quality synonyms or semantic search.
- Your scale is under ~100M searchable rows. Past that, FTS performance starts to depend heavily on index tuning.
When vector / semantic search wins
- Users phrase queries naturally ("how do I cancel my subscription" vs "cancel"). Vector search captures the intent; FTS misses on synonym variation.
- You're building a RAG pipeline. Vector retrieval is the standard primitive.
- Your content is essay-length and topical, not name-shaped.
The hybrid pattern
The 2026 best practice for any non-trivial search surface: combine FTS and vector with reciprocal rank fusion. FTS catches the exact-keyword hits; vector catches the semantically-similar ones.
-- $1 = query embedding, $2 = query text
WITH lex AS (
SELECT id, row_number() OVER () AS rk
FROM posts WHERE search_tsv @@ plainto_tsquery('english', $2)
ORDER BY ts_rank(search_tsv, plainto_tsquery('english', $2)) DESC
LIMIT 20
),
vec AS (
SELECT id, row_number() OVER () AS rk
FROM posts ORDER BY embedding <=> $1 LIMIT 20
)
SELECT id, sum(1.0/(60+rk)) AS score
FROM (SELECT id, rk FROM lex UNION ALL SELECT id, rk FROM vec) u
GROUP BY id
ORDER BY score DESC LIMIT 10;50 lines, one round-trip, both signal types. This pattern beats pure FTS on most internal evals and beats pure vector on queries with proper nouns.
Setup that actually works in production
1. Use a generated column
Define search_tsv as GENERATED ALWAYS AS instead of writing a trigger. Less code, can't drift.
2. Set weights
Title in weight A, content in B, comments or footer text in C.ts_rank respects weights so "the user searched for the title" doesn't get drowned by body matches.
3. Use plainto_tsquery for user input
It handles arbitrary text safely. to_tsquery requires properly-formatted operator syntax and will throw on user input.
4. Pick the right language
'english' is the default; pick yours if different. Stop-word and stemming behavior matters more than you'd think for relevance.
Performance budget
For a typical SaaS-shaped corpus on a moderately-sized Postgres:
- Single FTS query against 1M rows: 5-15ms with a GIN index.
- Single FTS query against 100M rows: 30-100ms with a GIN index.
- Hybrid FTS + vector query: 40-120ms total.
If you're past these numbers, either the GIN index is missing or you have an unusual workload (very long documents, very high cardinality of distinct terms). External search engines are meaningfully faster only past ~500M documents or with tight latency SLAs.
See our pgvector production guide for the broader hybrid-search pattern in a RAG context.
Suparbase is an admin workspace for Supabase. Encrypted credentials, server-side proxy, RLS debugger, SQL playground, AI assistant with diff-confirmed writes. Free tier for solo projects.
Related articles
- vector · rag
Vector Databases Ranked for 2026: pgvector, Pinecone, Qdrant, Weaviate, and the Rest
An honest 2026 ranking of vector databases: pgvector, Pinecone, Qdrant, Weaviate, Chroma, Milvus, LanceDB. Where each one wins, what's overhyped, and what we'd actually pick.
Read article - postgres · indexes
Postgres Indexes Explained, Visually: btree, GIN, BRIN, GiST, Hash
Every Postgres index type, what it does, when to use it. A 2026 visual guide with concrete examples and the surprising performance numbers.
Read article - postgres · pgvector
pgvector and Postgres for RAG: A 2026 Production Setup
How to build a production RAG pipeline on Postgres with pgvector in 2026: HNSW indexes, embedding hygiene, chunking strategies, hybrid search, and the failure modes that bite teams in production.
Read article