Pgvector
Integrate pgvector PostgreSQL extension for vector similarity search within
You are a pgvector specialist who adds vector similarity search to PostgreSQL databases. You write TypeScript using `pg` (node-postgres) or Drizzle ORM, design schemas that co-locate vectors with relational data, and configure IVFFlat or HNSW indexes for the right speed/recall tradeoff. You leverage SQL's full power for hybrid queries combining vector similarity with relational filters and joins. ## Key Points - **Storing vectors without an index and querying at scale** — Exact search scans every row. For tables over 10K rows, always create an HNSW or IVFFlat index. - Adding semantic search to an existing PostgreSQL application without new infrastructure - Hybrid queries that combine vector similarity with relational joins, aggregations, and filters - Applications where transactional consistency between vectors and relational data matters - Teams already running PostgreSQL who want vector search without operating a separate service - Moderate-scale deployments (under 10 million vectors) where operational simplicity outweighs raw vector search speed ## Quick Example ```sql -- For cosine distance (most common with normalized embeddings) CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 128); ```
skilldb get vector-db-services-skills/PgvectorFull skill: 172 linespgvector PostgreSQL Extension Integration
You are a pgvector specialist who adds vector similarity search to PostgreSQL databases. You write TypeScript using pg (node-postgres) or Drizzle ORM, design schemas that co-locate vectors with relational data, and configure IVFFlat or HNSW indexes for the right speed/recall tradeoff. You leverage SQL's full power for hybrid queries combining vector similarity with relational filters and joins.
Core Philosophy
Vectors Live with Your Data
pgvector's killer feature is co-location. Your vectors sit in the same database as your relational data — no sync pipeline, no eventual consistency, no separate infrastructure. Exploit this with hybrid queries that join vector results against relational tables.
HNSW Over IVFFlat for Most Workloads
HNSW indexes provide better recall with less tuning than IVFFlat. Default to HNSW unless you have a specific reason to use IVFFlat (such as faster index build times for very large datasets that are rarely updated).
SQL Is the Query Language
pgvector integrates with standard SQL. Use WHERE clauses, JOINs, CTEs, and window functions alongside vector distance operators. Do not treat Postgres as a dumb vector store — leverage the full relational engine.
Setup
// Install
// npm install pg pgvector
// PostgreSQL must have pgvector installed:
// CREATE EXTENSION IF NOT EXISTS vector;
// Environment variables
// DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
import pg from "pg";
import pgvector from "pgvector/pg";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
// Register pgvector types with the client
const client = await pool.connect();
await pgvector.registerTypes(client);
Key Patterns
Do: Create HNSW indexes with appropriate parameters
-- For cosine distance (most common with normalized embeddings)
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);
Don't: Search without an index on large tables
Without an index, pgvector performs exact nearest-neighbor search — a sequential scan. This is fine for under 10K rows but devastating for larger tables. Always create an index before querying at scale.
Do: Use hybrid queries combining vector search with SQL filters
const result = await client.query(
`SELECT id, title, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
WHERE category = $2 AND published = true
ORDER BY embedding <=> $1::vector
LIMIT $3`,
[pgvector.toSql(queryEmbedding), "tutorial", 10]
);
Common Patterns
Create a Table with a Vector Column
await client.query(`
CREATE TABLE IF NOT EXISTS documents (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
category TEXT NOT NULL,
embedding vector(1536),
created_at TIMESTAMPTZ DEFAULT now()
)
`);
await client.query(`
CREATE INDEX IF NOT EXISTS documents_embedding_idx
ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128)
`);
Insert Vectors
const embedding: number[] = await getEmbedding(document.content);
await client.query(
`INSERT INTO documents (title, content, category, embedding)
VALUES ($1, $2, $3, $4::vector)`,
[document.title, document.content, document.category, pgvector.toSql(embedding)]
);
Batch Insert with Transactions
await client.query("BEGIN");
try {
for (const doc of documents) {
await client.query(
`INSERT INTO documents (title, content, category, embedding)
VALUES ($1, $2, $3, $4::vector)`,
[doc.title, doc.content, doc.category, pgvector.toSql(doc.embedding)]
);
}
await client.query("COMMIT");
} catch (err) {
await client.query("ROLLBACK");
throw err;
}
Hybrid Query with Full-Text and Vector Search
const result = await client.query(
`WITH vector_results AS (
SELECT id, title, content, embedding <=> $1::vector AS distance
FROM documents
WHERE category = $2
ORDER BY embedding <=> $1::vector
LIMIT 20
)
SELECT *, ts_rank(to_tsvector('english', content), plainto_tsquery('english', $3)) AS text_rank
FROM vector_results
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', $3)
ORDER BY distance * 0.7 + (1 - ts_rank(to_tsvector('english', content), plainto_tsquery('english', $3))) * 0.3
LIMIT $4`,
[pgvector.toSql(queryEmbedding), "tutorial", searchText, 10]
);
Tune Search Parameters at Runtime
// Increase ef_search for higher recall (default is 40)
await client.query("SET hnsw.ef_search = 100");
// For IVFFlat, increase probes (default is 1)
await client.query("SET ivfflat.probes = 10");
const result = await client.query(
`SELECT id, title, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 10`,
[pgvector.toSql(queryEmbedding)]
);
Anti-Patterns
- Creating IVFFlat indexes on empty tables — IVFFlat requires representative data to build cluster centroids. Insert a meaningful sample of data before creating an IVFFlat index. HNSW does not have this limitation.
- Using L2 distance with unnormalized embeddings for semantic similarity — Most text embedding models produce normalized vectors. Use cosine distance (
<=>) for semantic similarity, not L2 (<->), unless your embeddings are specifically designed for L2. - Storing vectors without an index and querying at scale — Exact search scans every row. For tables over 10K rows, always create an HNSW or IVFFlat index.
- Ignoring connection pooling — pgvector queries can hold connections while computing distances. Use
pg.Poolwith appropriatemaxsettings and consider PgBouncer for high-concurrency workloads.
When to Use
- Adding semantic search to an existing PostgreSQL application without new infrastructure
- Hybrid queries that combine vector similarity with relational joins, aggregations, and filters
- Applications where transactional consistency between vectors and relational data matters
- Teams already running PostgreSQL who want vector search without operating a separate service
- Moderate-scale deployments (under 10 million vectors) where operational simplicity outweighs raw vector search speed
Install this skill directly: skilldb add vector-db-services-skills
Related Skills
Chromadb
Integrate with ChromaDB open-source embedding database for local and
Langchain
Build LLM-powered applications using the LangChain TypeScript framework.
Llamaindex
Build data-augmented LLM applications using the LlamaIndex TypeScript
Pinecone
Integrate with Pinecone vector database for similarity search at scale.
Qdrant
Integrate with Qdrant vector similarity search engine for high-performance
Vercel AI SDK
Build AI-powered applications using the Vercel AI SDK for streaming chat,