pg_textsearch + pgvectorscale

Two Extensions.
Complete Search.

The BM25 engine behind hybrid search in Microsoft Azure HorizonDB.
Open source, Postgres licensed, running in your database today.

Microsoft HorizonDB announcement

Postgres

pg_textsearch

Keyword

BM25

pgvectorscale

Vector

Semantic

Trusted by

Zero Complexity

Forget provisioning three separate systems. It's just one database.

Zero Latency

Queries run where your data lives. No ETL pipelines or sync lag.

Total Consistency

Single source of truth. Writes and searches are transactionally safe.

pg_textsearch

Keyword

BM25

Modern ranked text search as a native Postgres extension. Industry-standard BM25 scoring with the <@> operator, Block-Max WAND optimization, and parallel index builds.

CREATE INDEX ON documents USING bm25(content) WITH (text_config='english'); SELECT * FROM documents ORDER BY content <@> 'database system' LIMIT 10;

Supports 29 languages

Configurable parameters

View on GitHub

pgvectorscale

Vector

Semantic

High-performance embedding search built on top of pgvector. The StreamingDiskANN index delivers 28x lower p95 latency and 16x higher throughput than Pinecone's storage-optimized index.

CREATE INDEX ON documents USING diskann (embedding vector_cosine_ops); SELECT * FROM documents ORDER BY embedding <=> $query_vector LIMIT 10;

75% less cost on AWS

Statistical Binary Quantization

View on GitHub

// the killer feature

Hybrid Search in a Single Query

The real power is using both together. Combine the lexical precision of BM25 with the semantic understanding of vector embeddings—without leaving your database, without an external reranker, and without syncing data.

How RRF works

Run BM25 keyword search and vector similarity search in parallel, then fuse results using Reciprocal Rank Fusion. One database, one query layer, state-of-the-art retrieval quality.

-- BM25 keyword results WITH keyword_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY content <@> 'search query') AS rank_kw FROM documents ORDER BY content <@> 'search query' LIMIT 20 ), -- Vector similarity results semantic_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $query_vec) AS rank_vec FROM documents ORDER BY embedding <=> $query_vec LIMIT 20 ) -- Reciprocal Rank Fusion SELECT COALESCE(k.id, s.id) AS id, COALESCE(1.0/(60 + k.rank_kw), 0) + COALESCE(1.0/(60 + s.rank_vec), 0) AS rrf_score FROM keyword_results k FULL OUTER JOIN semantic_results s ON k.id = s.id ORDER BY rrf_score DESC LIMIT 10;

BEGIN

TABLE
INSERT data

INDEX
UPDATE vector

COMMIT

atomic consistency

Why Build Search on
Postgres?

One system to operate

No ETL pipelines. No sync jobs. No eventual consistency. Insert a row and it's searchable.

ACID guarantees for search

Your search index participates in transactions. Rollback a write and the index rolls back with it.

Security you already have

Row-level security, roles, and permissions apply to search queries automatically.

Backups include everything

pg_dump, streaming replication, point-in-time recovery—your search indexes are just part of your database.

SQL you already know

No new query language. No client library. ORDER BY, LIMIT, WHERE, JOIN—search composes with everything else.

Up and running in 5 minutes