Python & Data Science
LLMs y GenAI En revisión

Comparativa de bases de datos vectoriales: cuándo realmente necesitas una

La última vez, Rae aprendió qué son los embeddings —cómo convierten el texto en coordenadas que capturan el significado y por qué superan a la búsqueda por palabras clave para los documentos de ayuda de su bot de soporte—. Vio que la similitud del coseno podía hacer coincidir la pregunta de un cliente con el artículo de ayuda correcto incluso cuando no compartían ninguna palabra. Pero se quedó con un problema práctico: con miles de fragmentos de documentos de ayuda para buscar, ¿cómo almacena y consulta todos estos vectores rápidamente?

Digamos que Rae está construyendo una biblioteca para la documentación del producto de su empresa. En una biblioteca tradicional, los libros se archivan por título o autor. Quieres “El gran Gatsby”, vas a la sección ‘F’ de Fitzgerald. Así es como funcionan las bases de datos SQL: coincidencias exactas o rangos específicos.

Pero, ¿qué pasa si un cliente entra y dice: “Quiero un libro que transmita la sensación de una tarde de lluvia solitaria en París”? Un índice estándar no te puede ayudar en ese caso. Necesitas entender el significado detrás de la solicitud. Este es el mundo de los embeddings y las bases de datos vectoriales, y es el siguiente problema que Rae necesita resolver para sus miles de fragmentos embebidos de documentos de ayuda.

1. El problema: por qué tu base de datos normal se siente lenta

Los embeddings convierten texto o imágenes en listas largas de números — vectores. Para encontrar el elemento más similar, calculas la distancia entre tu vector de búsqueda y cada vector en la base de datos.

Las bases de datos SQL estándar no están diseñadas para esto. Un índice B-Tree maneja price < 20 bien, pero no tiene idea de qué hacer con un vector de 1536 dimensiones. Sin un índice especializado, la base de datos recurre a un Full Table Scan.

Aquí hay una búsqueda ingenua en solo 10,000 elementos en Python estándar.

import numpy as np
import time

# Create 10,000 random vectors (1536 dimensions, like OpenAI embeddings)
data = np.random.rand(10000, 1536).astype('float32')
query_vector = np.random.rand(1536).astype('float32')

def naive_search(query, library):
    # Calculate cosine similarity manually for every item
    # This is an O(n) operation
    similarities = np.dot(library, query) / (np.linalg.norm(library, axis=1) * np.linalg.norm(query))
    return np.argsort(similarities)[-5:]

start = time.time()
results = naive_search(query_vector, data)
end = time.time()

print(f"Search took: {(end - start) * 1000:.2f} ms")
Este bloque genera datos sintéticos para medir el costo de la búsqueda de vectores por fuerza bruta. `np.random.rand(10000, 1536)` crea una matriz de 10,000 filas, cada una con 1,536 dimensiones (coincidiendo con el tamaño de embedding de OpenAI). La función `naive_search` calcula la similitud del coseno dividiendo el producto punto de cada fila contra la consulta por el producto de sus normas L2—`np.linalg.norm(library, axis=1)` normaliza cada fila. `np.argsort` devuelve los índices que ordenarían el arreglo de similitudes, y `[-5:]` toma los 5 más similares. El uso de `time.time()` alrededor del código mide el costo real en tiempo de reloj.

En mi máquina, esto se ejecuta en aproximadamente 40-60ms — no está mal para 10,000 elementos. Pero escala a 1 millón y estarás viendo de 4 a 6 segundos. ¿Tienes 1,000 usuarios buscando a la vez? Tu servidor se derrite. La matemática detrás de la búsqueda de vectores es simple; la escala es lo que duele.

2. Qué hacen realmente las bases de datos vectoriales

Una base de datos vectorial no es simplemente un depósito de almacenamiento. Es un motor especializado que hace tres cosas:

  1. Almacenamiento: Almacena arreglos numéricos densos de manera eficiente.
  2. Indexación: Utiliza algoritmos de “Approximate Nearest Neighbor” (ANN) como HNSW (Hierarchical Navigable Small World). Piensa en un mapa de metro que te permite saltarte paradas para llegar rápido cerca de tu destino.
  3. Filtrado de metadatos: Te permite decir: “Encuéntrame imágenes similares, pero SOLO de la categoría ‘Naturaleza’”.

Así es como consultarías una base de datos vectorial frente a una base de datos convencional con la extensión pgvector.

# Hypothetical comparison of API complexity

# 1. pgvector (SQL approach)
# "SELECT * FROM items ORDER BY embedding <=> '[0.1, 0.2...]' LIMIT 5 WHERE category = 'books'"

# 2. Qdrant (Vector DB approach)
# client.search(
#     collection_name="my_items",
#     query_vector=[0.1, 0.2, ...],
#     query_filter=Filter(must=[FieldCondition(key="category", match=MatchValue(value="books"))]),
#     limit=5
# )
Esta es una comparación conceptual lado a lado, no código ejecutable. El enfoque de pgvector utiliza SQL estándar con el operador `<=>` para la distancia coseno, familiar para cualquiera que haya escrito una consulta `SELECT`. El enfoque de Qdrant utiliza un cliente de Python con un objeto `Filter` que contiene comparadores `FieldCondition`. Ambos logran el mismo objetivo: búsqueda de similitud combinada con filtrado de metadatos, pero la ruta SQL vive dentro de tu instancia existente de Postgres, mientras que la ruta de Qdrant requiere un servicio separado.

Las bases de datos vectoriales manejan la cláusula “Where” y la búsqueda de similitud al mismo tiempo. Las bases de datos convencionales a menudo tienen dificultades para optimizar ambos en conjunto.

3. Los compromisos: velocidad vs. simplicidad vs. costo

Tienes tres caminos principales. Los números cuentan la historia.

CaracterísticaEn memoria (FAISS)PostgreSQL (pgvector)Base de datos vectorial (Qdrant/Pinecone)
Latencia< 1ms100-500ms10-50ms
ComplejidadBaja (solo un archivo)Media (base de datos existente)Alta (nuevo servicio)
EscalaDiminuta (limitada por RAM)Media (Millones)Masiva (Miles de millones)

Aquí tienes una rápida simulación de benchmark que compara los tres.

# Simulating the latency trade-offs
results = {
    "FAISS (In-Memory)": "0.8ms - Blazing fast, but loses data if script crashes",
    "pgvector (Postgres)": "120ms - Convenient, uses your existing database",
    "Qdrant (Dedicated)": "15ms - Optimized for high-speed production use"
}

for system, stat in results.items():
    print(f"{system}: {stat}")
Este bloque simula las compensaciones de latencia imprimiendo números representativos para tres niveles de almacenamiento. El diccionario asigna cada nombre de sistema a una cadena que describe su latencia y compensación típicas. El bucle `for` itera sobre cada par clave-valor y los imprime, haciendo que la comparación sea legible de un vistazo.

4. Cuándo vale la pena una base de datos vectorial

No compres un camión semi-remolque para mover una sola caja. Con menos de 100,000 documentos, probablemente no necesites una base de datos vectorial especializada.

Vale la pena si:

  • Necesitas latencias inferiores a 100ms: Cuando tu aplicación se siente lenta, es posible que Postgres sea el cuello de botella.
  • Alto volumen de consultas: Miles de personas buscando al mismo tiempo.
  • Metadatos complejos: Necesitas filtrar por 10 atributos diferentes mientras buscas por similitud.

5. Un ejemplo real: construyendo una función de búsqueda semántica

Digamos que estamos construyendo un buscador para una aplicación de recetas.

Etapa 1: El Prototipo (FAISS) Comienzas con 5,000 recetas. FAISS es gratuito y vive directamente en tu script de Python. Es rápido. Pero añadir una receta significa reconstruir todo el índice.

Etapa 2: Crecimiento (pgvector) Llegas a 50,000 recetas y quieres una base de datos real. Añades pgvector a tu instancia de Postgres. Ahora tienes copias de seguridad y transacciones. Sin embargo, al superar 100,000, el uso de tu CPU se dispara durante las búsquedas.

Etapa 3: Las Grandes Ligas (Qdrant/Pinecone) Tienes 1 millón de recetas y 100,000 usuarios. Es momento de una base de datos vectorial dedicada.

# Example of how a recommendation recommendation changes based on scale
def recommend_storage(doc_count, queries_per_sec):
    if doc_count < 10000 and queries_per_sec < 5:
        return "Use FAISS or simple Numpy"
    elif doc_count < 100000:
        return "Use pgvector (Postgres)"
    else:
        return "Use a dedicated Vector DB (Qdrant/Weaviate/Pinecone)"

print(f"Recommendation for 500k docs: {recommend_storage(500000, 50)}")
Esta función toma dos argumentos—cantidad de documentos y consultas por segundo—y devuelve una recomendación de almacenamiento basada en umbrales simples. Con menos de 10,000 documentos y poco tráfico, sugiere FAISS o Numpy. Con menos de 100,000 documentos, sugiere pgvector. Por encima de eso, una base de datos vectorial dedicada. La llamada `print` demuestra la función con 500k documentos y 50 consultas/seg, lo cual cae en el nivel de base de datos dedicada.

6. Panorama de bases de datos vectoriales: ¿cuál elegir?

  • Pinecone: El “botón fácil”. Es totalmente administrado, pero los costos pueden escalar rápidamente.
  • Qdrant: Rápido y de código abierto. Una opción sólida si quieres autohospedarlo.
  • Weaviate: Construido sobre GraphQL, y una buena opción para estructuras de datos complejas.
  • pgvector: La opción natural si ya estás en Postgres y aún no tienes una escala masiva.

¿Qué almacenamiento vectorial debería usar Rae en una startup pequeña?

FAISS (en memoria, archivo local) — Usa esto para la creación de prototipos y el desarrollo local. Si Rae está probando su pipeline de recuperación en su laptop con unos pocos cientos de fragmentos, FAISS es perfecto: cero infraestructura, búsquedas submilisegundo, sin dependencias más allá de pip install faiss-cpu. Pero vive en la RAM: si su script falla, el índice desaparece. Sin filtrado de metadatos, sin persistencia, sin concurrencia.

pgvector (extensión de Postgres) — El punto ideal para la mayoría de las startups en fase inicial, incluida Rae ahora mismo. Si ella ya tiene una base de datos de Postgres (y la mayoría de las startups la tienen), pgvector añade búsqueda de similitud vectorial sin levantar un nuevo servicio. Obtiene transacciones, copias de seguridad, joins con sus tablas existentes y la familiar interfaz SQL. El compromiso: con alrededor de 100k–500k vectores y una carga de consultas pesada, las búsquedas se ralentizan a 100–500ms porque Postgres no fue diseñado para cargas de trabajo ANN de alta dimensionalidad.

Qdrant / Pinecone / Weaviate (base de datos vectorial dedicada) — Recurre a estas opciones cuando pgvector comience a ceder bajo la carga—generalmente cuando superas los ~500k vectores, necesitas una latencia sub-50ms con alta concurrencia, o quieres funciones integradas como búsqueda híbrida y sharding. Pinecone es totalmente administrado (sin DevOps) pero se vuelve caro rápidamente. Qdrant es de código abierto y autohospedable, lo que se ajusta al presupuesto de Rae pero requiere que alguien cuide la infraestructura. Weaviate ofrece consultas basadas en GraphQL para esquemas complejos.

Recomendación actual de Rae: Comienza con pgvector. Ella ya ejecuta Postgres para los datos de usuario de su aplicación, así que añadir una columna vectorial no implica nueva infraestructura. Si el tráfico de su bot tiene picos y pgvector no da abasto, puede migrar a Qdrant (autohospedado, gratuito) sin cambiar su pipeline de embeddings—solo la capa de almacenamiento.

Cuándo evitar por completo una base de datos vectorial dedicada:

  • Si tienes menos de 100k vectores y un volumen de consultas modesto—pgvector es más simple y económico.
  • Si no tienes personal de DevOps—gestionar un servicio de base de datos independiente es un costo oculto.
  • Si necesitas transacciones ACID en tus datos vectoriales y relacionales—dividirlos en dos almacenes introduce los problemas de sincronización descritos en la Sección 7.

7. Los inconvenientes que nadie menciona

Las bases de datos vectoriales no son mágicas. Dos compromisos a tener en cuenta:

  1. Deriva de embeddings: Cambia de modelos de embeddings —de OpenAI a Cohere, por ejemplo— y tienes que recalcular cada vector en tu base de datos. Eso es una reelaboración importante.
  2. Sincronización de metadatos: Actualiza el nombre de un usuario en Postgres, y también necesitas actualizarlo en tu base de datos vectorial. Si omites este paso, tus filtros serán incorrectos.
# The Sync Problem
postgres_user = {"id": 1, "status": "inactive"}
vector_db_user = {"id": 1, "status": "active"} # Oops! Out of sync.

if postgres_user['status'] != vector_db_user['status']:
    print("Warning: Your Vector DB is returning 'inactive' users because of a sync lag!")
Este bloque ilustra un problema de consistencia de datos del mundo real al usar dos almacenes separados. Dos diccionarios representan al mismo usuario (id 1) pero discrepan en `status`—Postgres dice "inactive" mientras que la base de datos vectorial todavía tiene "active". La verificación `if` compara ambos e imprime una advertencia, simulando lo que sucede cuando Postgres se actualiza pero la base de datos vectorial separada aún no se ha sincronizado: los resultados de búsqueda incluirían a usuarios que deberían haber sido filtrados.

8. Marco de decisión: ¿deberías usar una?

Hazte tres preguntas:

  1. ¿Tengo más de 100,000 elementos?
  2. ¿Mi búsqueda tarda más de 200ms?
  3. ¿Tengo a una persona de DevOps que pueda administrar otra base de datos?

Si las tres respuestas son no, quédate con Postgres o con una búsqueda simple en memoria.

9. Lo que sigue: búsqueda híbrida y más allá

La búsqueda vectorial es excelente para las “vibras” pero tiene problemas con palabras específicas. Busca “iPhone 15” y una búsqueda vectorial podría devolver “Samsung Galaxy” porque son semánticamente similares.

A continuación: Búsqueda híbrida. Combinaremos el “significado” de la búsqueda vectorial con la “exactitud” de la búsqueda por palabras clave.

Lista de verificación resumida:

  • Usa FAISS para scripts locales y prototipos pequeños.
  • Usa pgvector para la mayoría de las startups en fase inicial.
  • Usa Qdrant/Pinecone cuando llegues a millones de filas o necesites velocidad extrema.

Rae ha tomado su decisión: pgvector. Ya ejecuta Postgres para los datos de usuario de su aplicación, así que añadir una columna vectorial supone cero nueva infraestructura. Con el almacenamiento resuelto, está lista para construir el sistema real: no solo recuperación, sino un pipeline RAG completo que divide el manual de su producto en fragmentos, genera los embeddings de cada fragmento, recupera los correctos cuando un cliente hace una pregunta y los alimenta a un LLM para generar una respuesta. Eso es lo que construye a continuación.

Comprueba tu comprensión

Las preguntas a continuación van desde un simple recuerdo hasta un diseño abierto, siguiendo aproximadamente la Taxonomía de Bloom.

Recordar ¿Cuáles son las tres cosas que hace una base de datos vectorial, según la Sección 2 del artículo?

Comprender En tus propias palabras, explica por qué un índice B-Tree estándar (ideal para price < 20) no puede ayudar con una búsqueda de similitud sobre vectores de 1536 dimensiones.

Aplicar Usando la función recommend_storage del artículo, ¿qué devolvería para una startup con 75,000 documentos y 3 consultas por segundo?

Analizar El ejemplo del “Problema de Sincronización” del artículo muestra a Postgres y a la base de datos vectorial discrepando sobre el status de un usuario. Explica paso a paso por qué este tipo de desincronización es un riesgo estructural específicamente para la arquitectura de “dos almacenes separados” (Postgres + base de datos vectorial) que no existiría si usaras pgvector dentro de tu única base de datos Postgres.

Evaluar La advertencia “Embedding Drift” del artículo dice que cambiar de modelos de IA significa recalcular cada vector en la base de datos. Critica las tres preguntas de sí/no del marco de decisión (cantidad de elementos, latencia, capacidad de DevOps) por no mencionar este costo en absoluto: ¿debería “con qué frecuencia esperamos cambiar de modelos de embeddings” ser una cuarta pregunta en ese marco, y por qué la respuesta de un equipo a ella podría cambiar su elección de almacenamiento incluso si respondió “no” a las tres preguntas enumeradas?

Crear Diseña un plan de escalado por fases (siguiendo el ejemplo de la aplicación de recetas del artículo) para un nuevo producto: una función de búsqueda de tickets de soporte al cliente que comienza con 2,000 tickets y se espera que crezca a 2 millones en 18 meses. Usando el patrón de tres etapas del artículo (FAISS → pgvector → base de datos vectorial dedicada), especifica aproximadamente cuándo migrarías en cada etapa y qué señal (de las advertencias del artículo o del marco de decisión) desencadenaría cada migración.

Artículos relacionados

Referencias y lecturas adicionales


Esta traducción fue generada automáticamente y puede contener errores. Si el idioma inglés es tu preferencia, puedes leer el artículo original en inglés .

«Aplica lo que aprendiste» es para suscriptores Supporter e Insider.

Suscríbete para desbloquear los ejercicios de este artículo.

Ver planes

¿Buscas otra cosa?

Busca en todos los artículos por título, resumen o tema.