Bases de datos vectoriales explicadas: pgvector, embeddings y búsqueda semántica
Embeddings, HNSW, IVFFlat y la pregunta que de verdad importa: ¿necesitas una base de datos vectorial dedicada o te basta con la que ya tienes en producción?
Cuando un sistema de IA “recuerda” documentación, busca por significado en lugar de por palabra exacta, o alimenta a un LLM con el contexto correcto antes de responder, casi siempre hay una base de datos vectorial detrás. No es magia: es matemática de toda la vida (distancias entre puntos en un espacio de muchas dimensiones) aplicada a un problema muy concreto. Este artículo explica qué es un embedding, cómo se indexa un espacio vectorial para buscar en él sin comparar contra todos los puntos uno a uno, y por qué en 2026 la respuesta por defecto para la mayoría de equipos no es “instalar una base de datos vectorial nueva” sino “activar una extensión en el PostgreSQL que ya tienen”.
Qué es un embedding, en términos concretos
Un embedding es un vector: una lista de números de longitud fija que representa el significado de un texto (o una imagen, un audio, un fragmento de código) en un espacio de muchas dimensiones. Lo produce un modelo entrenado para eso —text-embedding-3-small de OpenAI, los modelos de Voyage AI, Cohere Embed o alternativas abiertas— y la propiedad que lo hace útil es esta: textos con significado similar producen vectores cercanos entre sí en ese espacio, aunque no compartan ni una palabra.
“El gato duerme en el sofá” y “El felino descansa en el sillón” no tienen vocabulario en común más allá de artículos y preposiciones, pero un buen modelo de embeddings los coloca muy cerca en el espacio vectorial. Esa es la diferencia estructural frente a la búsqueda de texto completo tradicional (como la de tsvector en PostgreSQL o Elasticsearch), que compara tokens y variantes léxicas: la búsqueda vectorial compara significado, no coincidencia de caracteres.
En la práctica, cada modelo de embeddings produce vectores de una dimensionalidad fija: 1.536 dimensiones en text-embedding-3-small de OpenAI, 3.072 en text-embedding-3-large, 1.024 en los modelos de Cohere. No son intercambiables entre sí: un vector generado por un modelo no es comparable con uno generado por otro, así que la elección del modelo de embeddings es una decisión que condiciona todo el sistema y que rara vez conviene cambiar a mitad de proyecto sin reprocesar todos los datos.
Por qué “comparar contra todo” no escala
La operación básica de búsqueda semántica es: dado un vector de consulta, encuentra los k vectores más cercanos de una colección (k-nearest neighbors, k-NN). La forma exacta de resolverlo es calcular la distancia contra cada vector almacenado y quedarte con los más cercanos: eso es una búsqueda exacta, con recall perfecto (nunca te pierdes el vecino real más cercano) pero con coste O(N) por consulta.
Con cientos de miles de vectores, eso ya empieza a doler en latencia. Con decenas de millones, es directamente inviable para una aplicación que responde en tiempo real. La solución es la misma que ya conoces de los índices relacionales —evitar recorrer todo para encontrar algo—, pero aplicada a un problema geométrico distinto: en lugar de un B-tree ordenado por valor, necesitas una estructura que aproxime “quién está cerca de quién” en un espacio de cientos o miles de dimensiones.
Ahí es donde entran los índices ANN (Approximate Nearest Neighbor): a cambio de renunciar a la garantía de encontrar siempre el vecino exacto más cercano, ganas velocidad de varios órdenes de magnitud. La pérdida de precisión (recall) es medible y ajustable: puedes decidir cuánta velocidad quieres cambiar por cuánta exactitud.
HNSW e IVFFlat: los dos índices que importan
pgvector, la extensión que trae búsqueda vectorial a PostgreSQL, ofrece dos índices ANN con filosofías distintas.
HNSW: un grafo navegable por capas
HNSW (Hierarchical Navigable Small World) construye un grafo en varias capas: la capa superior tiene pocos nodos con conexiones de “salto largo” que permiten atravesar el espacio rápido, y cada capa inferior añade más nodos con conexiones más densas y locales, hasta llegar a la capa base que contiene todos los vectores. Buscar significa entrar por la capa superior, moverse hacia el nodo más prometedor, y bajar de capa en capa refinando la búsqueda, de forma parecida a como bajas por un mapa de carreteras: autopistas primero, calles locales al final.
graph TD subgraph "Capa 2 (pocos nodos, saltos largos)" A2[Nodo A] --- B2[Nodo B] end subgraph "Capa 1" A1[Nodo A] --- C1[Nodo C] --- B1[Nodo B] A1 --- D1[Nodo D] end subgraph "Capa 0 (todos los vectores)" A0[A] --- E0[E] --- C0[C] --- F0[F] --- B0[B] A0 --- D0[D] --- F0 end A2 -.-> A1 -.-> A0 B2 -.-> B1 -.-> B0
Los parámetros que controlas al crear un índice HNSW en pgvector son:
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
m(por defecto 16): el número máximo de conexiones por nodo en cada capa. Más conexiones significan mayor recall pero un índice más grande y más lento de construir.ef_construction(por defecto 64): cuántos candidatos evalúa el algoritmo al insertar cada nodo durante la construcción. Subirlo mejora la calidad del grafo a costa de un build más lento.ef_search(por defecto 40, configurable por sesión): cuántos candidatos evalúa en cada búsqueda. Es el dial que ajustas en tiempo de consulta para intercambiar velocidad por recall sin reconstruir el índice:SET hnsw.ef_search = 100;.
HNSW ofrece mejor rendimiento de consulta que IVFFlat en la gran mayoría de escenarios, especialmente cuando se necesita recall alto, y no requiere una fase de entrenamiento previa: puedes construir el índice antes de cargar datos o según van llegando. El coste es un build más lento y un consumo de memoria notablemente mayor, porque cada nodo almacena sus conexiones de grafo además del propio vector.
IVFFlat: agrupar primero, buscar después
IVFFlat (Inverted File with Flat compression) sigue una lógica distinta: agrupa todos los vectores en lists clústeres mediante un algoritmo tipo k-means, y en cada búsqueda solo examina los vectores dentro de los clústeres cuyo centroide está más cerca del vector de consulta, en lugar de recorrer la colección entera.
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- En cada sesión de consulta:
SET ivfflat.probes = 10;
lists: cuántos clústeres crear. La recomendación habitual esfilas / 1000para colecciones de menos de un millón de vectores, y aproximadamentesqrt(filas)por encima de esa cifra.probes: cuántos clústeres examina cada consulta. Conprobes = 1solo mira el clúster más cercano al centroide (rápido, menos preciso); subirlo mejora el recall al coste de examinar más vectores.
IVFFlat necesita datos ya cargados antes de construir el índice, porque el paso de clustering necesita ejemplos reales para calcular los centroides; construirlo sobre una tabla vacía y rellenarla después produce clústeres mal calibrados. A cambio, el build es más rápido y el índice ocupa bastante menos memoria que un HNSW equivalente.
Sea cual sea el índice, recuerda que ambos son aproximados: existe la opción de no crear ningún índice y hacer sequential scan con distancia exacta, que sigue siendo razonable para colecciones pequeñas (unas pocas decenas de miles de filas) o cuando la exactitud importa más que la latencia.
pgvector en la práctica
Instalar y usar pgvector no exige salir de SQL:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documentos (
id bigserial PRIMARY KEY,
contenido text NOT NULL,
embedding vector(1536) -- dimensión del modelo usado, aquí text-embedding-3-small
);
-- Insertar un embedding ya calculado por tu modelo
INSERT INTO documentos (contenido, embedding)
VALUES ('Cómo configurar CORS en Express', '[0.021, -0.114, ...]');
-- Buscar los 5 documentos más cercanos semánticamente a una consulta
SELECT contenido, embedding <=> '[0.019, -0.108, ...]' AS distancia
FROM documentos
ORDER BY embedding <=> '[0.019, -0.108, ...]'
LIMIT 5;
El operador <=> es distancia coseno; <-> es distancia L2; <#> es producto interno (negado, así que valores más bajos siguen indicando más cercanía). pgvector también soporta halfvec (media precisión, hasta 4.000 dimensiones, la mitad de espacio en disco) y vectores binarios, útiles cuando el volumen de datos hace que cada byte cuente.
Lo interesante no es solo que puedas ejecutar búsqueda vectorial: es que puedes combinarla con SQL normal en la misma consulta, algo que una base de datos vectorial pura no ofrece de forma nativa:
-- Búsqueda semántica filtrada por metadatos relacionales reales
SELECT d.contenido
FROM documentos d
JOIN categorias c ON c.id = d.categoria_id
WHERE c.nombre = 'backend'
AND d.publicado_en > '2026-01-01'
ORDER BY d.embedding <=> '[0.019, -0.108, ...]'
LIMIT 5;
Esto es exactamente el tipo de consulta híbrida (semántica + filtros exactos + JOIN con otras tablas) que aparece constantemente en sistemas RAG reales: no basta con “los documentos más parecidos”, hace falta “los documentos más parecidos que además cumplen estas condiciones de negocio”. Si además necesitas que un agente de IA use esta búsqueda como una herramienta expuesta de forma estandarizada, MCP explicado a fondo cubre cómo exponer una consulta como esta a través de un servidor MCP.
pgvector frente a una base de datos vectorial dedicada
El mercado de bases de datos vectoriales se ha consolidado en 2026 alrededor de un puñado de opciones con filosofías distintas: Pinecone (gestionada, sin infraestructura que operar, con reranking e híbrido integrados), Qdrant (open source en Rust, filtrado muy rápido, buena relación precio-rendimiento autoalojado), Weaviate (búsqueda híbrida vector + texto completo + metadatos como funcionalidad de primera clase) y pgvector (la extensión sobre la base de datos relacional que ya tienes).
Las razones concretas para quedarte en pgvector en lugar de añadir una base de datos vectorial dedicada:
- Menos infraestructura que operar. Un sistema menos que parchear, monitorizar y hacer backup, con la disciplina operativa (roles, replicación, auditoría) que ya tienes en producción.
- Consultas híbridas nativas. Combinar similitud semántica con filtros relacionales exactos, agregaciones y
JOINs en una sola consulta SQL, sin sincronizar índices entre dos bases de datos. - Consistencia transaccional. Si insertas un documento y su embedding en la misma transacción que actualiza otras tablas, tienes garantías ACID reales; sincronizar un documento en PostgreSQL con su vector en un sistema externo introduce una ventana de inconsistencia que hay que gestionar a mano.
- Coste. No hay una factura adicional de un servicio gestionado por almacenar y consultar vectores; escalas lo que ya pagas.
Las razones para elegir una base de datos vectorial dedicada en su lugar:
- Escala por encima de varios millones de vectores con necesidad de latencia muy baja y consistente: los motores dedicados están optimizados específicamente para ese problema y suelen rendir mejor a esa escala sin ajuste manual fino.
- Funcionalidad de búsqueda avanzada de serie: reranking integrado, búsqueda híbrida vector + BM25 con fusión de resultados ya resuelta, filtrado de metadatos extremadamente rápido con estructuras propias (el caso de Qdrant).
- Multi-tenancy o aislamiento de datos vectoriales como requisito de producto, con un plano de control pensado específicamente para eso.
- Cero operación si no quieres (o no puedes) dedicar tiempo de ingeniería a dimensionar y mantener PostgreSQL bajo esa carga adicional: un servicio como Pinecone quita ese trabajo a cambio de una factura recurrente y menos control.
La pregunta que de verdad debes hacerte no es “¿cuál es mejor en abstracto?” sino “¿mi caso de uso está lejos de los límites de pgvector, o simplemente asumo que necesito algo especializado porque ‘vectorial’ suena a que hace falta una base de datos nueva?”. Si ya operas PostgreSQL en producción y tu colección de vectores se cuenta en cientos de miles o pocos millones, la respuesta casi siempre es: instala la extensión, no añadas un sistema.
Errores frecuentes al construir búsqueda semántica
- Mezclar embeddings de modelos distintos en la misma columna. Un vector de
text-embedding-3-small(1.536 dimensiones) y uno de un modelo de Cohere (1.024 dimensiones) no son comparables ni compatibles con la misma columnavector(n). Cambiar de modelo de embeddings implica reprocesar toda la colección. - No normalizar cuando la métrica lo requiere. Algunos modelos devuelven vectores ya normalizados (norma 1); si el tuyo no lo hace y usas distancia coseno sin darte cuenta de la diferencia, los resultados pueden ser inconsistentes con lo esperado. Comprueba la documentación del modelo.
- Ignorar el recall real del índice ANN. “Los resultados parecen razonables” no es lo mismo que medir el recall contra una búsqueda exacta con un conjunto de consultas de prueba. Vale la pena validar
ef_searchoprobescon datos reales antes de fijar los valores en producción. - Fragmentar documentos mal (chunking). La calidad de la búsqueda semántica depende tanto del índice como de cómo trocees el texto original antes de generar embeddings: fragmentos demasiado grandes diluyen el significado específico, demasiado pequeños pierden contexto. Es un problema de diseño de datos, no solo de base de datos.
- Rehacer embeddings innecesariamente. Generar embeddings tiene coste (dinero y latencia si usas una API). Cachea y reutiliza los vectores de contenido que no cambia; recalcula solo lo que se ha modificado.
La recomendación práctica
Si estás construyendo búsqueda semántica o un pipeline RAG y ya tienes PostgreSQL en producción, empieza por CREATE EXTENSION vector y un índice HNSW con los parámetros por defecto. Mide el recall y la latencia con tráfico real antes de considerar una base de datos vectorial dedicada: en la mayoría de proyectos, esa migración nunca llega a ser necesaria. Reserva la decisión de añadir un sistema especializado para cuando tengas datos concretos —volumen, latencia objetivo, funcionalidad que pgvector no cubre— que la justifiquen, no como punto de partida por defecto.
Artículos relacionados
RAG en 2026: arquitectura, embeddings y cuándo (no) usarlo
Chunking, embeddings, retrieval, reranking y generación: cómo es realmente un pipeline RAG en producción, y las señales de que estás sobreingenierizando.
PostgreSQL en 2026: por qué sigue siendo la base de datos por defecto
Extensiones, rendimiento y un ecosistema cloud (Neon, Supabase, RDS) que ha convertido a PostgreSQL en la opción segura para casi cualquier proyecto nuevo en 2026.
Cómo construir un agente de IA con Next.js y MCP
Una aplicación Next.js completa que habla con un servidor MCP propio: arquitectura, código real y las decisiones que cambian entre desarrollo local y producción.