pgvector: a complete overview
What pgvector adds to PostgreSQL: distances, indexes and types, pgvector compared with vector databases, three SQL examples, settings for production and common mistakes.
In short
pgvector is an extension for PostgreSQL that adds a vector type, distance operators and indexes for fast similarity search. It turns an ordinary database into the storage for search by meaning, recommendations and RAG: vectors live in the same table as products, articles or tickets, are filtered with ordinary SQL, joined with other tables and backed up together with everything else. For most projects — up to tens of millions of vectors — a separate vector database is not needed. It is available in the main managed PostgreSQL services and installs on your own server with one package.
pgvector at a glance
The main facts in one table — what the extension adds and where it runs.
- What it is
- An open-source extension for PostgreSQL for storing and searching vectors
- History
- Andrew Kane, 2021; HNSW indexes since 0.5 (2023), iterative scans since 0.8 (2024)
- License
- PostgreSQL License — free, including commercial use
- Types
vector,halfvec(half the size),sparsevec,bit- Indexes
- HNSW and IVFFlat — approximate search
- Where it runs
- Own server, AWS, Google Cloud, Azure, Supabase, Neon and other services
- Comfortable scale
- Up to tens of millions of vectors on one server
Distances, indexes and types
Everything pgvector adds to SQL. The distance in the query must match the operator class of the index, otherwise the index is not used.
| Element | What it is | When to use |
|---|---|---|
| <=> | cosine distance | text embeddings — the usual choice |
| <-> | Euclidean distance (L2) | images, coordinates, some models |
| <#> | negative inner product | normalised vectors — the fastest |
| <+> | Manhattan distance (L1) | rare special cases |
| HNSW | a graph index: fast and accurate | the default for most projects |
| IVFFlat | an index of clusters: smaller, builds faster | large static sets, after loading the data |
| halfvec | vectors in half precision | half the memory with almost the same quality |
| bit | binary vectors with Hamming distance | a fast first pass over very large sets |
pgvector or a separate vector database
Pinecone, Qdrant, Weaviate and Milvus are built only for vectors. Here is what that gives and what it costs.
| Criterion | pgvector | Vector database |
|---|---|---|
| Where the vectors live | next to the data, in the same table | in a separate service |
| Synchronisation | not needed — one transaction | needed, a source of mismatches |
| Filters | ordinary SQL, joins | its own filter language |
| Search by words | PostgreSQL full-text search | depends on the product |
| Backups and rights | the same as for the whole base | set up separately |
| Scale | tens of millions of vectors | up to billions |
| Cost | nothing extra | a separate bill or servers |
What pgvector looks like: 3 examples
A table with an index, search with a filter and hybrid search by meaning and by words. The vector of the question is computed by the application and passed as a parameter.
A table and an index
The vector is just another column; the HNSW index is built for cosine distance.
-- pgvector: vectors live in an ordinary table next to the rest of the data
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE products (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku text UNIQUE NOT NULL,
title text NOT NULL,
category text NOT NULL,
in_stock boolean NOT NULL DEFAULT true,
embedding vector(1024) -- the size is set by the embedding model
);
-- HNSW index for cosine distance: fast approximate search
CREATE INDEX products_embedding_idx
ON products USING hnsw (embedding vector_cosine_ops);
Search with a filter
Ordinary WHERE conditions next to the similarity; the iterative scan keeps the result full when the filter is strict.
-- Similar products in stock; the vector of the query comes from the application as $1
SET hnsw.iterative_scan = relaxed_order; -- pgvector 0.8+: keep scanning while the filter discards rows
SELECT sku, title, 1 - (embedding <=> $1) AS similarity
FROM products
WHERE in_stock AND category = 'tables'
ORDER BY embedding <=> $1
LIMIT 10;
Hybrid search
Meaning finds “dining table” for “table for the kitchen”, words find the exact article number — together they find both.
-- Hybrid search: by meaning (pgvector) and by words (full-text search),
-- the two lists are merged by rank — Reciprocal Rank Fusion
WITH semantic AS (
SELECT id, row_number() OVER (ORDER BY embedding <=> $1) AS rank
FROM products
ORDER BY embedding <=> $1
LIMIT 20
),
keyword AS (
SELECT id, row_number() OVER (ORDER BY ts_rank_cd(to_tsvector('english', title), q) DESC) AS rank
FROM products, plainto_tsquery('english', $2) AS q
WHERE to_tsvector('english', title) @@ q
ORDER BY ts_rank_cd(to_tsvector('english', title), q) DESC
LIMIT 20
)
SELECT p.sku, p.title,
coalesce(1.0 / (60 + s.rank), 0) + coalesce(1.0 / (60 + k.rank), 0) AS score
FROM semantic s
FULL OUTER JOIN keyword k ON k.id = s.id
JOIN products p ON p.id = coalesce(s.id, k.id)
ORDER BY score DESC
LIMIT 10;
7 settings that matter in production
-
01
The index matches the operator
An index on
vector_cosine_opsworks only for<=>; check the plan with EXPLAIN. -
02
ef_search for accuracy
hnsw.ef_search(40 by default) is raised when relevant results are missing. -
03
Memory for building
A larger
maintenance_work_membuilds an HNSW index many times faster. -
04
halfvec for large sets
Halves the size of the table and the index almost without loss of quality.
-
05
Iterative scan with filters
Without it a strict WHERE can return fewer rows than LIMIT.
-
06
Partial indexes
A separate index per language or category when searches never cross them.
-
07
One model per column
Vectors from different models are not comparable — a new model means a new column and re-indexing.
Common mistakes with pgvector
-
A separate vector database too early
Another service and synchronisation for a few hundred thousand vectors that PostgreSQL holds easily.
-
No index
Without it every query compares the question with every row — fine for thousands, slow for millions.
-
IVFFlat on an empty table
Its clusters are computed from the data present; built too early, it searches badly.
-
Similarity without a threshold
ORDER BY always returns the nearest rows, even when none of them is relevant.
-
Mixing models
Part of the rows indexed by one model and part by another — the search silently degrades.
-
Vectors without source text
The text must be kept next to the vector — to show it, cite it and re-index.
Questions about pgvector
What is pgvector?
An extension that lets PostgreSQL store vectors and search for the most similar ones.
How many vectors can it handle?
Tens of millions on one server with an HNSW index; beyond that — partitioning or a dedicated system.
HNSW or IVFFlat?
HNSW in most cases: it is more accurate and works on a table that keeps changing.
What vector size should I use?
The one the embedding model produces — 384, 768, 1024, 1536 and so on.
Is it available in managed PostgreSQL?
Yes, in AWS, Google Cloud, Azure, Supabase, Neon and most other services.
Does it work with MySQL?
No, it is a PostgreSQL extension; MySQL has its own vector type in recent versions.
Can it search images?
Yes, if the images are turned into vectors by a suitable model; the database does not care what the vector means.
Online form
Search
by meaning
I build search by meaning, recommendations and AI assistants on PostgreSQL with pgvector — next to your data, without a separate service. Tell me about the project — I answer within one working day.