Postgres Es Todo Lo Que Necesitas
El ano pasado evalue cuatro bases de datos vectoriales
para un sistema RAG en produccion. Lei todos los
benchmarks, asisti a tres demos de proveedores y construi
integraciones de prueba de concepto con Pinecone, Weaviate
y Qdrant.
Despues lance pgvector en la instancia de Postgres que ya
teniamos corriendo. Me tomo una tarde.
Seis meses despues, ese sistema atiende 50,000 busquedas
semanticas por dia con latencia p95 por debajo de 40ms.
El costo total de infraestructura para busqueda vectorial
es $0 adicionales al mes porque corre en la misma base de
datos que almacena usuarios, sesiones y datos de la
aplicacion.
El Ciclo de Hype de las Bases de Datos Vectoriales
El capital de riesgo invirtio mas de $350M en startups de
bases de datos vectoriales en 2023-2024. Pinecone levanto
$138M. Weaviate levanto $50M. Qdrant levanto $28M. Ese
dinero necesita justificarse, lo que significa presupuestos
de marketing dirigidos directamente a convencerte de que
Postgres no puede manejar tus embeddings.
Asi suena el pitch: "Necesitas una base de datos vectorial
construida a proposito para IA en produccion. Postgres no
fue disenado para busqueda de similitud en alta dimension.
Vas a chocar contra muros de escalabilidad."
Ese pitch no esta equivocado para todos. Esta equivocado
para cerca del 90% de los equipos construyendo features de
IA hoy.
La mayoria de los sistemas RAG en produccion tienen menos
de 5M de vectores. La mayoria de los volumenes de consulta
estan por debajo de 100 QPS. La mayoria de los requisitos
de latencia son "menos de 200ms." Si eso describe tu
sistema, eres el cliente objetivo de un servicio de
$200K/ano que no necesitas.
Lo Basico de pgvector
pgvector es una extension de Postgres que agrega tipos de
datos vectoriales y operadores de busqueda por similitud.
Lo habilitas con una linea:
CREATE EXTENSION IF NOT EXISTS vector;
Eso te da un tipo de columna vector, tres operadores de
distancia (<-> para L2, <=> para coseno, <#> para
producto interno), y tres estrategias de indexacion.
Flat Scan (Sin Indice)
Sin indice. Postgres escanea cada fila y calcula la
distancia. 100% de recall, pero rendimiento O(n).
Funciona para tablas con menos de 10,000 filas. Lo uso
para desarrollo y tablas de consulta pequenas.
IVFFlat
Indice de archivo invertido. Particiona vectores en
clusters (llamados "lists"), luego busca solo en los
clusters mas cercanos al momento de la consulta. Tu
eliges el numero de lists y cuantas explorar.
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
Buen recall a velocidad razonable. La trampa: necesitas
construir el indice despues de insertar tus datos, y la
calidad depende de elegir el numero correcto de lists.
Regla general: lists = filas / 1000 para hasta 1M de
filas.
HNSW
Hierarchical Navigable Small World graph. Este es el que
quieres para produccion. Construye una estructura de grafo
multicapa que permite busqueda en tiempo logaritmico con
recall configurable.
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
HNSW tarda mas en construirse pero ofrece mejor
rendimiento de consulta que IVFFlat y no requiere
reconstruccion despues de inserciones. Los dos parametros
que importan:
- m: conexiones por nodo (default 16, mayor = mejor
recall pero mas memoria)
- ef_construction: amplitud de busqueda en
construccion (default 64, mayor = mejor recall pero
builds mas lentos)
La Realidad del Rendimiento
Corri benchmarks en una instancia Supabase Pro (4 GB RAM,
CPU de 2 nucleos) con 1M de vectores a 1536 dimensiones
(tamano de salida de OpenAI text-embedding-3-small).
Esto es lo que medi:
| Metrica | HNSW | IVFFlat | Pinecone p1 |
|---|---|---|---|
| Latencia p50 | 8ms | 12ms | 5ms |
| Latencia p95 | 22ms | 35ms | 11ms |
| Latencia p99 | 38ms | 58ms | 18ms |
| Recall@10 | 0.98 | 0.95 | 0.99 |
| QPS (conexion unica) | 120 | 85 | 200 |
Pinecone es mas rapido. Sin duda. Un indice vectorial
construido a proposito corriendo en hardware optimizado
le ganara a una base de datos de proposito general. Pero
mira los numeros reales. 22ms vs 11ms en p95. Para un
pipeline RAG donde el paso de generacion del LLM toma
800-2000ms, estas discutiendo por 11 milisegundos.
Recall de 0.98 significa que de cada 100 consultas,
pgvector devuelve los mismos top-10 resultados que una
busqueda exacta 98 veces. El 2% donde difiere? Los
resultados "incorrectos" siguen siendo semanticamente
cercanos -- generalmente items rankeados 11vo o 12vo en
el ordenamiento exacto.
Ajuste ef_search (la amplitud de busqueda en consulta)
a 100:
SET hnsw.ef_search = 100;
Eso llevo el recall a 0.99 con p95 en 28ms. Aun bien
dentro del rango aceptable para cualquier aplicacion de
cara al usuario.
La Ventaja de Supabase
Corro mi Postgres en Supabase, que viene con pgvector
habilitado por defecto. Cero configuracion. El mismo
connection string que mi app de Next.js usa para auth de
usuarios y datos de la aplicacion tambien sirve consultas
de busqueda vectorial.
Esto importa mas que los benchmarks. Aqui esta por que:
Un servicio menos que monitorear. Sin dashboard
separado de base de datos vectorial, sin SLA de uptime
adicional que rastrear, sin segundo conjunto de
credenciales de acceso que rotar.
Un salto de red menos. Mi aplicacion consulta
usuarios, embeddings y metadata en una sola consulta SQL
con JOINs. Una base de datos vectorial dedicada significa
una llamada API separada, un round trip separado, y la
complejidad de correlacionar resultados entre dos stores
de datos.
Una sorpresa de facturacion menos. Supabase Pro
cuesta $25/mes. Se exactamente cuanto cuesta. El pricing
de bases de datos vectoriales es por vector, por consulta,
por dimension, por namespace -- un modelo de precios
disenado para escalar con tu exito y castigarte por ello.
Consistencia transaccional. Cuando inserto un nuevo
documento, el contenido de texto, los metadatos y el
embedding aterrizan en la misma transaccion. No hay lag
de sincronizacion entre mi base de datos de aplicacion y
mi indice vectorial. Sin dolores de cabeza de consistencia
eventual.
Cuando pgvector NO Es Suficiente
Seria deshonesto si te dijera que pgvector resuelve todo.
Aqui estan los casos donde una base de datos vectorial
dedicada justifica su costo:
Mas de 10M de vectores. A esta escala, los tiempos de
construccion del indice HNSW se extienden a horas y los
requisitos de memoria exceden lo que una instancia
estandar de Postgres provee. Pinecone y Qdrant hacen
sharding entre maquinas de forma nativa. pgvector no.
Requisitos de latencia p99 por debajo de 10ms. Si
estas construyendo un sistema de recomendaciones en
tiempo real donde cada milisegundo cuenta (piensa en
servicio de anuncios, senales de trading), el rango de
20-40ms p99 de pgvector no va a funcionar.
Aislamiento multi-tenant a escala masiva. Tienes 1000
tenants cada uno con 1M de vectores. Los namespaces en
Pinecone manejan esto limpiamente. En pgvector, estas
particionando tablas o filtrando con clausulas WHERE, y
ninguno escala tan elegantemente pasado cierto punto.
Busqueda acelerada por GPU. Algunas cargas de trabajo
necesitan el throughput que la busqueda vectorial en GPU
provee. pgvector corre solo en CPU.
Si tu caso de uso cae en alguno de estos, la base de
datos vectorial dedicada vale el dinero. Pero se honesto
sobre si estas resolviendo el problema de hoy o un
problema que imaginas tener en 18 meses.
Implementacion: Supabase + pgvector
Aqui esta el setup completo que uso en produccion. Cinco
pasos de cero a busqueda semantica.
1. Crear la Tabla
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
embedding VECTOR(1536),
created_at TIMESTAMPTZ DEFAULT NOW()
);
2. Agregar el Indice HNSW
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
Puse ef_construction en 200 para produccion. Mas alto
que el default, pero solo afecta el tiempo de
construccion, no el de consulta. Vale el costo inicial
para mejor recall.
3. Crear una Funcion de Busqueda
CREATE OR REPLACE FUNCTION match_documents(
query_embedding VECTOR(1536),
match_threshold FLOAT DEFAULT 0.78,
match_count INT DEFAULT 10
)
RETURNS TABLE (
id BIGINT,
content TEXT,
metadata JSONB,
similarity FLOAT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
d.id,
d.content,
d.metadata,
1 - (d.embedding <=> query_embedding) AS similarity
FROM documents d
WHERE 1 - (d.embedding <=> query_embedding)
> match_threshold
ORDER BY d.embedding <=> query_embedding
LIMIT match_count;
END;
$$;
4. Insertar Embeddings (TypeScript)
import { createClient } from '@supabase/supabase-js'
import OpenAI from 'openai'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!
)
const openai = new OpenAI()
async function insertDocument(
content: string,
metadata: Record<string, unknown>
) {
const { data: embeddingData } =
await openai.embeddings.create({
model: 'text-embedding-3-small',
input: content,
})
const embedding =
embeddingData[0].embedding
const { error } = await supabase
.from('documents')
.insert({
content,
metadata,
embedding,
})
if (error) throw error
}
5. Busqueda Semantica (TypeScript)
async function searchDocuments(query: string) {
const { data: embeddingData } =
await openai.embeddings.create({
model: 'text-embedding-3-small',
input: query,
})
const queryEmbedding =
embeddingData[0].embedding
const { data, error } = await supabase
.rpc('match_documents', {
query_embedding: queryEmbedding,
match_threshold: 0.78,
match_count: 5,
})
if (error) throw error
return data
}
Esa es la implementacion completa. Sin SDK para un
servicio vectorial separado. Sin gestion de API keys para
otro proveedor. Sin pipeline de sincronizacion de datos.
Solo SQL y el cliente de Supabase que ya tienes.
Comparacion de Costos: Pinecone vs. pgvector
Cotice ambas opciones para una carga de trabajo realista:
500K vectores a 1536 dimensiones, 50K consultas por dia,
una sola region.
| | Pinecone (s1) | Supabase Pro |
|---|---|---|
| Costo base | $70/mes | $25/mes |
| Almacenamiento (500K vecs) | incluido | incluido |
| Costos de consulta | incluido | incluido |
| Escrituras (10K/dia) | incluido | incluido |
| Infra adicional | $0 | $0 |
| Total | $70/mes | $25/mes |
El pod s1 de Pinecone a $70/mes maneja esta carga de
trabajo. Supabase Pro a $25/mes tambien la maneja -- y
ademas corre tu auth, tu base de datos de aplicacion,
tus suscripciones en tiempo real y tu almacenamiento.
Pero el costo real no es la factura. Es el tiempo de
ingenieria.
Agregar Pinecone significa: un nuevo SDK que aprender,
un pipeline de sincronizacion de datos que construir y
mantener, una configuracion de monitoreo separada,
rotacion de credenciales para otro servicio, un camino
de migracion si superas el pod s1 o Pinecone cambia
precios, y la carga cognitiva de razonar sobre
consistencia de datos entre dos stores.
Estimo ese overhead en 2-4 horas de ingenieria por mes
para un equipo pequeno. A $150/hora con carga completa,
eso son $300-600/mes en costos ocultos. Mas que la
infraestructura misma.
La Tesis de la Tecnologia Aburrida
Dan McKinley escribio "Choose Boring Technology" en 2015.
El argumento: cada eleccion de tecnologia que haces gasta
de un presupuesto limitado de innovacion. Solo puedes
sostener unas pocas tecnologias novedosas a la vez antes
de que tu equipo se ahogue en complejidad operacional.
Postgres es la tecnologia mas aburrida que conozco. Ha
estado en produccion desde 1996. Cada ingeniero en tu
equipo lo ha usado. Tu infraestructura de monitoreo,
backup y failover ya lo maneja.
pgvector convierte a Postgres en una base de datos
vectorial lo suficientemente buena. No la mas rapida. No
la mas rica en features. Pero lo suficientemente buena
para la gran mayoria de cargas de trabajo de IA en
produccion. Y lo hace sin gastar nada de tu presupuesto
de innovacion.
He lanzado tres features de IA sobre pgvector. Ninguna
requirio migrar a una base de datos vectorial dedicada
despues. La unica vez que lo considere, me di cuenta de
que el cuello de botella era mi estrategia de chunking,
no el indice vectorial. Arregle el problema real y segui
adelante.
La brecha de rendimiento de 10-20% entre pgvector y una
solucion dedicada es real. Pero es una brecha que
intercambias por: un servicio menos, una relacion con
proveedor menos, un modo de falla menos, y una cosa
menos que explicarle al ingeniero de guardia a las 3 AM.
Ese intercambio vale la pena casi siempre.
Empieza Aqui
Si estas evaluando bases de datos vectoriales, intenta
esto antes de firmar un contrato:
- Habilita pgvector en tu Postgres existente
- Carga tus embeddings reales (no un dataset de
benchmark)
- Corre tus consultas reales y mide la latencia
- Compara esa latencia con tus requisitos reales
La mayoria de los equipos descubren que pgvector cumple
sus necesidades antes de terminar el paso 3. Los que no,
generalmente saben exactamente por que, lo que hace el
caso para una solucion dedicada mucho mas claro.
La mejor decision de infraestructura es la que no tienes
que tomar. Ya corres Postgres. Usalo.