pgvector
Open-source PostgreSQL extension for vector similarity search. Two components: (1) the server-side Postgres extension (C, compiled and installed into Postgres), and (2) the Python client package 'pgvector' on PyPI which provides ORM/adapter integrations for psycopg2, psycopg3, asyncpg, SQLAlchemy, Django, SQLModel, and Peewee. The extension name in SQL is 'vector' (CREATE EXTENSION vector), not 'pgvector'. Maintained by Andrew Kane. Current extension version: 0.8.2 (CVE security fix). Python client: 0.4.2.
Warnings
- breaking CVE-2026-3172: Buffer overflow with parallel HNSW index builds in versions 0.6.0–0.8.1. Can leak sensitive data from other relations or crash the database server. Fixed in 0.8.2.
- breaking Illegal instruction crashes (SIGILL) when pgvector is compiled with -march=native on one CPU architecture and run on another. Occurs on managed cloud Postgres (Azure Flexible Server, some GCP instances) after upgrading to 0.8.0+.
- breaking LangChain's langchain-postgres package requires psycopg3 (package name: psycopg). Connection strings must use postgresql+psycopg:// not postgresql+psycopg2://. Mixing drivers causes driver-not-found errors.
- breaking Postgres 17.0–17.2 causes link error: 'unresolved external symbol float_to_shortest_decimal_bufn' when building pgvector from source.
- gotcha The SQL extension name is 'vector', not 'pgvector'. CREATE EXTENSION pgvector raises 'extension not found'. This is a consistent source of confusion.
- gotcha register_vector(conn) must be called after every new connection. It is not persistent. Failing to call it means vector columns are returned as raw strings, not numpy arrays. No error is raised — silent wrong behavior.
- gotcha HNSW and IVFFlat indexes without ORDER BY + LIMIT do not use the ANN index — Postgres falls back to sequential scan. Queries without LIMIT return exact results but at O(n) cost.
- gotcha COSINE distance in pgvector uses the range [0, 2], not [0, 1]. 0 = identical, 2 = opposite. Thresholds from other libraries (which use [0,1]) must be remapped.
- gotcha IVFFlat index must be built AFTER data is loaded. Creating the index on an empty table and then inserting data results in a near-useless index (lists are not representative of the data distribution).
Install
-
pip install pgvector -
sudo apt install postgresql-17-pgvector -
brew install pgvector -
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=pass pgvector/pgvector:pg17 -
git clone --branch v0.8.2 https://github.com/pgvector/pgvector.git && cd pgvector && make && make install
Imports
- register_vector (psycopg2)
from pgvector.psycopg2 import register_vector
- register_vector (psycopg3)
from pgvector.psycopg import register_vector
- Vector (SQLAlchemy)
from pgvector.sqlalchemy import Vector
Quickstart
# Step 1: Enable extension in Postgres (run once per database)
# CREATE EXTENSION IF NOT EXISTS vector;
import psycopg2
from pgvector.psycopg2 import register_vector
import numpy as np
conn = psycopg2.connect("dbname=mydb user=postgres")
register_vector(conn) # REQUIRED: registers the vector type
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS items (id bigserial PRIMARY KEY, embedding vector(3))")
# Insert vectors
cur.execute("INSERT INTO items (embedding) VALUES (%s)", (np.array([1.0, 2.0, 3.0], dtype='float32'),))
conn.commit()
# L2 distance search (<->)
cur.execute("SELECT id FROM items ORDER BY embedding <-> %s LIMIT 5", (np.array([1.0, 1.0, 1.0], dtype='float32'),))
print(cur.fetchall())
cur.close()
conn.close()