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.

Stack and technologies Updated

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.

ElementWhat it isWhen 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.

CriterionpgvectorVector 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.

schema.sql
-- 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.

search.sql
-- 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.sql
-- 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

  1. 01

    The index matches the operator

    An index on vector_cosine_ops works only for <=>; check the plan with EXPLAIN.

  2. 02

    ef_search for accuracy

    hnsw.ef_search (40 by default) is raised when relevant results are missing.

  3. 03

    Memory for building

    A larger maintenance_work_mem builds an HNSW index many times faster.

  4. 04

    halfvec for large sets

    Halves the size of the table and the index almost without loss of quality.

  5. 05

    Iterative scan with filters

    Without it a strict WHERE can return fewer rows than LIMIT.

  6. 06

    Partial indexes

    A separate index per language or category when searches never cross them.

  7. 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

  1. A separate vector database too early

    Another service and synchronisation for a few hundred thousand vectors that PostgreSQL holds easily.

  2. No index

    Without it every query compares the question with every row — fine for thousands, slow for millions.

  3. IVFFlat on an empty table

    Its clusters are computed from the data present; built too early, it searches badly.

  4. Similarity without a threshold

    ORDER BY always returns the nearest rows, even when none of them is relevant.

  5. Mixing models

    Part of the rows indexed by one model and part by another — the search silently degrades.

  6. 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.

Or write to [email protected]