Cómo funciona un vector database por dentro: HNSW y IVF
Un cliente me escribió porque su buscador "se había vuelto tonto". Metías la frase exacta de un documento y ese documento no aparecía entre los primeros cinco resultados. A veces ni entre los primeros veinte.
El equipo sospechó del modelo de embeddings. Lo cambiaron dos veces. Mismo comportamiento.
Nadie se preguntó cómo funciona un vector database por dentro. Daban por hecho que "buscar por similitud" era comparar la consulta contra todos los vectores guardados y devolver los más parecidos. Eso sí habría encontrado el documento, siempre.
El problema era el contrario: no estaban comparando contra todos. Usaban un índice que recorre solo una fracción del dataset, y nadie sabía que puede —por diseño, no por bug— dejar fuera al vecino real.
En corto: un vector database no compara tu consulta contra cada vector guardado. Usa un índice de approximate nearest neighbor (ANN) —típicamente HNSW o IVF— que navega una estructura para encontrar vecinos muy probablemente cercanos sin tocar el resto del dataset. A cambio de esa velocidad, el resultado deja de estar garantizado al 100%.
¿Qué es un vector database?
Un vector database indexa vectores de alta dimensión —cientos o miles de números por registro— para responder "¿qué está más cerca de esto?" en milisegundos, sin recorrer todo el dataset.
La palabra clave es "cerca". Una base relacional indexa para responder "¿qué fila es igual a X?" o "¿qué está entre A y B?". Un B-tree hace eso bien porque los números tienen orden total: 5 va antes que 8, siempre.
Un embedding de 768 o 1536 dimensiones no tiene ese orden. No hay un "antes" entre dos vectores, solo una distancia en un espacio que no puedes dibujar. Por eso un B-tree no sirve aquí: no es lento, resuelve otro problema.
Fuerza bruta: por qué no escala comparar contra todos
La forma honesta es la más simple: calculas la distancia de tu consulta contra cada vector guardado, ordenas y te quedas con los k más cercanos.
# búsqueda exacta por fuerza bruta — siempre correcta, siempre O(n)
def buscar_exacto(consulta, vectores, k):
distancias = [(distancia(consulta, v), v) for v in vectores]
distancias.sort(key=lambda x: x[0])
return distancias[:k]
Es exacta, nunca se equivoca, y es inviable a escala porque el coste es O(n) por consulta: recalcula distancia contra cada vector, sin excepción.
Con mil vectores no lo notas. Con diez millones, cada consulta son diez millones de cálculos de distancia antes del primer resultado. Los índices ANN existen para que un vector database evite ese barrido completo.
HNSW: el grafo en capas que evita mirarlo todo
HNSW (Hierarchical Navigable Small World) organiza los vectores de tu vector database en un grafo de varias capas, del paper original de Malkov y Yashunin (2016, arXiv:1603.09320). No todos los vectores se conectan entre sí, solo con sus vecinos aproximados —y algunos, al azar, se replican también en capas superiores más dispersas.
La capa de arriba tiene pocos nodos con conexiones largas. Cada capa hacia abajo tiene más nodos y conexiones más cortas, hasta la capa base, que contiene todos los vectores.
Buscar es navegar de arriba a abajo: entras por un punto fijo en la capa superior, saltas al vecino más cercano hasta que ninguno mejora la distancia, y bajas una capa. Repites hasta la capa base, donde exploras un grupo más amplio y devuelves los k mejores. Es la lógica de una skip list: saltos largos para acercarte, cortos para afinar. Así el paper logra una complejidad de búsqueda cercana a logarítmica, no lineal.
Y aquí el trato que nadie lee hasta que le muerde: HNSW hace ANN, no exact nearest neighbor. La navegación golosa —saltar siempre al vecino más próximo— puede quedarse en un óptimo local y devolver el segundo o tercer vecino real, no el primero. No es un bug: es el precio de no comparar contra todo.
El parámetro que controla cuánto exploras en la capa base suele llamarse ef_search: más candidatos, más cerca del resultado exacto, más lenta la consulta. Cuántos vecinos conecta cada nodo se controla con m. No hay un valor universal para ninguno: depende de tu dataset.
IVF: particionar el espacio en clusters
IVF (Inverted File Index) agrupa los vectores en clusters —normalmente con k-means— y guarda, por cada cluster, la lista de vectores que le pertenecen. En la búsqueda comparas primero tu consulta contra los centroides, no contra los vectores, y solo entras en detalle dentro de los clusters más cercanos.
El parámetro equivalente a ef_search aquí es nprobe (en pgvector, ivfflat.probes): cuántos clusters revisas por consulta. Con nprobe = 1 solo miras el más cercano, con riesgo real de que el vecino real esté en el cluster de al lado. Subir nprobe sube el recall y baja la velocidad — el mismo trade-off que en HNSW, con otro nombre.
IVF suele pesar menos en memoria porque no guarda un grafo con punteros por vecino en cada capa. A cambio, un buen índice depende de que el k-means inicial refleje bien la forma real de tus datos.
Qué métrica de distancia usar
"Cerca" no significa lo mismo según qué mides:
- Euclidiana (L2): distancia en línea recta. Sensible a la magnitud del vector.
- Similitud coseno: mide el ángulo entre dos vectores, ignorando su magnitud.
- Producto interno: combina ángulo y magnitud. Un vector más "largo" puede ganar aunque apunte peor.
La trampa habitual: si tus embeddings están normalizados (magnitud = 1, lo que hacen muchos modelos por defecto), coseno y producto interno dan el mismo ranking, porque la magnitud es idéntica para todos. Ahí la elección es cuestión de coste computacional, no de cuál es "más correcta". Sin normalizar, euclidiana y coseno sí pueden ordenar distinto —revisa qué asume tu modelo antes de fijar la métrica.
HNSW vs IVF, cara a cara
| Criterio | HNSW | IVF |
|---|---|---|
| Velocidad de búsqueda | Muy alta, escala cerca de forma logarítmica | Alta, depende directamente de nprobe |
| Memoria | Mayor — grafo de vecinos en cada capa, más los vectores | Menor — solo vectores agrupados por cluster |
| Recall "de fábrica" | Buena incluso con parámetros conservadores | Depende mucho de clusters y nprobe |
| Coste de construir el índice | Alto — cada inserción busca y conecta vecinos | Más barato — k-means inicial y asignación por vector |
| Actualizaciones (insert/delete) | Delicado — en pgvector las tuplas eliminadas quedan en el grafo hasta el VACUUM (issue #244) |
Más simple, aunque un cambio grande pide re-clusterizar |
| Cuándo usarlo | Cabe en memoria, latencia mínima consistente | Datasets enormes donde la memoria manda |
Ninguno es "mejor" en abstracto: reparten distinto la memoria, la velocidad de construcción y el recall. Es justo el tipo de decisión de arquitectura que discutimos cada semana en Dominicode Labs, donde el trade-off cambia según el caso real, no según la benchmark del paper.
El trade-off que gobierna todo
Todo parámetro de un índice ANN —ef_search, m, nprobe, el número de clusters— mueve el mismo dial en direcciones opuestas. Explorar más candidatos sube el recall y sube la latencia. Un grafo más denso o más clusters mejoran la búsqueda, y pesan más en memoria y tardan más en construirse.
No hay un valor correcto en general para ningún vector database: depende de tu dataset, tu distribución de consultas y cuánta latencia puedes pagar. Sin conocer tu caso, cualquier número es solo un punto de partida para medir tú mismo.
El how-to práctico de montar búsqueda híbrida con embeddings en Supabase entra en el paso a paso de producción. Este post es el mecanismo de por qué esos números existen.
Cuándo un vector database NO es la respuesta
Para filtros exactos y booleanos sigue haciendo falta un índice tradicional. "Dame los documentos del usuario 4821 publicados después del 1 de marzo" no es una pregunta de similitud, es de igualdad y rango, y un B-tree la resuelve mejor. Casi todo motor serio combina ambos.
ANN nunca garantiza el resultado exacto, ni con parámetros altos. Es la consecuencia directa de no comparar contra todo el dataset: HNSW puede quedarse en un óptimo local, IVF puede dejar el vecino real fuera de los clusters revisados. Sube ef_search o nprobe todo lo que quieras, seguirá siendo una apuesta, no una garantía.
Reindexar a escala no es gratis. Un HNSW con millones de vectores tarda en construirse, y si tu pipeline lo reconstruye en cada actualización, ese coste se paga en cada despliegue. Es justo lo que describe el hilo de "The Case Against PGVector" en Hacker News: construir el índice puede consumir más de 10 GB de RAM durante horas, y mantenerlo sincronizado con inserciones continuas complica el pipeline entero. El issue de pgvector sobre tuplas muertas es el mismo problema desde otro ángulo.
Con poco volumen, la fuerza bruta gana. Con unos pocos miles de vectores, mantener un índice ANN puede costar más que comparar contra todo. Mide antes de asumir que necesitas HNSW.
Qué hacer con esto hoy
Si tienes un RAG o un agente con búsqueda vectorial en producción, ve a la configuración del índice ahora. Busca ef_search, nprobe o el equivalente en tu motor. Si está en el valor por defecto y nadie lo ha tocado, ese es tu primer sospechoso la próxima vez que un resultado "obvio" no aparezca.
Y si ese índice te lo montó un agente de IA sin que nadie revisara qué parámetros eligió, ese es el tipo de decisión silenciosa que cubro en el ebook gratuito Revisión por Contrato: límites explícitos a lo que un agente decide por ti sin que lo notes.
Entender el mecanismo no te ahorra elegir los parámetros, pero deja de sorprenderte que la búsqueda sea aproximada: elegiste velocidad sobre fuerza bruta.
Si estás construyendo el sistema completo —agente, ingestión, capa de recuperación—, es la decisión de arquitectura que trabajamos en el curso Construye con IA. Y si todavía dudas entre montar un vector database o resolverlo de otra forma, en RAG vs fine-tuning vs contexto: cuándo usar cada uno explico cuándo compensa cada camino.
Preguntas frecuentes
¿Qué es un índice HNSW?
Un grafo en varias capas que organiza los vectores para no compararlos todos contra todos. La capa superior tiene pocos nodos con conexiones largas; hacia abajo hay más nodos y conexiones más cortas, hasta la capa base con todos los vectores. Del paper original de Malkov y Yashunin (arXiv:1603.09320).
¿Por qué la búsqueda vectorial a veces no encuentra el resultado "correcto"?
Porque HNSW e IVF son índices de approximate nearest neighbor: sacrifican precisión por velocidad. HNSW puede quedarse en un óptimo local; IVF puede dejar el vecino real en un cluster no revisado. No es un fallo, es la consecuencia de no comparar contra todo el dataset.
¿Qué métrica de distancia debo usar: coseno, euclidiana o producto interno?
Con embeddings normalizados (magnitud 1), coseno y producto interno dan el mismo ranking: la elección es cuestión de coste computacional. Sin normalizar, euclidiana y coseno pueden ordenar distinto porque uno considera la magnitud y el otro la ignora.
¿Un vector database sustituye a mi base de datos relacional?
No. Para filtros exactos o por rango —igualdad, fechas, IDs— un índice tradicional sigue siendo más rápido. La mayoría de los sistemas en producción combinan ambos: filtran con índices relacionales y comparan por similitud dentro de ese subconjunto.
¿Cuándo compensa usar fuerza bruta en vez de HNSW o IVF?
Con datasets pequeños —unos pocos miles de vectores— mantener un índice ANN puede costar más que comparar contra todos directamente. La fuerza bruta es O(n) por consulta, pero exacta y sin parámetros que ajustar.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.
