Skip links
Ilustración conceptual de una base de datos vectorial con vectores en espacio multidimensional y búsqueda por similitud

Base de datos vectorial: comparativa y elección para RAG 2026

Cuando construyes un sistema RAG (Retrieval-Augmented Generation) en producción, la elección de la base de datos vectorial determina la latencia de cada consulta, el coste de almacenamiento a escala y la complejidad operativa que tu equipo asume el día uno. No es una decisión de infraestructura periférica: es la columna vertebral del pipeline de recuperación. Si los vectores llegan tarde o mal filtrados, el LLM no puede compensarlo.

Este artículo es una guía técnica comparativa orientada a equipos que ya están implementando o van a implementar agentes de IA para empresas con capacidades de recuperación semántica. Cubrimos cómo funcionan internamente las vector databases, qué algoritmos ANN usan, cómo hacer hybrid search con metadata filtering, y una comparativa real entre Pinecone, Weaviate, Qdrant, Chroma y pgvector con código Python ejecutable para cada opción.

Para quién es: AI engineers y backend developers que tienen ya experiencia básica con LLMs y necesitan decidir sobre qué vector store construir su capa de retrieval. Para quién no es: si buscas una introducción a qué son los LLMs o qué es la IA en general, este no es el punto de entrada.

Tabla de contenidos

  1. ¿Qué es una base de datos vectorial y por qué la necesitas?
  2. Cómo funciona: embeddings + similarity search (ANN)
  3. Algoritmos ANN: HNSW, IVF, ScaNN
  4. Filtrado híbrido: metadata + vector search
  5. Comparativa: Pinecone vs Weaviate vs Qdrant vs Chroma vs pgvector
  6. Self-hosted vs managed: cuándo elegir cada uno
  7. Implementación práctica (código Python con 3 vector stores)
  8. Performance: latencia, escalabilidad, costes
  9. Preguntas frecuentes sobre bases de datos vectoriales
  10. Conclusión

¿Qué es una base de datos vectorial y por qué la necesitas?

Una base de datos vectorial es un sistema de almacenamiento especializado en indexar y buscar vectores de alta dimensionalidad de forma eficiente. A diferencia de una base de datos relacional —donde las filas tienen tipos escalares (texto, enteros, fechas)— una vector database almacena embeddings: arrays de números flotantes de 768, 1.536 o incluso 3.072 dimensiones que representan semántica. La unidad de búsqueda no es la igualdad exacta, sino la similitud en el espacio vectorial.

La diferencia con un índice vectorial simple (como el que puedes construir con NumPy o FAISS) es que una vector database añade capas que son imprescindibles en producción:

  • Persistencia: los vectores sobreviven reinicios y actualizaciones del sistema.
  • Filtrado por metadata: puedes combinar búsqueda semántica con filtros SQL-like (campo de fecha, categoría, tenant ID).
  • Escalabilidad horizontal: particionado y replicación para millones o miles de millones de vectores.
  • API REST/gRPC: integración desacoplada con el pipeline de embedding y el LLM.
  • Actualizaciones y borrados: CRUD completo sobre colecciones vivas, imprescindible cuando tus documentos cambian.

El rol de la vector database en un pipeline RAG

En un pipeline de retrieval augmented generation, la vector database es el componente que ejecuta el paso de retrieval. El flujo es: el documento se fragmenta (chunking), cada fragmento se convierte en un embedding mediante un modelo, ese embedding se almacena en la vector database, y cuando llega una query del usuario, se embebe también y se buscan los K fragmentos más similares. Esos fragmentos se inyectan en el prompt del LLM como contexto. Sin un almacén vectorial que pueda hacer este retrieval en menos de 100 ms con millones de vectores, el sistema no es usable en tiempo real.

¿Cuándo necesitas específicamente una vector database?

No todo proyecto necesita una vector database dedicada desde el día uno. Necesitas una cuando: (a) el volumen de documentos supera los 100.000 fragmentos, momento en que FAISS en memoria empieza a ser inmanejable operacionalmente; (b) necesitas filtros por metadata (tenant, fecha, categoría) combinados con búsqueda semántica; (c) el sistema tiene múltiples servicios que acceden a los mismos vectores de forma concurrente; o (d) necesitas actualizar documentos de forma incremental sin reindexar todo el corpus. Para proyectos muy pequeños (demos, PoCs con <10K chunks), un índice FAISS serializado en disco puede ser suficiente. Para producción real con agentes de IA multi-tenant, necesitas una vector database.

Terminología técnica clave

Antes de entrar en comparativas, es útil tener claros los términos que usaremos: embedding es el vector numérico que representa un texto; colección (o índice, según la terminología de cada engine) es el namespace donde se agrupan vectores del mismo tipo; top-K es el número de resultados más similares que devuelve una query; ANN (Approximate Nearest Neighbor) es el tipo de búsqueda que hacen todas las vector databases modernas porque buscar el vecino exacto en alta dimensionalidad es NP-hard; y similarity score es la métrica (cosine, dot product, L2) que cuantifica cuán parecidos son dos vectores.

Para entender por qué las vector databases son distintas de cualquier otro sistema de almacenamiento, hay que entender el problema que resuelven: buscar los K vectores más similares a un vector de query entre millones de vectores indexados, en milisegundos.

Del texto al vector: el proceso de embedding

Los embeddings semánticos son representaciones numéricas de texto (o imagen, audio, etc.) en un espacio vectorial continuo. Modelos como text-embedding-3-small de OpenAI, embed-english-v3.0 de Cohere, o nomic-embed-text de Nomic AI convierten cualquier fragmento de texto en un vector de dimensionalidad fija. La propiedad clave es que textos semánticamente similares producen vectores que están cerca en ese espacio, medidos por distancia coseno o producto escalar.

El modelo de embedding determina la calidad del retrieval más que cualquier otra decisión técnica. Un embedding de baja calidad produce clusters ruidosos en el espacio vectorial y ningún vector store puede compensarlo. La elección del modelo (dimensionalidad, dominio, multilingüismo) es anterior a la elección del vector store.

Por qué la búsqueda exacta es inviable y qué es ANN

La búsqueda exacta del vecino más cercano (kNN exacto) requiere calcular la distancia entre el vector de query y todos los vectores del índice. Con 10 millones de vectores de 1.536 dimensiones, eso implica ~60 billones de operaciones de punto flotante por query. Incluso con GPUs modernas, la latencia es inaceptable para tiempo real. Las vector databases resuelven esto con algoritmos ANN (Approximate Nearest Neighbor) que sacrifican una fracción de recall (la fracción de vecinos verdaderos que se recuperan) a cambio de una aceleración de órdenes de magnitud. En la práctica, algoritmos como HNSW logran recall >0.95 con latencias de 1-10 ms en colecciones de millones de vectores.

Métricas de similitud: cosine, dot product, L2

La elección de la métrica de similitud no es trivial: cosine similarity normaliza la magnitud y solo mide el ángulo entre vectores, siendo robusta ante variaciones de longitud del texto; el dot product (producto escalar) es más rápido y es la métrica preferida cuando los vectores están pre-normalizados (como en la mayoría de embedding models modernos); la distancia L2 euclidiana mide distancia absoluta en el espacio y es útil para ciertos tipos de clustering pero menos intuitiva para similaridad semántica. La mayoría de stacks RAG usan dot product o cosine. Elegir la métrica incorrecta respecto al modelo de embedding puede reducir el recall en un 15-20%.

Algoritmos ANN: HNSW, IVF, ScaNN

Los tres algoritmos ANN más relevantes para builders de sistemas RAG son HNSW, IVF y ScaNN. Cada uno tiene un trade-off distinto entre velocidad de indexación, velocidad de query, uso de memoria y recall. Conocer estos trade-offs es lo que permite elegir la configuración correcta del vector store para tu caso de uso.

HNSW: el estándar de facto para latencia baja

HNSW (Hierarchical Navigable Small World), introducido en el paper de Malkov y Yashunin de 2016 (arXiv:1603.09320), construye un grafo jerárquico multicapa sobre los vectores. Las capas superiores tienen pocas conexiones de largo alcance (para navegación rápida) y las capas inferiores son densas (para precisión). Una query empieza en la capa más alta y desciende de forma greedy hasta la capa cero, donde se recogen los candidatos finales. Los parámetros clave son M (número de conexiones por nodo en capas superiores, típicamente 16-64) y ef_construction (tamaño del conjunto dinámico durante construcción). HNSW domina en latencia de query (1-10 ms) pero requiere almacenar el grafo completo en RAM, lo que lo hace costoso a escala de cientos de millones de vectores.

IVF: particionado por clusters para datasets masivos

IVF (Inverted File Index) primero aplica k-means para dividir el espacio vectorial en nlist celdas (centroides de Voronoi). Durante el indexado, cada vector se asigna a su centroide más cercano. En tiempo de query, se busca primero en los nprobe centroides más cercanos al vector de query, limitando la búsqueda a solo esa fracción del índice. El trade-off: IVF consume mucha menos RAM que HNSW porque no necesita el grafo, pero tiene mayor latencia de query y su recall cae si nprobe es bajo. Es la opción estándar en FAISS y está disponible en Weaviate como índice flat para vectores cuantizados. IVF con cuantización de producto (IVF-PQ) puede comprimir vectores 4-8x a costa de algo de recall.

ScaNN: el enfoque de Google para escala extrema

ScaNN (Scalable Nearest Neighbors), desarrollado por Google Research, combina particionado anisotrópico con cuantización de vectores y re-ranking en dos fases. La idea central es que no todos los errores de aproximación tienen el mismo coste: los errores en la dirección radial (magnitud) impactan menos el ranking que los errores angulares, por lo que ScaNN optimiza la cuantización de forma asimétrica. En los benchmarks de ann-benchmarks.com, ScaNN supera a HNSW en throughput (queries por segundo) en datasets >10M vectores, aunque a costa de una indexación más lenta y un proceso de tuning más complejo. No está disponible como opción nativa en la mayoría de vector databases open-source, pero sí en Vertex AI Matching Engine de Google.

El hybrid search —combinar filtros de metadata con búsqueda vectorial— es uno de los casos de uso más comunes en sistemas RAG empresariales y también uno de los más técnicamente complejos de implementar correctamente. En un sistema multi-tenant, por ejemplo, necesitas que las queries de un cliente solo recuperen sus propios documentos, combinando un filtro tenant_id = "cliente_A" con la búsqueda semántica.

Pre-filtering vs post-filtering

Hay dos estrategias arquitectónicas para combinar metadata y vector search: pre-filtering y post-filtering. En pre-filtering, el motor primero aplica los filtros de metadata para obtener un subconjunto de vectores candidatos, y luego ejecuta la búsqueda ANN solo sobre ese subconjunto. El problema es que si el subconjunto es muy pequeño, el grafo HNSW pierde sus propiedades de navegación eficiente y el recall cae. En post-filtering, el motor ejecuta la búsqueda ANN sobre el índice completo y luego filtra los resultados por metadata. El problema es que el top-K devuelto puede tener menos de K resultados tras el filtro si la metadata es selectiva. La solución correcta es un índice que mantiene la metadata como parte del grafo y puede podarla durante la travesía: esto es lo que implementan Qdrant (con su FilterContext), Weaviate (con su motor WAND) y Pinecone (con sus metadata indexes paralelos).

Hybrid search con sparse + dense vectors

Otro modo de hybrid search, distinto del filtering, es combinar búsqueda densa (embeddings semánticos) con búsqueda sparse (BM25 o TF-IDF sobre tokens exactos). Esto es especialmente valioso cuando el corpus contiene términos técnicos muy específicos, números de referencia o acrónimos que los modelos de embedding no codifican bien. Weaviate soporta nativo hybrid search sparse+dense con parámetro alpha para ponderar ambas señales. Qdrant lo implementa vía colecciones con vectores sparse separados (tipo SparseVector con el índice HNSW configurado a null). Para las necesidades de LangChain en proyectos con documentación técnica densa, este modo híbrido puede elevar el recall en un 15-30% respecto a solo búsqueda semántica.

Comparativa: Pinecone vs Weaviate vs Qdrant vs Chroma vs pgvector

La comparativa que sigue es técnica y honesta. No hay un ganador universal: cada opción gana en un conjunto de condicionantes. El objetivo es darte los criterios para decidir, no una respuesta mágica.

CriterioPineconeWeaviateQdrantChromapgvector
ModeloManaged SaaSOSS + CloudOSS + CloudOSSExtensión PostgreSQL
Algoritmo índiceHNSW propietarioHNSW + flatHNSWHNSW (via hnswlib)IVFFlat + HNSW
Hybrid searchSi (sparse+dense)Si (BM25 + dense)Si (sparse vectors)No nativoManual (FTS + vector)
MultitenancyNamespacesMulti-tenancy nativoColecciones + filtrosNo nativoRow-level security
Escala>1B vectores>100M vectores>100M vectores<10M vectoresDepende de PG
Latencia p50 (1M vec)~5 ms~8 ms~3 ms~20 ms~50 ms
Coste (10M vec/mes)~$70-$100~$40-$60 (cloud)~$30-$50 (cloud)Self-hostedCoste infra PG
Lenguajes SDKPython, JS, Go, JavaPython, JS, GoPython, JS, Rust, GoPython, JScualquier ORM PG
Curva de setupBaja (API key)MediaMediaMuy bajaMuy baja (si ya usas PG)

Pinecone: managed sin operaciones, ideal para equipos sin DevOps

Pinecone es el vector store managed por excelencia. No hay infraestructura que gestionar: creas un index, subes vectores, consultas. Su arquitectura desacopla el almacenamiento (en S3 propietario) del índice en memoria, lo que permite escalar a miles de millones de vectores de forma transparente. Soporta namespaces para multitenancy (cada tenant en su namespace propio) y desde 2024 tiene sparse-dense hybrid search. El precio es el inconveniente: a partir de 5-10M vectores en producción, el coste mensual supera al de una instancia cloud auto-gestionada. Pinecone es la opción correcta si tu equipo no quiere operar infraestructura de bases de datos y el presupuesto no es el factor limitante.

Qdrant: el mejor equilibrio OSS para producción

Qdrant, escrito en Rust, tiene las latencias de query más bajas del benchmark en self-hosted (2-5 ms p50 en 1M vectores) gracias a su arquitectura de memoria compartida y su implementación HNSW optimizada. Su sistema de payload filtering es el más expresivo de los analizados: puedes filtrar por cualquier campo JSON del payload con operadores must, should, must_not al estilo Elasticsearch, y el motor integra estos filtros en la travesía del grafo sin caída de recall. Ofrece cloud managed (Qdrant Cloud) y self-hosted (Docker, Kubernetes). Para proyectos con agentes de IA empresariales que manejan documentos de múltiples clientes con filtros complejos, Qdrant es frecuentemente la mejor elección.

Weaviate: hybrid search nativo y grafo de objetos

Weaviate se diferencia por integrar hybrid search sparse+dense de forma nativa y por su concepto de schema orientado a objetos con clases, propiedades y referencias entre objetos (puede funcionar como un grafo de conocimiento vectorial). Su módulo text2vec permite configurar directamente el modelo de embedding dentro del schema, simplificando el pipeline. Sin embargo, esta abstracción de alto nivel tiene un coste: Weaviate consume más RAM por vector que Qdrant, y su configuración inicial es más compleja. Es la opción natural si tu pipeline usa LangChain y necesitas hybrid search sin implementarlo manualmente.

Chroma: la opción para prototipos y desarrollo local

Chroma es el vector store más fácil de arrancar: pip install chromadb y en tres líneas tienes una colección persistida en disco. Su integración con LangChain y LlamaIndex es la más directa del mercado. El problema es que no está diseñado para producción a escala: no tiene replicación, no tiene sharding nativo, y su rendimiento en colecciones >1M vectores es significativamente peor que Qdrant o Pinecone. Chroma es la opción correcta para desarrollo local, demos, y proyectos de <100K documentos que no van a crecer. Usarlo en producción a escala es uno de los errores más frecuentes que vemos al revisar arquitecturas de sistemas RAG de equipos que escalaron desde un prototipo.

pgvector: cuando ya tienes PostgreSQL

pgvector es una extensión de PostgreSQL que añade el tipo de dato vector y los operadores de distancia correspondientes (<-> para L2, <=> para cosine, <#> para inner product). Su ventaja principal es que vive dentro de PostgreSQL: los vectores están en la misma base de datos que el resto de datos relacionales, con transacciones ACID, row-level security para multitenancy, y toda la tooling PostgreSQL (backups, replicación, monitoreo). La desventaja es rendimiento: el índice HNSW de pgvector (disponible desde la versión 0.5.0) tiene una latencia 3-5x mayor que Qdrant o Pinecone en colecciones grandes, y el throughput de queries concurrentes está limitado por el modelo de concurrencia de PostgreSQL. pgvector es la elección correcta si tu equipo ya opera PostgreSQL, el corpus no supera los 5-10M vectores, y la simplicidad operativa (un solo sistema de datos) vale más que la latencia óptima.

Self-hosted vs managed: cuándo elegir cada uno

La decisión entre self-hosted y managed no es solo de coste: es de capacidad operativa del equipo, control sobre los datos, y requisitos de compliance. Veamos los condicionantes reales.

El argumento para managed (Pinecone Cloud, Qdrant Cloud)

Managed tiene sentido cuando el equipo es pequeño (1-3 ingenieros), el tiempo de go-to-market es crítico, y el volumen de datos no requiere optimizaciones de infraestructura personalizadas. El coste de operación de un vector store self-hosted (sizing, backups, upgrades, monitoring, incident response) para un equipo sin experiencia en infraestructura de bases de datos puede superar fácilmente el delta de precio de la opción managed. Además, servicios como Pinecone tienen SLAs de disponibilidad del 99.9% y equipos dedicados a optimizar el rendimiento del índice. Para agentes de IA en producción que necesitan alta disponibilidad sin un equipo de SRE dedicado, managed es casi siempre la decisión correcta.

El argumento para self-hosted (Docker, Kubernetes)

Self-hosted tiene sentido cuando: (a) el volumen de datos es grande y el coste de managed escala linealmente hasta cifras inaceptables; (b) hay requisitos de soberanía de datos o compliance (GDPR, sectores financieros o sanitarios) que impiden almacenar vectores en infraestructura de terceros; (c) el equipo tiene capacidad de operaciones en Kubernetes y quiere control total sobre el tuning del índice (parámetros HNSW, cuantización, asignación de RAM). Qdrant en Kubernetes con Helm chart propio es la opción de referencia para self-hosted: consume ~1.2 GB de RAM por millón de vectores de 1.536 dimensiones sin cuantización, y con cuantización de producto (PQ) se puede reducir a ~300 MB por millón con recall >0.93. Para proyectos con implementaciones empresariales de IA en sectores regulados, self-hosted es frecuentemente no-negociable.

El caso especial de pgvector en RDS / Supabase

Una tercera vía intermedia es usar pgvector en un servicio PostgreSQL managed (AWS RDS, Supabase, Neon). Aquí obtienes la simplicidad operativa de managed con el control sobre el schema relacional. Supabase, en particular, ha invertido en optimizar pgvector y ofrece una experiencia developer-friendly con políticas RLS preconfiguradas para multitenancy. Es una opción razonable para startups que ya usan Supabase como backend y quieren añadir capacidad vectorial sin añadir un nuevo sistema a su stack.

Implementación práctica (código Python con 3 vector stores)

Los cuatro bloques de código que siguen son ejecutables con las dependencias estándar de cada vector store. Asumen que ya tienes un modelo de embedding disponible (usamos text-embedding-3-small de OpenAI para todos, con dimensionalidad 1536) y documentos fragmentados. Para la estrategia de chunking de documentos previa a estos bloques, ver la guía específica de fragmentación.

Chroma: insert y query básico

import chromadb
from openai import OpenAI

openai_client = OpenAI()  # usa OPENAI_API_KEY del entorno

def get_embedding(text: str) -> list[float]:
    response = openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

# Inicializar cliente Chroma (persistente en disco)
chroma_client = chromadb.PersistentClient(path="./chroma_db")
collection = chroma_client.get_or_create_collection(
    name="documentos_empresa",
    metadata={"hnsw:space": "cosine"}  # métrica de similitud
)

# Insertar documentos
documentos = [
    {"id": "doc_001", "texto": "El agente gestiona facturas de forma autónoma.", "categoria": "finanzas"},
    {"id": "doc_002", "texto": "El sistema detecta anomalías en el inventario.", "categoria": "logistica"},
]

collection.upsert(
    ids=[d["id"] for d in documentos],
    embeddings=[get_embedding(d["texto"]) for d in documentos],
    documents=[d["texto"] for d in documentos],
    metadatas=[{"categoria": d["categoria"]} for d in documentos]
)

# Query: top-3 más similares
query_text = "automatización de pagos y facturas"
query_embedding = get_embedding(query_text)

results = collection.query(
    query_embeddings=[query_embedding],
    n_results=3,
    where={"categoria": "finanzas"}  # filtro metadata
)

for doc, score in zip(results["documents"][0], results["distances"][0]):
    print(f"Score: {1 - score:.4f} | Doc: {doc}")

Qdrant: insert y query con payload filtering

from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, PointStruct,
    Filter, FieldCondition, MatchValue
)
from openai import OpenAI
import uuid

openai_client = OpenAI()

def get_embedding(text: str) -> list[float]:
    return openai_client.embeddings.create(
        model="text-embedding-3-small", input=text
    ).data[0].embedding

# Conectar a Qdrant (local Docker o Qdrant Cloud)
qdrant = QdrantClient(url="http://localhost:6333")
# Para Qdrant Cloud: QdrantClient(url="https://xxx.qdrant.io", api_key="...")

COLLECTION = "documentos_empresa"
VECTOR_SIZE = 1536

# Crear colección si no existe
if not qdrant.collection_exists(COLLECTION):
    qdrant.create_collection(
        collection_name=COLLECTION,
        vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
    )

# Insertar documentos con payload
documentos = [
    {"texto": "El agente gestiona facturas de forma autónoma.", "tenant_id": "cliente_A", "categoria": "finanzas"},
    {"texto": "Sistema de detección de anomalías en inventario.", "tenant_id": "cliente_B", "categoria": "logistica"},
]

points = [
    PointStruct(
        id=str(uuid.uuid4()),
        vector=get_embedding(d["texto"]),
        payload={"texto": d["texto"], "tenant_id": d["tenant_id"], "categoria": d["categoria"]}
    )
    for d in documentos
]
qdrant.upsert(collection_name=COLLECTION, points=points)

# Query con filtro por tenant_id (multi-tenant pattern)
query_vector = get_embedding("automatización de pagos y facturas")

results = qdrant.search(
    collection_name=COLLECTION,
    query_vector=query_vector,
    query_filter=Filter(
        must=[
            FieldCondition(key="tenant_id", match=MatchValue(value="cliente_A")),
            FieldCondition(key="categoria", match=MatchValue(value="finanzas")),
        ]
    ),
    limit=5,
    with_payload=True
)

for hit in results:
    print(f"Score: {hit.score:.4f} | Texto: {hit.payload['texto']}")

pgvector con SQLAlchemy: insert y query

from sqlalchemy import create_engine, text, Column, String, Integer
from sqlalchemy.orm import DeclarativeBase, Session
from pgvector.sqlalchemy import Vector
from openai import OpenAI

openai_client = OpenAI()
engine = create_engine("postgresql+psycopg2://user:pass@localhost:5432/rag_db")

class Base(DeclarativeBase):
    pass

class Documento(Base):
    __tablename__ = "documentos"
    id = Column(Integer, primary_key=True, autoincrement=True)
    tenant_id = Column(String, nullable=False)
    categoria = Column(String)
    contenido = Column(String)
    embedding = Column(Vector(1536))  # dimensionalidad text-embedding-3-small

# Crear tabla e índice HNSW
Base.metadata.create_all(engine)
with engine.connect() as conn:
    conn.execute(text(
        "CREATE INDEX IF NOT EXISTS idx_embedding_hnsw "
        "ON documentos USING hnsw (embedding vector_cosine_ops) "
        "WITH (m = 16, ef_construction = 64)"
    ))
    conn.commit()

def get_embedding(text_input: str) -> list[float]:
    return openai_client.embeddings.create(
        model="text-embedding-3-small", input=text_input
    ).data[0].embedding

# Insertar documentos
docs = [
    {"tenant_id": "cliente_A", "categoria": "finanzas", "contenido": "El agente gestiona facturas de forma autónoma."},
    {"tenant_id": "cliente_B", "categoria": "logistica", "contenido": "Detección de anomalías en inventario."},
]

with Session(engine) as session:
    for d in docs:
        doc = Documento(
            tenant_id=d["tenant_id"],
            categoria=d["categoria"],
            contenido=d["contenido"],
            embedding=get_embedding(d["contenido"])
        )
        session.add(doc)
    session.commit()

# Query: top-5 similares para un tenant específico
query_embedding = get_embedding("automatización de pagos y facturas")

with Session(engine) as session:
    results = session.execute(
        text("""
            SELECT contenido, 1 - (embedding <=> :query_vec) AS similarity
            FROM documentos
            WHERE tenant_id = :tenant
            ORDER BY embedding <=> :query_vec
            LIMIT 5
        """),
        {"query_vec": str(query_embedding), "tenant": "cliente_A"}
    ).fetchall()

for row in results:
    print(f"Similarity: {row.similarity:.4f} | Contenido: {row.contenido}")

Hybrid search con metadata filtering (Qdrant sparse+dense)

from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance, VectorParams, SparseVectorParams, SparseIndexParams,
    PointStruct, SparseVector, NamedVector, NamedSparseVector,
    SearchRequest, Filter, FieldCondition, MatchValue
)
from openai import OpenAI
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np

openai_client = OpenAI()
qdrant = QdrantClient(url="http://localhost:6333")
COLLECTION = "hybrid_docs"

# Colección con vectores densos Y sparse
qdrant.recreate_collection(
    collection_name=COLLECTION,
    vectors_config={"dense": VectorParams(size=1536, distance=Distance.COSINE)},
    sparse_vectors_config={"sparse": SparseVectorParams(index=SparseIndexParams())}
)

def get_dense_embedding(text: str) -> list[float]:
    return openai_client.embeddings.create(
        model="text-embedding-3-small", input=text
    ).data[0].embedding

# Vectorizer TF-IDF para sparse embeddings (BM25-like)
corpus = [
    "El agente RAG procesa PDFs de facturas automáticamente.",
    "Sistema de retrieval con HNSW para búsqueda semántica.",
    "Chatbot multicanal con integración WhatsApp y Telegram.",
]
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(corpus)

# Construir e insertar puntos con vectores densos + sparse
points = []
for i, texto in enumerate(corpus):
    sparse_row = tfidf_matrix[i]
    indices = sparse_row.indices.tolist()
    values = sparse_row.data.tolist()

    points.append(PointStruct(
        id=i + 1,
        vector={
            "dense": get_dense_embedding(texto),
            "sparse": SparseVector(indices=indices, values=values)
        },
        payload={"texto": texto, "tipo": "tecnico"}
    ))

qdrant.upsert(collection_name=COLLECTION, points=points)

# Hybrid search: combina dense + sparse con RRF (Reciprocal Rank Fusion)
query = "sistema RAG con búsqueda semántica HNSW"
query_dense = get_dense_embedding(query)
query_sparse_vec = vectorizer.transform([query])
q_indices = query_sparse_vec.indices.tolist()
q_values = query_sparse_vec.data.tolist()

# Búsqueda densa
dense_results = qdrant.search(
    collection_name=COLLECTION,
    query_vector=NamedVector(name="dense", vector=query_dense),
    query_filter=Filter(must=[FieldCondition(key="tipo", match=MatchValue(value="tecnico"))]),
    limit=5,
    with_payload=True
)

# Búsqueda sparse
sparse_results = qdrant.search(
    collection_name=COLLECTION,
    query_vector=NamedSparseVector(name="sparse", vector=SparseVector(indices=q_indices, values=q_values)),
    limit=5,
    with_payload=True
)

# RRF manual para fusionar rankings (k=60 es el estándar)
def rrf_score(rank: int, k: int = 60) -> float:
    return 1.0 / (k + rank + 1)

scores: dict[int, float] = {}
for rank, hit in enumerate(dense_results):
    scores[hit.id] = scores.get(hit.id, 0) + rrf_score(rank)
for rank, hit in enumerate(sparse_results):
    scores[hit.id] = scores.get(hit.id, 0) + rrf_score(rank)

sorted_ids = sorted(scores, key=scores.get, reverse=True)
print("Resultados hybrid search (RRF):")
for doc_id in sorted_ids:
    # recuperar payload del punto
    point = qdrant.retrieve(collection_name=COLLECTION, ids=[doc_id], with_payload=True)
    if point:
        print(f"  RRF score: {scores[doc_id]:.4f} | {point[0].payload['texto']}")

Performance: latencia, escalabilidad, costes

Los benchmarks de rendimiento de vector databases dependen fuertemente del hardware, del tamaño de los vectores, del índice configurado y del patrón de acceso. Los números que siguen son orientativos basados en los benchmarks públicos de ann-benchmarks y en mediciones publicadas por los propios vendors. Siempre haz tu propio benchmark con tu dataset y tu patrón de queries antes de comprometerte con una solución en producción.

Latencia de query: qué esperar en producción

Qdrant self-hosted en una instancia con suficiente RAM para cargar el índice HNSW completo logra p50 de 2-5 ms y p99 de 15-30 ms en colecciones de 1M vectores de 1.536 dimensiones. Pinecone managed reporta p50 de 5-10 ms incluyendo la latencia de red (asumiendo despliegue en la misma región que el cliente). pgvector con HNSW en RDS tiene típicamente p50 de 30-80 ms dependiendo del tamaño del índice en RAM y la configuración de shared_buffers. Para sistemas RAG donde el retrieval es parte de una cadena que incluye una llamada al LLM (200-2.000 ms), la diferencia entre 5 ms y 50 ms en el vector store raramente es el cuello de botella, pero sí importa en aplicaciones de alta frecuencia o cuando se hacen múltiples llamadas de retrieval por turno de conversación.

Escalabilidad: cuántos vectores y a qué coste de RAM

HNSW sin cuantización requiere aproximadamente 1-2 GB de RAM por millón de vectores de 1.536 dimensiones (depende del parámetro M). A 100M vectores, eso implica 100-200 GB de RAM solo para el índice, lo que requiere instancias de memoria alta o sharding. La cuantización de producto (PQ) en Qdrant puede reducir ese footprint 4-8x a costa de un ~5% de recall. Para escalar más allá de 100M vectores sin invertir en hardware masivo, Weaviate con PQ o Pinecone serverless (que desacopla almacenamiento del índice en memoria) son las opciones más manejables. La consideración de arquitectura agéntica también es relevante: sistemas con múltiples agentes especializados pueden usar colecciones separadas optimizadas para cada tipo de documento en lugar de un índice monolítico.

Modelo de costes: comparativa real

Para una instalación típica con 5M vectores de 1.536D y 50K queries/día:

  • Pinecone Starter: ~$70-100/mes (incluyendo 1 pod p1.x1 o modo serverless).
  • Qdrant Cloud: ~$35-55/mes en el tier estándar con 8 GB RAM.
  • Self-hosted Qdrant (instancia cloud con 16 GB RAM): ~$60-80/mes dependiendo del proveedor cloud, pero con coste operativo del equipo.
  • pgvector en Supabase Pro: ~$25/mes (incluido en el plan, hasta cierto uso).
  • Chroma self-hosted: coste de infraestructura (~$20-40/mes), pero sin SLA ni replicación.

El coste del modelo de embedding es independiente y frecuentemente mayor que el del vector store en proyectos con indexación continua de documentos.

Preguntas frecuentes sobre bases de datos vectoriales

¿Qué diferencia hay entre una base de datos vectorial y FAISS?

FAISS es una librería de indexación vectorial desarrollada por Meta AI que implementa algoritmos ANN (HNSW, IVF, PQ) de forma muy eficiente en memoria. No es una base de datos: no tiene persistencia automática, no tiene API de red, no gestiona actualizaciones incrementales, y no tiene soporte de metadata filtering. Una base de datos vectorial como Qdrant o Weaviate usa internamente algoritmos similares a FAISS pero añade capas de producción imprescindibles: persistencia en disco, API REST/gRPC, filtros de metadata integrados, replicación y escalado horizontal. FAISS es un bloque de construcción; una vector database es un sistema completo. Para producción real, siempre una vector database. Para experimentación en notebook, FAISS con serialización manual puede ser suficiente.

¿Puedo usar pgvector en producción o es solo para pruebas?

pgvector es perfectamente válido para producción en rangos de hasta 5-10 millones de vectores, siempre que la latencia de 30-80 ms sea aceptable para tu caso de uso. Su ventaja principal es la simplicidad operativa: vive dentro de PostgreSQL, con todas las garantías transaccionales y el tooling ya conocido por el equipo. Desde la versión 0.5.0, pgvector soporta índice HNSW nativo (antes solo IVFFlat), lo que mejora significativamente la latencia. Para sistemas RAG donde el retrieval es parte de un flujo que ya tarda >500 ms (por la llamada al LLM), pgvector en una instancia bien configurada es una opción sólida que evita añadir un nuevo sistema de datos al stack operativo.

¿Qué modelo de embedding debo usar con mi vector store?

La elección del modelo de embedding es independiente del vector store, pero hay algunas consideraciones de compatibilidad. El vector store debe configurarse con la dimensionalidad correcta del modelo elegido: 1.536D para text-embedding-3-small de OpenAI, 1.024D para embed-english-v3.0 de Cohere, 768D para modelos como nomic-embed-text. Dimensionalidades mayores dan generalmente mejor recall pero consumen más RAM y aumentan el coste por vector. Para proyectos en castellano, modelos multilingües como multilingual-e5-large o text-embedding-3-large suelen superar a los modelos solo-inglés en un 10-20% de recall en queries en español. Nunca mezcles modelos de embedding en la misma colección: los vectores no son comparables entre modelos diferentes.

¿Qué es el hybrid search y cuándo vale la pena implementarlo?

El hybrid search combina búsqueda semántica densa (embeddings) con búsqueda léxica sparse (BM25 o TF-IDF). La búsqueda semántica recupera documentos conceptualmente similares aunque no compartan vocabulario exacto; la búsqueda léxica recupera documentos con coincidencia exacta de términos clave. Los dos enfoques son complementarios: la semántica falla en términos técnicos muy específicos (números de modelo, SKUs, acrónimos de dominio) que los embeddings no capturan bien; la léxica falla en paráfrasis y sinónimos. Hybrid search vale la pena cuando el corpus contiene terminología técnica especializada, nombres propios, o códigos que deben coincidir exactamente. En la práctica, para la mayoría de sistemas RAG con documentación en castellano, el hybrid search mejora el recall en un 10-25% respecto a solo búsqueda densa.

¿Cómo gestiono actualizaciones de documentos en el vector store?

Las actualizaciones de documentos en un vector store requieren una estrategia de sincronización. El patrón más común es upsert por ID externo: cada chunk tiene un ID determinístico (hash del contenido o ID del documento + posición del chunk) que permite sobrescribir el vector cuando el documento cambia, sin acumular vectores obsoletos. Cuando un documento se elimina, todos sus chunks deben borrarse explícitamente. Para colecciones grandes con actualizaciones frecuentes, considera separar el índice «vivo» del índice «base» y hacer merges periódicos (el patrón LSM-tree). Qdrant soporta borrado por payload filter, lo que simplifica eliminar todos los chunks de un documento en una sola operación: qdrant.delete(collection, points_selector=Filter(must=[FieldCondition(key="doc_id", match=MatchValue(value="abc"))])).

¿Cuándo debo migrar de Chroma a un vector store más robusto?

La señal más clara de que es momento de migrar de Chroma es cuando el tiempo de query supera los 100 ms de forma consistente, lo que suele ocurrir entre 500K y 1M vectores dependiendo del hardware. Otras señales: necesitas multitenancy real (filtrar por tenant de forma eficiente), necesitas alta disponibilidad con replicación, o el proceso de Python que aloja Chroma se convierte en un cuello de botella de concurrencia. La migración de Chroma a Qdrant es relativamente directa porque el API de LangChain abstrae el vector store: cambiar Chroma.from_documents() por Qdrant.from_documents() requiere reindexar los documentos pero no cambiar la lógica de retrieval. Planifica la migración antes de que sea urgente, idealmente cuando superes los 200K chunks, para no hacerla bajo presión de incidente.

Conclusión

La decisión sobre qué base de datos vectorial usar para tu sistema RAG es multidimensional: no hay una respuesta correcta universal. La heurística de primer nivel es simple: si ya tienes PostgreSQL en producción y el corpus no supera 5M vectores, empieza con pgvector. Si necesitas self-hosted con rendimiento alto y filtros complejos, Qdrant es la opción de referencia. Si el equipo no quiere operar infraestructura y el presupuesto lo permite, Pinecone elimina ese problema. Si estás en fase de prototipo o desarrollo local, Chroma es la más rápida de arrancar. Weaviate gana cuando el hybrid search sparse+dense y el modelo de datos orientado a objetos se alinean con tu arquitectura.

Lo que importa más que la elección del vector store es la calidad del modelo de embedding, la estrategia de chunking de documentos, y la arquitectura de retrieval: cómo construyes las queries, cómo combinas los resultados, y cómo los inyectas en el prompt del LLM. Estos factores tienen más impacto en la calidad final del sistema que el vector store elegido. Sobre estos temas, la guía de implementación de RAG en Python cubre la arquitectura completa.

En Baigency trabajamos con estas tecnologías en proyectos de agentes de IA empresariales con requisitos de retrieval semántico. Si estás evaluando qué arquitectura encaja con tu caso de uso específico —volumen de datos, requisitos de latencia, modelo de costes, restricciones de compliance— podemos hacer una revisión técnica de tu pipeline. El formulario de contacto está abajo.

Explore
Drag