pgvector Basics
pgvector adds a vector type and distance operators to PostgreSQL for embedding storage and similarity search. Install the extension, pick a distance metric, and understand dimension limits before building RAG indexes.
Search across all documentation pages
pgvector adds a vector type and distance operators to PostgreSQL for embedding storage and similarity search. Install the extension, pick a distance metric, and understand dimension limits before building RAG indexes.
CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA extensions VERSION '0.8.0';
CREATE TABLE embeddings (
id bigserial PRIMARY KEY,
label text NOT NULL,
embedding extensions.vector(3) -- dimension fixed at DDL time
);
INSERT INTO embeddings (label, embedding) VALUES
('postgres', '[1,2,3]'),
('vectors', '[2,3,4]');
-- Distance operators (use matching index opclass)
SELECT label,
embedding <-> '[1,2,3]'::extensions.vector AS l2_distance,
embedding <=> '[1,2,3]'::extensions.vector AS cosine_distance,
embedding <#> '[1,2,3]'::extensions.vector AS inner_product_neg
FROM embeddings
ORDER BY embedding <=> '[1,2,3]'::extensions.vector
LIMIT 5;When to reach for this: Semantic search, RAG retrieval, recommendations, or deduplication where embeddings already exist from an upstream model.
CREATE TABLE documents (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
title text,
embedding extensions.vector(1536) -- OpenAI text-embedding-3-small
);
INSERT INTO documents (title, embedding)
VALUES (
'Vacuum and bloat',
(SELECT array_agg(random())::float[]::extensions.vector FROM generate_series(1, 1536))
);
-- Exact nearest neighbor (no index; fine for small tables)
SELECT id, title, embedding <=> (
SELECT embedding FROM documents LIMIT 1
) AS distance
FROM documents
ORDER BY distance
LIMIT 10;What this demonstrates:
vector(1536))'[...]' or casts from arrays<=> is cosine distance for normalized embeddings| Operator | Metric | Index opclass | Typical use |
|---|---|---|---|
<-> | L2 (Euclidean) | vector_l2_ops | Raw embedding spaces |
<=> | Cosine distance | vector_cosine_ops | Normalized text embeddings |
<#> | Negative inner product | vector_ip_ops | Max inner product search |
Rule: Query operator must match index opclass. Cosine-indexed column with L2 operator returns wrong ordering or no index.
vector type in pgvector 0.8+ (check release notes for your pin).4 * dimensions bytes per vector (float32) plus heap overhead.halfvec (when enabled in your build) reduces storage for large corpora.SELECT typname, typlen FROM pg_type WHERE typname = 'vector';
-- Validate dimension at insert
ALTER TABLE documents
ADD CONSTRAINT embedding_dim CHECK (vector_dims(embedding) = 1536);SELECT vector_dims(embedding), vector_norm(embedding) FROM documents LIMIT 1;
-- Element-wise arithmetic
SELECT embedding + '[0.1,0.2,0.3]'::extensions.vector FROM embeddings WHERE id = 1;Postgres + pgvector wins when:
Consider dedicated vector stores when billion-scale, sub-10ms at huge QPS, or advanced filtering DSL is mandatory and proven costly in SQL.
vector_l2_ops but queries use <=>. Fix: Rebuild index with vector_cosine_ops to match queries.CHECK (vector_norm(embedding) > 0) or normalize at insert in app.type vector does not exist. Fix: Schema-qualify extensions.vector or add schema to search_path for app role only.| Alternative | Use When | Don't Use When |
|---|---|---|
| IVFFlat index | Large table, memory constrained | Need highest recall out of the box |
| HNSW index | Production ANN default in 0.8+ | Cannot afford index build RAM |
| External Pinecone/Weaviate | Massive scale, managed ANN | Strong FK joins with relational data |
pg_trgm FTS | Exact keyword | Semantic paraphrase |
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PostGIS 3.5+, pgbouncer 1.x, and Patroni 3.x.
Reviewed by Chris St. John·Last updated Jul 18, 2026