Intermediate

pgvector

pgvector is an open-source PostgreSQL extension that adds vector similarity search to your existing PostgreSQL database. No new infrastructure needed — just add an extension.

What is pgvector?

pgvector brings vector search capabilities directly into PostgreSQL, the world's most popular open-source relational database. Key advantages:

  • No new database: Add vector search to your existing PostgreSQL setup.
  • Full SQL support: Combine vector similarity with JOINs, CTEs, transactions, and all PostgreSQL features.
  • ACID compliant: Vectors are part of your regular database with full transactional guarantees.
  • Familiar tooling: Use psql, pgAdmin, any PostgreSQL client, ORMs like SQLAlchemy.
  • Cost-effective: One database for relational data AND vectors.

Installation

On Linux/macOS

Terminal
# Install pgvector (Ubuntu/Debian)
sudo apt install postgresql-16-pgvector

# Or build from source
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install

With Docker

Terminal
# Use the pgvector Docker image
docker run -d --name pgvector \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  pgvector/pgvector:pg16

Enable the Extension

SQL
-- Enable pgvector in your database
CREATE EXTENSION IF NOT EXISTS vector;

Creating Vector Columns

SQL - Create Table with Vector Column
-- Create a table with a vector column
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    category TEXT,
    embedding vector(1536),     -- 1536-dimensional vector
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Insert a document with its embedding
INSERT INTO documents (title, content, category, embedding)
VALUES (
    'Introduction to AI',
    'Artificial intelligence is the simulation of human intelligence...',
    'technology',
    '[0.012, -0.034, 0.056, ...]'  -- 1536 float values
);

Indexing (IVFFlat and HNSW)

Without an index, pgvector performs exact nearest neighbor search (brute force). For large datasets, create an index for fast approximate search.

SQL - Create Indexes
-- HNSW index (recommended - better recall, no training needed)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- IVFFlat index (faster build, lower memory)
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Note: IVFFlat requires data to be present before creating the index

-- Distance operators:
-- vector_cosine_ops   for cosine distance   (<=>)
-- vector_l2_ops       for L2 distance       (<->)
-- vector_ip_ops       for inner product      (<#>)

SQL Queries for Similarity Search

SQL - Similarity Search Queries
-- Find 5 most similar documents (cosine distance)
SELECT id, title, content,
       1 - (embedding <=> '[0.012, -0.034, ...]') AS similarity
FROM documents
ORDER BY embedding <=> '[0.012, -0.034, ...]'
LIMIT 5;

-- Combine similarity search with SQL filters
SELECT id, title,
       1 - (embedding <=> $1) AS similarity
FROM documents
WHERE category = 'technology'
  AND created_at > NOW() - INTERVAL '30 days'
ORDER BY embedding <=> $1
LIMIT 10;

-- Use with JOINs
SELECT d.title, d.content, a.name AS author
FROM documents d
JOIN authors a ON d.author_id = a.id
ORDER BY d.embedding <=> $1
LIMIT 5;

Python with psycopg2

Python - Full Example
import psycopg2
import openai
import numpy as np

# Connect to PostgreSQL
conn = psycopg2.connect(
    host="localhost",
    database="mydb",
    user="postgres",
    password="mysecretpassword"
)
cur = conn.cursor()

# Generate embedding for the query
client = openai.OpenAI()
query = "How do neural networks learn?"
response = client.embeddings.create(
    input=[query],
    model="text-embedding-3-small"
)
query_embedding = response.data[0].embedding

# Search for similar documents
cur.execute("""
    SELECT id, title, content,
           1 - (embedding <=> %s::vector) AS similarity
    FROM documents
    WHERE category = 'ai'
    ORDER BY embedding <=> %s::vector
    LIMIT 5
""", (query_embedding, query_embedding))

results = cur.fetchall()
for row in results:
    print(f"[{row[3]:.4f}] {row[1]}: {row[2][:80]}...")

conn.close()

Python with SQLAlchemy

Python - SQLAlchemy ORM
from sqlalchemy import create_engine, Column, Integer, Text
from sqlalchemy.orm import declarative_base, Session
from pgvector.sqlalchemy import Vector

Base = declarative_base()

class Document(Base):
    __tablename__ = "documents"
    id = Column(Integer, primary_key=True)
    title = Column(Text)
    content = Column(Text)
    embedding = Column(Vector(1536))

engine = create_engine("postgresql://postgres:password@localhost/mydb")

# Query similar documents
with Session(engine) as session:
    results = (
        session.query(Document)
        .order_by(Document.embedding.cosine_distance(query_embedding))
        .limit(5)
        .all()
    )
    for doc in results:
        print(doc.title)

Performance Tuning

Parameter Effect Recommendation
HNSW m Connections per node 16 (default) to 64 for higher recall
HNSW ef_construction Build-time search width 64 (default) to 256 for better index quality
hnsw.ef_search Query-time search width 40 (default) to 200 for higher recall
IVFFlat lists Number of clusters sqrt(n) for n rows (e.g., 1000 for 1M rows)
ivfflat.probes Clusters searched per query sqrt(lists) (e.g., 32 for 1000 lists)
SQL - Tune Search Parameters
-- Increase HNSW search accuracy (per session)
SET hnsw.ef_search = 100;

-- Increase IVFFlat probes (per session)
SET ivfflat.probes = 20;

-- Check index usage
EXPLAIN ANALYZE
SELECT id, title
FROM documents
ORDER BY embedding <=> '[...]'::vector
LIMIT 10;
When to choose pgvector: Choose pgvector when you already use PostgreSQL and want to add vector search without introducing a new database. It is ideal for applications where vectors are tightly coupled with relational data (users, products, transactions) and you value ACID transactions and SQL familiarity.

💡 Try It Yourself

Set up pgvector with Docker, create a table with a vector column, insert 100 sample vectors, create an HNSW index, and run similarity queries. Compare query times with and without the index.

Use EXPLAIN ANALYZE to see whether PostgreSQL is using the HNSW index. Without the index, it will do a sequential scan.