Cada comparación de “pgvector vs Pinecone” en internet termina convirtiéndose, tarde o temprano, en una gráfica de benchmark de consultas por segundo en bruto con una cantidad fija de vectores, como si ese número por sí solo debiera decidir la arquitectura de tu sistema de recuperación. No debería. Los equipos que se arrepienten de su elección de base de datos vectorial seis meses después casi nunca se arrepienten por el rendimiento bruto, sino porque eligieron basándose en un benchmark en lugar de preguntarse si sus consultas filtradas, su capacidad operativa o sus requisitos de consistencia realmente encajaban con lo que eligieron.
La versión honesta de esta decisión tiene tres variables reales, y ninguna es “cuál es más rápida en aislamiento”: qué tan grande va a llegar a ser realmente el corpus, qué tan complejos son los filtros que deben ejecutarse junto con la búsqueda por similitud y cuánto presupuesto operativo dedicado existe para ejecutar y ajustar una nueva pieza de infraestructura. Obtén primero esas tres respuestas, y la pregunta pgvector-versus-base-de-datos-dedicada prácticamente se responde sola; no porque una sea universalmente mejor, sino porque están optimizadas para puntos genuinamente diferentes en esos tres ejes.
Aprenderás:
- Qué es realmente pgvector y qué significa mecánicamente “búsqueda vectorial dentro de Postgres”
- Las compensaciones reales entre los tipos de índice HNSW e IVFFlat de pgvector
- Por qué la complejidad de los filtros, y no la cantidad bruta de vectores, suele ser el factor decisivo
- Qué cambia operativamente cuando agregas una base de datos vectorial dedicada a tu stack
- Un marco de decisión concreto basado en tamaño del corpus, necesidades de filtrado y presupuesto de operaciones
- Cómo medir realmente el recall y la latencia sobre tus propios datos en lugar de confiar en una gráfica de un proveedor
Tabla de contenidos
- Qué es realmente pgvector
- Tipos de índice de pgvector: HNSW vs. IVFFlat
- Por qué la complejidad de los filtros importa más que la cantidad de vectores
- Qué te aporta realmente una base de datos vectorial dedicada
- El marco de decisión
- Medir recall y latencia sobre tus propios datos
- Ejecutar pgvector en producción
- Errores comunes
Qué es realmente pgvector
pgvector es una extensión de Postgres que añade un tipo de columna vector y un conjunto de operadores de distancia — <-> (Euclidiana/L2), <=> (distancia coseno), <#> (producto interno negativo) — además de tipos de índice construidos específicamente para búsqueda aproximada de vecinos más cercanos sobre esas columnas. No es un proceso lateral acoplado a la fuerza; los vectores viven en una tabla normal, junto con cualquier columna relacional que ya describa esa fila, y una consulta de similitud es una consulta SQL ordinaria con un ORDER BY sobre un operador de distancia.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
tenant_id INT NOT NULL,
published_at TIMESTAMPTZ NOT NULL,
content TEXT NOT NULL,
embedding VECTOR(1536)
);
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> '[0.012, -0.034, ...]'::vector
LIMIT 10;
Esta es toda la propuesta en una sola consulta: búsqueda vectorial en Postgres que se compone de forma natural con WHERE, JOIN, transacciones y cualquier otra herramienta relacional que ya exista en el stack, en lugar de vivir en un sistema separado que debe mantenerse sincronizado con la fuente de verdad. Como el embedding vive en la misma fila que todo lo demás —la misma transacción que inserta un documento nuevo puede calcular y almacenar su embedding, sin una ruta de escritura separada que haya que mantener consistente— no existe una ventana de consistencia eventual entre “el documento existe” y “el documento se puede buscar”, lo cual es una propiedad real de corrección, aunque a menudo poco valorada, frente a sincronizar una base de datos fuente de verdad con un índice de búsqueda independiente. Esa composabilidad es también exactamente donde aparecen sus límites, como veremos a continuación.
Tipos de índice de pgvector: HNSW vs. IVFFlat
Sin un índice, ORDER BY embedding <=> ... es una búsqueda exacta de vecinos más cercanos: calcula la distancia a cada fila y luego ordena, lo cual es correcto pero escala linealmente con el tamaño de la tabla. Más allá de unas pocas decenas de miles de filas, esto se vuelve lo bastante lento como para necesitar un índice aproximado, y pgvector incluye dos.
IVFFlat particiona el espacio vectorial en un número fijo de clústeres (lists) mediante k-means, y una consulta solo busca en los probes clústeres más cercanos al vector de consulta en lugar de recorrer toda la tabla. Es más barato de construir y más pequeño en disco que HNSW, pero necesita construirse después de que ya se haya cargado una cantidad representativa de datos (un índice IVFFlat construido sobre una tabla vacía o no representativa produce clústeres mal formados), y el recall se degrada de forma medible a medida que se añaden nuevos datos sin reconstruir.
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
SET ivfflat.probes = 10; -- higher = better recall, slower query
HNSW (Hierarchical Navigable Small World) construye una estructura de grafo multicapa que conecta vectores cercanos, y se ha convertido en la recomendación predeterminada para nuevos despliegues de pgvector porque no necesita la advertencia de “construir después de cargar datos representativos” que sí tiene IVFFlat, y normalmente ofrece un recall significativamente mejor con una latencia de consulta comparable. El coste es un proceso de construcción más lento, que consume más memoria, y una huella en disco mayor que IVFFlat con la misma cantidad de vectores.
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
SET hnsw.ef_search = 40; -- higher = better recall, slower query
Ambos tipos de índice son aproximados: intercambian una pequeña cantidad de recall por grandes mejoras de velocidad, y ambos exponen un parámetro ajustable (probes para IVFFlat, ef_search para HNSW) que te permite desplazarte por esa curva de recall frente a latencia en tiempo de consulta sin reconstruir el índice. Usa HNSW por defecto para trabajo nuevo; recurre a IVFFlat específicamente cuando el tiempo de construcción o la memoria durante la creación del índice sean la restricción principal.
Por qué la complejidad de los filtros importa más que la cantidad de vectores
La mayoría de las comparaciones se obsesionan con la cantidad bruta de vectores como factor decisivo, pero el problema más difícil en una aplicación real casi siempre es la búsqueda por similitud filtrada —“encuentra los 10 documentos más similares para este tenant, publicados en los últimos 30 días, excluyendo los archivados"— y no la búsqueda sin filtros sobre todo el corpus.
Esto importa porque los índices aproximados de vecinos más cercanos y los filtros WHERE no se combinan automáticamente bien. Si el filtro se aplica después de que el índice ANN devuelve sus mejores candidatos, un filtro muy selectivo puede dejarte con muchas menos filas de las que pediste con LIMIT, porque la búsqueda ANN encontró sus vecinos más cercanos antes de saber que la mayoría de ellos serían descartados por el filtro. pgvector maneja esto razonablemente bien porque es una base de datos relacional real: el planificador de consultas puede optar por filtrar primero con un índice normal sobre tenant_id y published_at y solo después ejecutar la búsqueda por distancia sobre el conjunto reducido, exactamente el tipo de decisión del planificador que la guía de EXPLAIN ANALYZE te enseña a leer y verificar:
EXPLAIN ANALYZE
SELECT id, content
FROM documents
WHERE tenant_id = 42
AND published_at > now() - interval '30 days'
AND status != 'archived'
ORDER BY embedding <=> '[0.012, ...]'::vector
LIMIT 10;
Si el plan muestra que el filtro de tenant_id/published_at se ejecuta antes del recorrido del índice ANN y reduce cómodamente el conjunto de candidatos, filtrar es barato en este caso. Si el corpus está dominado por un solo tenant, o el filtro está sobre un campo con poca selectividad, el planificador puede terminar recorriendo más del grafo HNSW de lo esperado para satisfacer al mismo tiempo el filtro y el LIMIT, y ese es un coste real que conviene medir, no ignorar por suposición. Históricamente, muchas bases de datos vectoriales diseñadas específicamente para ello manejaban la búsqueda filtrada prefiltrando todo el conjunto candidato (costoso) o posfiltrando después de la búsqueda ANN (inexacto), y cerrar esa brecha ha sido un área activa de desarrollo en toda la categoría; vale la pena comprobar el comportamiento actual de búsqueda filtrada de un proveedor concreto en lugar de confiar en una reputación general, en cualquier dirección.
Qué te aporta realmente una base de datos vectorial dedicada
Una base de datos vectorial dedicada — Pinecone, Weaviate, Qdrant, Milvus — se gana su lugar gracias a un conjunto de prioridades de ingeniería genuinamente diferente al de una base de datos relacional de propósito general extendida con un tipo vectorial:
- Escalado horizontal diseñado desde el inicio. Fragmentar un índice vectorial entre muchos nodos para servir un corpus de cientos de millones a miles de millones de vectores es un problema resuelto y convertido en producto en estos sistemas; escalar pgvector más allá de los límites prácticos de una sola instancia de Postgres implica fragmentar manualmente o apoyarse en herramientas de escalado específicas de Postgres (Citus, réplicas de lectura) que no fueron diseñadas específicamente para cargas vectoriales.
- Herramientas operativas orientadas al propósito. Reconstrucciones de índices en vivo sin tiempo de inactividad, búsqueda híbrida densa+m dispersa y aislamiento de espacios de nombres multi-tenant son características de primera clase y bien documentadas, en lugar de algo ensamblado a partir de primitivas de propósito general.
- Infraestructura gestionada por defecto. La mayoría de las opciones dedicadas se ofrecen como servicio gestionado: sin planificación de capacidad para la huella de memoria de HNSW, sin ajuste manual del índice, a cambio de una nueva relación con un proveedor, una nueva factura y un nuevo sistema que monitorizar y proteger.
- Integraciones del ecosistema. Las bases de datos vectoriales gestionadas suelen incluir conectores de primera clase para los frameworks populares de embeddings y RAG, lo que puede ahorrar un tiempo real de integración en un proyecto nuevo frente a conectar pgvector a esos mismos frameworks manualmente, aunque esta diferencia se ha reducido a medida que maduró el soporte de ecosistema de pgvector.
Nada de esto hace que una base de datos vectorial dedicada sea estrictamente “mejor”; la convierte en la herramienta adecuada específicamente cuando la escala del corpus o las necesidades funcionales superan un umbral que una sola instancia de Postgres, por bien afinada que esté, no fue diseñada para sobrepasar.
| pgvector | Base de datos vectorial dedicada | |
|---|---|---|
| Mejor rango de corpus | hasta ~10M vectores cómodamente | decenas de millones a miles de millones |
| Filtros relacionales complejos | nativo — el mismo planificador de consultas que para todo lo demás | a menudo acoplado después, mejorando pero variable según el proveedor |
| Consistencia con los datos fuente | misma transacción, sin retraso de sincronización | ruta de escritura separada, ventana de sincronización/consistencia |
| Sobrecarga operativa | casi nula si ya ejecutas Postgres | un nuevo sistema gestionado o autohospedado que ejecutar |
| Escalado horizontal | manual (sharding, Citus) | incorporado |
| Modelo de coste | parte del gasto existente en Postgres | facturación separada basada en uso |
Usa esta tabla como una comprobación rápida de sentido común frente al marco siguiente, no como la decisión en sí misma: la complejidad real de los filtros y el recall medido sobre tus propios datos tienen prioridad sobre cualquier tabla general como esta.
El marco de decisión
Valora estos tres ejes de forma conjunta en lugar de aislar cualquiera de ellos:
Tamaño del corpus. Por debajo de unos pocos millones de vectores, pgvector sobre hardware razonablemente aprovisionado maneja con comodidad tanto el tiempo de construcción como la latencia de consulta para la mayoría de las aplicaciones. Decenas de millones ya empiezan a requerir atención real al ajuste de HNSW (m, ef_construction, RAM disponible para el grafo). Cientos de millones a miles de millones es donde la historia de escalado horizontal de un sistema dedicado empieza a importar más de lo que cualquier ajuste de un solo nodo puede compensar.
Complejidad de los filtros. Los filtros simples y de baja cardinalidad (unos pocos IDs de tenant, un indicador booleano) son baratos en cualquiera de las dos arquitecturas. Las combinaciones complejas, de alta cardinalidad y que cambian con frecuencia son donde ser una base de datos relacional genuina constituye la ventaja estructural más fuerte de pgvector: el planificador de consultas ya sabe optimizar combinaciones arbitrarias de filtros, porque esa es precisamente la disciplina para la que una base de datos de propósito general se ha perfeccionado durante décadas.
Presupuesto operativo. Si el equipo ya ejecuta y monitoriza Postgres, añadir pgvector es operativamente casi gratis: es una sentencia CREATE EXTENSION y un índice, no un sistema nuevo. Poner en marcha una base de datos vectorial dedicada sí es una nueva pieza real de infraestructura: un nuevo despliegue, nuevas credenciales y rutas de red que proteger, un nuevo panel que monitorizar y un nuevo modo de fallo para el que tener un runbook de guardia. Merece la pena pagar ese coste cuando la escala del corpus o las necesidades funcionales realmente lo exigen, y es sobrecarga pura cuando no.
Una regla práctica concreta: empieza con pgvector si el corpus está por debajo de aproximadamente diez millones de vectores, los filtros se apoyan en datos que ya son relacionales (tenant, fecha, estado, permisos) y el equipo no dispone ya de un equipo especializado separado de infraestructura de datos. Pásate a una base de datos vectorial dedicada cuando el crecimiento del corpus apunte claramente mucho más allá de ese rango, cuando las funciones de búsqueda híbrida o jerárquica se conviertan en un requisito estricto, o cuando la carga de búsqueda vectorial sea lo bastante grande y variable como para que aislarla del presupuesto de recursos de la base de datos transaccional primaria tenga valor por sí misma.
Medir recall y latencia sobre tus propios datos
Todos los benchmarks de proveedores usan un conjunto de datos y una distribución de consultas que puede no parecerse en nada a los embeddings, filtros o forma del corpus de la aplicación real. El único benchmark en el que merece la pena confiar es uno ejecutado contra datos reales (o sintéticos realistas):
import time
import numpy as np
def recall_at_k(approx_results, exact_results, k=10):
approx_ids = set(r[0] for r in approx_results[:k])
exact_ids = set(r[0] for r in exact_results[:k])
return len(approx_ids & exact_ids) / k
# Compare HNSW results against an exact (no-index) scan on the same query
exact = conn.execute(
"SELECT id FROM documents ORDER BY embedding <=> %s LIMIT 10", [qvec]
).fetchall()
approx = conn.execute(
"SET hnsw.ef_search = 40; SELECT id FROM documents ORDER BY embedding <=> %s LIMIT 10", [qvec]
).fetchall()
print(recall_at_k(approx, exact))
Ejecuta esto sobre una muestra representativa de consultas reales, no sobre un ejemplo escogido a mano, y prueba distintos valores de ef_search o probes frente a la latencia medida para encontrar dónde se aplana la curva de recall para tus datos reales; ese es el número que debería guiar la configuración de ef_search/probes en producción, no un valor predeterminado copiado de la documentación.
Ejecutar pgvector en producción
Hacer que pgvector funcione en un notebook es una tarea de cinco minutos; ejecutar cargas de trabajo de pgvector en producción de forma fiable significa tratar el índice igual que tratarías cualquier otro objeto crítico de rendimiento en Postgres: planificado, monitorizado y reconstruido deliberadamente en lugar de dejarlo solo después del primer CREATE INDEX. Construir un índice HNSW sobre una tabla con millones de filas lleva tiempo y memoria reales, y un CREATE INDEX simple bloquea las escrituras en la tabla durante todo el proceso: el mismo comportamiento de bloqueo que tiene cualquier construcción de índice en Postgres, tratado con más profundidad en la guía de migraciones de Postgres sin tiempo de inactividad. Usa CREATE INDEX CONCURRENTLY para un índice vectorial sobre una tabla activa exactamente igual que harías con cualquier otro tipo de índice, y presupuesta un tiempo realista para ello: las construcciones HNSW consumen tanta CPU y memoria que tardan significativamente más que una construcción B-tree equivalente sobre el mismo número de filas.
maintenance_work_mem también importa más aquí que en una construcción típica de B-tree: la construcción de HNSW consume mucha memoria, y un maintenance_work_mem demasiado pequeño puede hacer que la construcción sea muchísimo más lenta de lo necesario. Súbelo específicamente para la sesión de construcción en lugar de hacerlo globalmente, si el presupuesto normal de memoria de trabajo del servidor es más ajustado de lo que una gran construcción HNSW necesita.
Errores comunes
Error: construir un índice IVFFlat antes de cargar datos representativos. El paso de clustering k-means necesita datos reales para formar buenos clústeres; un índice construido sobre una tabla vacía o diminuta produce clústeres deficientes que perjudican el recall de forma permanente hasta que se reconstruye. Solución: carga primero una muestra representativa, o usa HNSW por defecto, que no tiene este requisito de orden.
Error: asumir que una consulta filtrada con un filtro muy selectivo es automáticamente rápida. Un filtro muy selectivo combinado con un índice ANN puede, en algunos planes, implicar recorrer más del índice de lo esperado para satisfacer a la vez el filtro y el límite de filas. Solución: ejecuta EXPLAIN ANALYZE sobre tus consultas filtradas reales, no solo sobre la búsqueda de similitud sin filtros.
Error: elegir una base de datos vectorial dedicada basándose solo en una gráfica de benchmark. Los benchmarks de proveedores rara vez coinciden con la complejidad de tus filtros, la dimensionalidad de tus embeddings o tu distribución de consultas. Solución: mide recall y latencia sobre tus propios datos antes de comprometerte con cualquiera de las dos arquitecturas.
Error: tratar la decisión pgvector-vs-dedicada como permanente e irreversible. Muchos equipos empiezan con pgvector, y la ruta de migración posterior a un sistema dedicado —una vez que el tamaño del corpus o las necesidades funcionales realmente lo justifican— es algo muy recorrido, no una excepción rara. Solución: usa por defecto primero la opción de menor coste operativo, salvo que ya tengas evidencia clara de que el corpus cruzará el umbral a partir del cual deja de ser suficiente.
Error: omitir el ajuste de maintenance_work_mem durante la construcción del índice. Una construcción HNSW lenta y hambrienta de memoria puede tardar muchísimo más de lo necesario y, en casos extremos, desbordarse de formas que perjudiquen la calidad de la construcción. Solución: aumenta maintenance_work_mem para la sesión de construcción antes de ejecutar un CREATE INDEX CONCURRENTLY ... USING hnsw grande.
Conclusión
pgvector y una base de datos vectorial dedicada no compiten por el mismo trabajo: están optimizadas para puntos distintos en tamaño del corpus, complejidad de filtros y presupuesto operativo, y la elección correcta surge de ser honesto sobre dónde se encuentra realmente tu aplicación en esos tres ejes, en lugar de fijarte en qué sistema ganó una gráfica de benchmark. Empieza con pgvector cuando el corpus sea moderado y los filtros sean principalmente relacionales, ya que su coste operativo sobre una infraestructura que probablemente ya ejecutas es casi nulo; pásate a un sistema dedicado cuando la escala o las necesidades funcionales realmente lo superen, y valida ese cambio con cifras de recall y latencia medidas sobre tus propios datos, no sobre los de otra persona.
¿Has medido realmente el recall sobre tus propias consultas, o la configuración de tu índice sigue funcionando con los valores predeterminados de la documentación?
