pgvector: Vector Search in PostgreSQL for Enterprise Data Teams
While dedicated vector databases (Pinecone, ChromaDB) are popular, they introduce a major operational problem: architectural fragmentation.
Now your data platform team must manage two separate systems:
- PostgreSQL / Snowflake for relational business data.
- A separate vector DB for embeddings.
To join customer details with vector search results, you must write distributed network glue code.
pgvector is an open-source extension for PostgreSQL that allows you to store, index, and query vector embeddings directly alongside your relational tables with full ACID compliance!
1. Why Data Engineers Choose pgvector
flowchart TD
subgraph SinglePostgresInstance["Unified PostgreSQL Database (ACID)"]
Table["Relational Table: 'customer_support_cases'"]
Col1["case_id: INT PRIMARY KEY"]
Col2["customer_id: INT FOREIGN KEY"]
Col3["created_at: TIMESTAMP"]
Col4["issue_text: TEXT"]
Col5["embedding: vector(1536) <== PGVECTOR COLUMN!"]
Table --- Col1
Table --- Col2
Table --- Col3
Table --- Col4
Table --- Col5
end
style SinglePostgresInstance fill:#1e293b,color:#fff
style Table fill:#2563eb,color:#fff
style Col5 fill:#059669,color:#fff
The Unbeatable Advantages:
- ACID Transactions: If an ETL transaction rolls back, the embedding rolls back with it. No orphan vectors!
- Single SQL Query: Join relational user permissions, subscription tiers, and vector semantic similarity in a single standard
SELECTstatement! - Familiar Tooling: Backups via
pg_dump, replication with standard Postgres read replicas, and standard connection pooling with PgBouncer.
2. Enabling pgvector and Distance Operators
-- 1. Enable the extension
CREATE EXTENSION IF NOT EXISTS vector;
-- 2. Create table with vector column (1536 dimensions for OpenAI embeddings)
CREATE TABLE documentation_chunks (
chunk_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
article_title VARCHAR(255) NOT NULL,
chunk_content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
embedding vector(1536) NOT NULL
);
pgvector Distance Operators:
| Operator | Mathematical Metric | Best For |
|---|---|---|
<=> |
Cosine Distance ($1 - ext{CosineSim}$) | Text embeddings (OpenAI, BGE, Cohere) |
<#> |
Negative Inner Product ($-\mathbf{u} \cdot \mathbf{v}$) | Pre-normalized unit vectors |
<-> |
Euclidean ($L_2$) Distance | Unnormalized coordinate points |
3. Index Types: HNSW vs. IVFFlat
Without an index, Postgres performs an exact flat scan across every vector ($O(N)$ sequential scan). For millions of rows, you must add an Approximate Nearest Neighbor (ANN) index:
flowchart LR
IVF["IVFFlat Index<br/>Clusters space into inverted lists<br/>Faster build time, lower memory, slightly lower recall"]
HNSW["HNSW Index (Hierarchical Navigable Small World)<br/>Multi-layer proximity graphs<br/>Industry gold standard: 10x faster query latency, 99%+ recall!"]
-- Create an HNSW index using Cosine Distance
CREATE INDEX ON documentation_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
4. Hybrid Search: Combining Vector + Keyword (Full-Text) Search
The holy grail of enterprise search is Hybrid Search: combining keyword exact-matching (e.g., error code "ORA-00942") with semantic vector meaning:
WITH semantic_search AS (
SELECT chunk_id, article_title, chunk_content,
ROW_NUMBER() OVER (ORDER BY embedding <=> '[0.012, -0.045, ...]'::vector) as rank_vector
FROM documentation_chunks
LIMIT 20
),
keyword_search AS (
SELECT chunk_id, article_title, chunk_content,
ROW_NUMBER() OVER (ORDER BY ts_rank(to_tsvector('english', chunk_content), query) DESC) as rank_keyword
FROM documentation_chunks, plainto_tsquery('english', 'ORA-00942 table or view does not exist') query
WHERE to_tsvector('english', chunk_content) @@ query
LIMIT 20
)
-- Reciprocal Rank Fusion (RRF)
SELECT COALESCE(s.chunk_id, k.chunk_id) as chunk_id,
COALESCE(s.article_title, k.article_title) as title,
COALESCE(1.0 / (60 + s.rank_vector), 0.0) +
COALESCE(1.0 / (60 + k.rank_keyword), 0.0) as rrf_score
FROM semantic_search s
FULL OUTER JOIN keyword_search k ON s.chunk_id = k.chunk_id
ORDER BY rrf_score DESC
LIMIT 5;
Key Takeaways
- pgvector eliminates architectural sprawl by housing vectors inside existing relational Postgres databases.
- HNSW index is the recommended index type for 95% of production workloads due to superior speed and recall.
- Hybrid search via SQL combines full-text indexing (
tsvector) with semantic vector distance (<=>) in a single query.