Programación·10 min read

pgvector en producción: PostgreSQL como base de datos vectorial sin abandonar tu stack

Búsqueda semántica, RAG y embeddings directamente en tu Postgres. Cuándo pgvector es suficiente y cuándo necesitas algo más.

Dax Reyes
··1,850 vistas

La mayoría de equipos que empiezan a construir aplicaciones con RAG hacen lo mismo: buscan una base de datos vectorial, evalúan Pinecone, Weaviate o Qdrant, y acaban añadiendo una dependencia nueva a su stack. A veces tiene sentido. Muchas veces, no.

Si ya tienes PostgreSQL en producción —y la mayoría de aplicaciones lo tienen—, pgvector te da búsqueda vectorial sin salir de tu stack actual. La pregunta no es si puedes usarlo: la pregunta es cuándo es suficiente y cuándo no lo es.

Qué es pgvector y qué problema resuelve

pgvector es una extensión de PostgreSQL que añade un tipo de dato vector y operadores para calcular similitud entre vectores. Esto te permite almacenar embeddings directamente en tus tablas y hacer búsquedas de similitud (nearest neighbor) con SQL normal.

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documentos (
  id BIGSERIAL PRIMARY KEY,
  contenido TEXT,
  embedding vector(1536),  -- dimensiones del modelo de embeddings
  metadata JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Búsqueda por similitud coseno
SELECT contenido, 1 - (embedding <=> $1) AS similitud
FROM documentos
ORDER BY embedding <=> $1
LIMIT 10;

El índice que importa: HNSW vs IVFFlat

pgvector soporta dos tipos de índice para búsqueda aproximada. Elegir mal aquí es el error más común en producción.

IVFFlat divide los vectores en listas (clusters) y busca solo en los más cercanos al query. Es rápido de construir y funciona bien para colecciones estáticas, pero degrada mucho si insertas datos frecuentemente porque el índice no se rebalancea.

HNSW (Hierarchical Navigable Small World) construye un grafo multicapa que permite búsqueda logarítmica. Es más lento de construir y consume más memoria, pero mantiene la precisión bajo inserciones continuas y escala mejor en producción.

-- HNSW: mejor para datos dinámicos
CREATE INDEX ON documentos USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- IVFFlat: mejor para colecciones grandes y estáticas
CREATE INDEX ON documentos USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);

Regla práctica: si insertas más de 1000 documentos al día, HNSW. Si cargas un corpus de una vez y rara vez actualizas, IVFFlat.

RAG con pgvector: el flujo completo

import asyncpg
from openai import AsyncOpenAI

client = AsyncOpenAI()

async def rag_query(pregunta: str, conn: asyncpg.Connection) -> str:
    # 1. Generar embedding de la pregunta
    response = await client.embeddings.create(
        model="text-embedding-3-small",
        input=pregunta
    )
    query_embedding = response.data[0].embedding

    # 2. Recuperar documentos relevantes
    docs = await conn.fetch("""
        SELECT contenido, 1 - (embedding <=> $1) AS score
        FROM documentos
        WHERE 1 - (embedding <=> $1) > 0.7
        ORDER BY embedding <=> $1
        LIMIT 5
    """, query_embedding)

    # 3. Construir contexto y llamar al LLM
    contexto = "\n\n".join(doc["contenido"] for doc in docs)
    completion = await client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": f"Contexto:\n{contexto}"},
            {"role": "user", "content": pregunta}
        ]
    )
    return completion.choices[0].message.content

Cuándo pgvector es suficiente

pgvector cubre bien estos escenarios:

  • Colecciones de hasta ~10 millones de vectores con HNSW
  • Equipos que ya operan PostgreSQL y quieren evitar otra dependencia
  • Aplicaciones donde los vectores conviven con datos relacionales (filtros por usuario, fecha, categoría)
  • Startups que quieren velocidad de iteración sin complejidad operacional

La ventaja clave frente a bases de datos vectoriales especializadas no es el rendimiento: es la colocación de datos. Puedes hacer JOINs entre tus vectores y tu tabla de usuarios, filtrar por permisos antes de buscar, o actualizar un documento y su embedding en la misma transacción.

Cuándo necesitas algo más

pgvector tiene límites reales que importan en ciertos contextos:

  • Más de 10-15M de vectores: el rendimiento de HNSW empieza a degradar con colecciones muy grandes
  • Multi-tenancy masivo: si tienes miles de tenants con colecciones separadas, la gestión de índices se complica
  • Latencia P99 muy estricta (<10ms): las bases de datos vectoriales especializadas tienen ventaja aquí
  • Búsqueda híbrida compleja: combinaciones de vectores + text search + facets a escala

En esos casos, Qdrant o Weaviate tienen sentido. Pero para el 80% de las aplicaciones RAG que se construyen hoy, pgvector es más que suficiente.

Configuración de producción que no puedes olvidar

-- Ajustar trabajo de memoria para construcción de índices
SET maintenance_work_mem = '1GB';

-- Para búsqueda HNSW en tiempo de query
SET hnsw.ef_search = 100;  -- más alto = más preciso, más lento

-- Monitorizar tamaño del índice
SELECT pg_size_pretty(pg_relation_size('documentos_embedding_idx'));

La configuración de maintenance_work_mem es especialmente importante: construir un índice HNSW con la configuración por defecto (64MB) en una colección de 1M de vectores puede tardar horas. Con 1-4GB, el mismo proceso tarda minutos.

Compartir

Dax Reyes

Sistemas y Kernel

// Relacionados

pgvector en producción: PostgreSQL como base de datos vectorial sin abandonar tu stack — SYNTHNODE