intermediate
Full-text search
Use tsvector, dictionaries, ranking, and GIN indexes when PostgreSQL search is sufficient before adopting a search cluster.
PostgreSQL full-text search (FTS) tokenizes text into `tsvector`, matches with `tsquery`, and ranks with `ts_rank`. It fits product search up to moderate scale before Elasticsearch complexity is justified.
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(body, '')), 'B')
) STORED;
CREATE INDEX idx_articles_fts ON articles USING GIN (search_vector);
SELECT id, title, ts_rank(search_vector, query) AS rank
FROM articles, plainto_tsquery('english', 'postgres indexing') AS query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;
Dictionaries control stemming and stop words. Prefix search needs `:*` in tsquery or trigram (`pg_trgm`) for fuzzy matching. Highlighting uses `ts_headline`.
On interviews: describe tsvector + GIN pipeline, when FTS is enough versus a dedicated search cluster, and how ranking weights affect relevance.
Common pitfalls: searching raw text without an index; wrong language config; expecting Google-grade typo tolerance from basic FTS; not updating vectors on write (use generated columns or triggers).
The trade-off is operational simplicity inside PostgreSQL versus advanced relevance, analyzers, and horizontal scale from a search engine.
Checklist:
- Store or generate tsvector on write.
- Index with GIN (or GiST for different trade-offs).
- Pick dictionary/language per content type.
- Define fallback when FTS recall or scale is insufficient.