pgvector Brings Semantic Search to Postgres, Cutting RAG Latency

Data Science
Date:October 13, 2026
Topic:
pgvector Brings Semantic Search to Postgres, Cutting RAG Latency
⏱ 3 min read

You don't need a dedicated vector database. You need a vector index inside the database you already trust. In 2024, teams spun up Pinecone, Weaviate, and Chroma by default. In 2026, the smart money is consolidating back into PostgreSQL with pgvector. The reason is boring but decisive: operational simplicity beats marginal recall gains when you are on call at 3 AM.

The Infrastructure Tax Is Real

Every external vector store adds a sync pipeline, a credentials rotation policy, a networking rule, and a billing surprise. You duplicate data. You race consistency. You debug latency across network hops. pgvector eliminates the sync. Your embeddings live next to your users, orders, and transactions. A single `JOIN` replaces a round-trip to a microservice. ACID compliance comes free. Backups, replication, and point-in-time recovery work exactly as they did yesterday.

Schema That Ships

sql
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  embedding VECTOR(1536),
  metadata JSONB DEFAULT '{}',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

Use `hnsw` (Hierarchical Navigable Small World) for production workloads. It builds a graph index that delivers sub-millisecond latency on millions of rows. Tune `m` for recall vs. memory; tune `ef_construction` for build speed vs. quality. For write-heavy workloads, consider `ivfflat` with `lists = sqrt(row_count)` as a lighter alternative.

Hybrid Search: The Production Multiplier

Pure semantic search misses exact matches—model numbers, error codes, proper nouns. PostgreSQL's native `tsvector` fixes this. Combine both signals in one query:

sql
SELECT id, content,
  1 - (embedding <=> $1) AS semantic_score,
  ts_rank_cd(to_tsvector('english', content), plainto_tsquery('english', $2)) AS lexical_score
FROM documents
WHERE embedding <=> $1 < 0.5
   OR to_tsvector('english', content) @@ plainto_tsquery('english', $2)
ORDER BY (0.7 * (1 - (embedding <=> $1))) + (0.3 * ts_rank_cd(to_tsvector('english', content), plainto_tsquery('english', $2))) DESC
LIMIT 10;
💡
TipWeight semantic vs. lexical per use case. Support tickets favor lexical (0.4/0.6). General Q&A favors semantic (0.7/0.3).

TypeScript Pipeline That Compiles

typescript
import { Pool } from 'pg';
import { OpenAIEmbeddings } from '@langchain/openai';

const pool = new Pool();
const embedder = new OpenAIEmbeddings({ model: 'text-embedding-3-small' });

async function upsertDoc(content: string, metadata = {}) {
  const embedding = await embedder.embedQuery(content);
  await pool.query(
    `INSERT INTO documents (content, embedding, metadata) VALUES ($1, $2, $3)`,
    [content, `[${embedding.join(',')}]`, JSON.stringify(metadata)]
  );
}

async function hybridSearch(query: string, k = 10) {
  const qEmbedding = await embedder.embedQuery(query);
  const vec = `[${qEmbedding.join(',')}]`;
  const { rows } = await pool.query(
    `SELECT id, content, metadata,
       1 - (embedding <=> $1) AS sem_score,
       ts_rank_cd(to_tsvector('english', content), plainto_tsquery('english', $2)) AS lex_score
     FROM documents
     WHERE embedding <=> $1 < 0.5
        OR to_tsvector('english', content) @@ plainto_tsquery('english', $2)
     ORDER BY (0.7 * (1 - (embedding <=> $1))) + (0.3 * ts_rank_cd(to_tsvector('english', content), plainto_tsquery('english', $2))) DESC
     LIMIT $3`,
    [vec, query, k]
  );
  return rows;
}

pgvectorscale: When You Outgrow Single-Node

If you hit 50M+ vectors or need distributed writes, `pgvectorscale` (from Timescale) adds tiered storage and statistical binary quantization (SBQ). SBQ compresses vectors to 1-bit representations with a correction layer, cutting disk usage by 96% while keeping recall above 95%. It also introduces `DiskANN`-style graph indexes that stream from SSD, keeping hot shards in RAM. Migration is a `CREATE EXTENSION` and an index rebuild—no schema change.

"

We replaced a $12k/mo Pinecone cluster with a $3k/mo RDS instance and got better latency because the data never left the VPC.

— Staff Engineer, Fintech Unicorn

Operational Checklist

TaskFrequencyCommand
ANALYZE documentsWeeklypg_sleep(0)
REINDEX CONCURRENTLYMonthlyREINDEX INDEX CONCURRENTLY documents_embedding_idx
Monitor index sizeDailySELECT pg_size_pretty(pg_relation_size('documents_embedding_idx'))
Tune ef_searchPer querySET hnsw.ef_search = 100;

✦

Ship It Tonight

Stop managing infrastructure you don't need. Enable the extension, add the column, build the HNSW index, and write the hybrid query. Your RAG latency drops, your stack shrinks, and your on-call rotation sleeps better. The vector database is Postgres. It always was.

Share𝕏 Twitterin LinkedInin Whatsapp