Módulo 5: Landscape de Vector Databases para AI Engineers
Cápsula 04: Comparación de Features para RAG
🎯 Objetivo de la cápsula
Comparar las features técnicas que más impactan un sistema RAG en producción — metadata filtering, hybrid search, multi-tenancy, batch operations, SDKs y scaling — y entender cómo varía el soporte entre proveedores.
En las cápsulas anteriores mapeaste el landscape de proveedores y entendiste el trade-off managed vs self-hosted. Ahora toca evaluar las features que afectan directamente la calidad, seguridad y eficiencia de tu pipeline RAG. Una base vectorial puede ser barata y fácil de operar, pero si no soporta metadata filtering avanzado o multi-tenancy, tu RAG tendrá problemas graves en producción.
Esta cápsula es deliberadamente table-heavy: las tablas comparativas son la herramienta más útil para una evaluación rápida. Cada tabla va acompañada de contexto sobre por qué esa feature importa para RAG y ejemplos de código que muestran diferencias de API entre proveedores.
Al finalizar esta cápsula:
- ✅ Evaluarás 6 features críticas para RAG en 5 proveedores
- ✅ Distinguirás entre soporte nominal y soporte real de cada feature
- ✅ Identificarás qué features son "no negociables" para tu caso
- ✅ Tendrás tablas de referencia para tu decision tree
Tiempo estimado: 25-30 minutos
📋 Las 6 Features Críticas para RAG
¿Qué hace una feature "crítica" para RAG?
Es crítica si afecta al menos uno de estos cuatro objetivos de un sistema RAG en producción:
| Objetivo | Pregunta clave |
|---|---|
| Precisión de retrieval | ¿Los documentos recuperados son relevantes? |
| Latencia p95 | ¿El retrieval es suficientemente rápido para el UX? |
| Seguridad / aislamiento | ¿Los datos de un tenant pueden filtrarse a otro? |
| Costo operativo | ¿La operación escala sin explotar el presupuesto? |
Mapa de features
Features Críticas para RAG
│
├── 1. Metadata Filtering → Precisión
├── 2. Hybrid Search → Precisión
├── 3. Multi-tenancy → Seguridad
├── 4. Batch Operations → Costo
├── 5. SDKs y DX → Velocidad
└── 6. Scaling → Costo + Latencia
1️⃣ Metadata Filtering
¿Por qué importa para RAG?
Metadata filtering reduce el espacio de búsqueda antes de calcular similitud. Sin filtrado, tu RAG busca en todos los vectores — con filtrado, busca solo en el subconjunto relevante.
Impacto real:
Sin filtrado:
Query: "¿Cómo configuro autenticación?"
Busca en: 500K vectores (todos los docs)
Resultado: Mezcla docs de v1, v2, v3 del producto
→ LLM genera respuesta con info desactualizada
Con filtrado (version="v3"):
Query: "¿Cómo configuro autenticación?"
Busca en: 80K vectores (solo v3)
Resultado: Docs relevantes y actualizados
→ LLM genera respuesta precisa
Comparación de metadata filtering
| Capacidad | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
Filtros de igualdad (=) | ✅ | ✅ | ✅ | ✅ | ✅ |
Filtros de rango (>, <) | ✅ | ✅ | ✅ | ✅ | ✅ |
Filtros IN (lista) | ✅ | ✅ | ✅ | ✅ | ✅ |
| AND / OR lógicos | ✅ | ✅ | ✅ | ✅ | ✅ |
| NOT / negación | ✅ | ✅ | ✅ | ✅ | ✅ |
| Nested filters (objetos) | ❌ | ❌ | ✅ | ✅ | ❌ |
| Full-text search en metadata | ❌ | ❌ | ✅ | ✅ | ✅ |
| Geo filtering | ❌ | ❌ | ✅ | ✅ | ❌ |
| Payload index dedicado | N/A | N/A | Auto | ✅ Manual | ✅ Manual |
| Performance con filtros | ⚠️ Degrada | ✅ Buena | ✅ Buena | ✅ Excelente | ✅ Buena |
Código: Filtrado en cada proveedor
# ChromaDB — where syntax
results = collection.query(
query_texts=["autenticación"],
n_results=5,
where={
"$and": [
{"version": {"$eq": "v3"}},
{"language": {"$eq": "es"}}
]
}
)
# Pinecone — MongoDB-like syntax
results = index.query(
vector=embedding,
top_k=5,
filter={
"$and": [
{"version": {"$eq": "v3"}},
{"language": {"$eq": "es"}}
]
}
)
# Weaviate — typed filter API
from weaviate.classes.query import Filter
response = collection.query.near_text(
query="autenticación",
limit=5,
filters=(
Filter.by_property("version").equal("v3") &
Filter.by_property("language").equal("es")
)
)
# Qdrant — model-based filters
from qdrant_client.models import Filter, FieldCondition, MatchValue
results = client.query_points(
collection_name="docs",
query=embedding,
limit=5,
query_filter=Filter(
must=[
FieldCondition(key="version", match=MatchValue(value="v3")),
FieldCondition(key="language", match=MatchValue(value="es"))
]
)
)
# Milvus — SQL-like string
results = client.search(
collection_name="docs",
data=[embedding],
limit=5,
filter='version == "v3" and language == "es"'
)
Insight para RAG: Qdrant destaca en performance de filtrado porque soporta payload indexes dedicados que evitan full-scan del metadata. Weaviate tiene los filtros más expresivos (geo, nested objects). ChromaDB cubre lo básico pero puede degradar con filtros complejos sobre colecciones grandes.
2️⃣ Hybrid Search
¿Por qué importa para RAG?
Hybrid search combina keyword match (BM25) con semantic search (vector) para capturar tanto coincidencias exactas ("GPT-4", "INV-2024-001") como conceptuales ("cómo mejorar rendimiento"). En RAG para documentación técnica, hybrid search mejora accuracy entre 15-25%.
Comparación de hybrid search
| Capacidad | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
| Hybrid search nativo | ❌ | ⚠️ Sparse | ✅ Built-in | ✅ Sparse | ✅ |
| Keyword index (BM25) | ❌ | ❌ | ✅ Inverted | ❌ | ✅ |
| Sparse vectors | ❌ | ✅ | ❌ | ✅ | ✅ |
| Fusion algorithm | N/A | Manual | RRF (default) | Manual | RRF/Weighted |
| Alpha tuning | N/A | N/A | ✅ (0-1) | Manual | ✅ |
| Custom ranking | N/A | ❌ | ✅ | ✅ | ✅ |
Niveles de soporte:
- Nativo (Weaviate): Un solo endpoint, fusión automática, alpha ajustable.
- Sparse vectors (Pinecone, Qdrant): Envías dense + sparse vectors, fusión manual o semi-auto.
- No soportado (ChromaDB): Necesitas implementar BM25 externamente (rank_bm25 library).
Código: Hybrid search por proveedor
# Weaviate — hybrid nativo con alpha
response = collection.query.hybrid(
query="GPT-4 API rate limits",
alpha=0.5, # 0 = pure keyword, 1 = pure vector
limit=10
)
# Pinecone — sparse + dense vectors
results = index.query(
vector=dense_embedding, # semantic
sparse_vector={ # keyword
"indices": [102, 5483, 9201],
"values": [0.8, 0.5, 0.3]
},
top_k=10
)
# Qdrant — sparse vectors con named vectors
results = client.query_points(
collection_name="docs",
query=embedding,
using="dense",
limit=10
)
# + query separada con sparse vector, fusión manual
# ChromaDB — NO soporta hybrid, workaround con BM25 externo
from rank_bm25 import BM25Okapi
tokenized = [doc.split() for doc in all_documents]
bm25 = BM25Okapi(tokenized)
keyword_scores = bm25.get_scores(query.split())
semantic_results = collection.query(
query_texts=[query], n_results=20
)
# Fusión manual (RRF o weighted)
Insight para RAG: Si tu RAG maneja documentación técnica con IDs, versiones o nombres de producto, hybrid search no es opcional — es esencial. Weaviate es la opción más madura para hybrid. Pinecone y Qdrant lo soportan via sparse vectors pero requieren más trabajo.
3️⃣ Multi-tenancy
¿Por qué importa para RAG?
Multi-tenancy determina cómo aislas datos entre usuarios, organizaciones o clientes. En un RAG SaaS, si el Tenant A puede ver documentos del Tenant B, tienes un data leakage — un problema de seguridad grave.
Estrategias de multi-tenancy
Estrategia 1: Collection per tenant
┌──────────┐ ┌──────────┐ ┌──────────┐
│Tenant A │ │Tenant B │ │Tenant C │
│Collection│ │Collection│ │Collection│
└──────────┘ └──────────┘ └──────────┘
Pro: Aislamiento total
Con: Overhead de gestión, no escala a 1000+ tenants
Estrategia 2: Metadata filtering
┌─────────────────────────────────┐
│ Shared Collection │
│ [tenant_id=A] [tenant_id=B] │
│ [tenant_id=A] [tenant_id=C] │
└─────────────────────────────────┘
Pro: Simple, escala a muchos tenants
Con: Filter overhead, riesgo de olvidar filtro (leakage)
Estrategia 3: Namespace / Partition
┌─────────────────────────────────┐
│ Index │
│ ┌─────────┐ ┌─────────┐ │
│ │Namespace│ │Namespace│ │
│ │ "A" │ │ "B" │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────┘
Pro: Aislamiento lógico nativo
Con: Límites por proveedor
Comparación de multi-tenancy
| Capacidad | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
| Estrategia principal | Collections | Namespaces | Tenants nativos | Payload filter | Partitions |
| Aislamiento de datos | Medio | Alto | Alto | Medio-Alto | Alto |
| Límite de tenants | ~100 collections | 10K namespaces | 100K+ tenants | Sin límite soft | 4096 partitions |
| Tenant inactivo offload | ❌ | ❌ | ✅ (activity-based) | ❌ | ❌ |
| Tenant-level metrics | ❌ | ❌ | ✅ | ❌ | ❌ |
| Riesgo de data leakage | Medio | Bajo | Bajo | Medio* | Bajo |
*Qdrant con payload filter requiere que el developer siempre incluya el filtro de tenant — si se olvida, hay leakage.
Código: Multi-tenancy por proveedor
# Pinecone — namespaces nativos
index.upsert(
vectors=[{"id": "doc_1", "values": emb, "metadata": {"content": "..."}}],
namespace="tenant_acme" # aislamiento por namespace
)
results = index.query(
vector=query_emb, top_k=5,
namespace="tenant_acme" # solo busca en este tenant
)
# Weaviate — multi-tenancy nativa (v1.20+)
import weaviate.classes as wvc
collection = client.collections.create(
name="Documents",
multi_tenancy_config=wvc.config.Configure.multi_tenancy(enabled=True),
vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai()
)
collection.tenants.create([
wvc.tenants.Tenant(name="tenant_acme"),
wvc.tenants.Tenant(name="tenant_globex"),
])
tenant_collection = collection.with_tenant("tenant_acme")
tenant_collection.data.insert(properties={"content": "..."})
# Qdrant — payload filter (manual, sin namespace nativo)
client.upsert(
collection_name="docs",
points=[PointStruct(
id=1, vector=emb,
payload={"tenant_id": "acme", "content": "..."}
)]
)
results = client.query_points(
collection_name="docs",
query=query_emb, limit=5,
query_filter=Filter(must=[
FieldCondition(key="tenant_id", match=MatchValue(value="acme"))
])
)
# ChromaDB — collection per tenant
tenant_collection = client.get_or_create_collection(name="tenant_acme")
tenant_collection.add(documents=["..."], ids=["doc_1"])
Insight para RAG: Si construyes RAG para SaaS (múltiples clientes), multi-tenancy es un requisito de seguridad, no una feature. Weaviate tiene la implementación más madura con offload de tenants inactivos. Pinecone con namespaces es simple y seguro. Qdrant requiere disciplina del developer para nunca olvidar el filtro.
4️⃣ Batch Operations
¿Por qué importa para RAG?
La ingesta de documentos en RAG es inherentemente un problema de batch: chunkeaste 10,000 documentos en 50,000 chunks → necesitas insertar 50,000 vectores. La velocidad y eficiencia de batch operations afecta directamente el tiempo de setup y el costo de re-indexación.
Comparación de batch operations
| Capacidad | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
| Batch upsert | ✅ | ✅ | ✅ | ✅ | ✅ |
| Batch size máximo | ~41K* | 100 vectors | Sin límite** | Sin límite** | Sin límite** |
| Upsert paralelo | Manual | ✅ Async | ✅ Batch API | ✅ Async | ✅ |
| Streaming ingestion | ❌ | ❌ | ❌ | ❌ | ✅ |
| Progress tracking | ❌ | ❌ | ❌ | ❌ | ❌ |
| Rate de ingesta (1536-dim) | ~5K/s local | ~1K/s API | ~10K/s local | ~15K/s local | ~20K/s local |
*ChromaDB tiene límite por request size de SQLite. **Limitado por memoria del servidor.
Código: Batch ingestion eficiente
# ChromaDB — batch con chunks
def batch_insert_chromadb(collection, documents, batch_size=1000):
for i in range(0, len(documents), batch_size):
batch = documents[i:i + batch_size]
collection.add(
documents=[d["text"] for d in batch],
metadatas=[d["metadata"] for d in batch],
ids=[d["id"] for d in batch]
)
print(f"Insertados {min(i + batch_size, len(documents))}/{len(documents)}")
# Pinecone — batch con límite de 100
def batch_insert_pinecone(index, vectors, batch_size=100):
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i + batch_size]
index.upsert(vectors=batch)
# Qdrant — batch con upload_points
def batch_insert_qdrant(client, collection_name, points, batch_size=1000):
client.upload_points(
collection_name=collection_name,
points=points,
batch_size=batch_size,
parallel=4 # hilos concurrentes
)
# Milvus — insert masivo
def batch_insert_milvus(client, collection_name, data, batch_size=5000):
for i in range(0, len(data), batch_size):
batch = data[i:i + batch_size]
client.insert(collection_name=collection_name, data=batch)
Insight para RAG: Pinecone tiene el batch size más restrictivo (100 vectors por request), lo que hace la ingesta inicial más lenta via API. Para cargas masivas (> 100K docs), Qdrant y Milvus self-hosted son significativamente más rápidos. ChromaDB es eficiente para tamaños moderados.
5️⃣ SDKs y Developer Experience
¿Por qué importa para RAG?
La calidad del SDK determina cuánto tiempo invierte tu equipo integrando la vector DB con tu pipeline RAG. Un SDK mal diseñado genera bugs, frustración y tiempo perdido en debugging.
Comparación de SDKs
| Capacidad | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
| Python SDK | ✅ Excelente | ✅ Excelente | ✅ Bueno (v4) | ✅ Excelente | ✅ Bueno |
| TypeScript SDK | ✅ | ✅ | ✅ | ✅ | ✅ |
| Go SDK | ❌ | ✅ | ✅ | ✅ | ✅ |
| Rust SDK | ❌ | ✅ | ❌ | ✅ (oficial) | ❌ |
| Java SDK | ❌ | ✅ | ✅ | ✅ | ✅ |
| Async support | ❌ | ✅ | ✅ | ✅ | ✅ |
| Type hints (Python) | Parcial | ✅ | ✅ | ✅ Completo | Parcial |
| Auto-embedding | ✅ | ❌ | ✅ (modules) | ❌ | ❌ |
| LangChain integration | ✅ | ✅ | ✅ | ✅ | ✅ |
| LlamaIndex integration | ✅ | ✅ | ✅ | ✅ | ✅ |
| Documentación SDK | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
Insight para RAG: ChromaDB gana en simplicidad absoluta (auto-embedding, 5 líneas para funcionar). Qdrant tiene la mejor documentación y type hints. Weaviate requiere más boilerplate pero ofrece más poder (schema, modules). Para equipos que usan LangChain/LlamaIndex, la diferencia de SDK importa menos porque el framework abstrae la API. La comparación detallada de patrones de código por proveedor está en la cápsula 02.
6️⃣ Scaling
¿Por qué importa para RAG?
Tu RAG empieza con 10K documentos en MVP. En 6 meses tiene 500K. En un año, 5M. La capacidad de escalar la vector DB sin re-arquitectar todo el sistema es crítica.
Comparación de scaling
| Capacidad | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
| Escala máxima recomendada | ~1M | 100M+ | 100M+ | 100M+ | Billones |
| Sharding automático | ❌ | ✅ | ✅ | Manual | ✅ |
| Replicación | ❌ | ✅ (multi-AZ) | ✅ | ✅ (Raft) | ✅ |
| Horizontal scaling | ❌ | ✅ (auto) | ✅ | ✅ | ✅ |
| Quantization | ❌ | ❌ | PQ | Scalar/PQ/Binary | SQ8/PQ |
| On-disk index | ❌ | N/A (cloud) | ✅ | ✅ (mmap) | ✅ (DiskANN) |
| GPU acceleration | ❌ | ❌ | ❌ | ❌ | ✅ |
| Segment management | ❌ | Auto | Auto | Manual/Auto | Auto |
Impacto de quantization en costos
| Tipo | Ejemplo (10M × 1536-dim) | Memoria | Ahorro | Accuracy loss |
|---|---|---|---|---|
| Sin quantization (float32) | 57.3 GB | — | — | — |
| Scalar (Qdrant) | 14.3 GB | 75% | < 1% | Recomendado |
| Product (Weaviate/Milvus) | ~1.8 GB | 96% | 2-5% | Aceptable |
| Binary (Qdrant) | ~1.8 GB | 96% | 3-7% | Requiere rescoring |
Insight para RAG: Si tu RAG crece a > 5M vectores, quantization se convierte en el factor más importante de costo. Qdrant lidera con 3 tipos de quantization. Milvus escala más con GPU y DiskANN. ChromaDB no es opción para esta escala.
📊 Tabla Maestra: Feature Comparison para RAG
| Feature | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|
| Metadata filtering | ✅ Básico | ✅ Bueno | ✅ Avanzado | ✅ Excelente | ✅ Bueno |
| Hybrid search | ❌ | ⚠️ Sparse | ✅ Nativo | ✅ Sparse | ✅ |
| Multi-tenancy | ⚠️ Collections | ✅ Namespaces | ✅ Nativo | ⚠️ Payload | ✅ Partitions |
| Batch operations | ✅ | ⚠️ Lento | ✅ | ✅ Rápido | ✅ Rápido |
| SDK quality | ✅ Simple | ✅ Bueno | ✅ Bueno | ✅ Excelente | ✅ Bueno |
| Scaling | ❌ Limitado | ✅ Auto | ✅ | ✅ | ✅ Enterprise |
| Quantization | ❌ | ❌ | ✅ PQ | ✅ 3 tipos | ✅ SQ8/PQ |
| Observabilidad | ❌ | ✅ Console | ✅ Metrics | ✅ Dashboard | ✅ Attu |
Lectura rápida de la tabla
- ChromaDB: Excelente para empezar, limitado para producción.
- Pinecone: Sólido en todo excepto hybrid search y quantization.
- Weaviate: El más completo en features (hybrid nativo, multi-tenancy, PQ).
- Qdrant: Mejor en filtering y quantization, hybrid search mejorando.
- Milvus: El más escalable, pero complejidad operativa mayor.
🔍 Cómo Usar Esta Comparación Sin Sesgo
Proceso de 3 pasos
Paso 1: Marca 2-3 features "no negociables" para tu RAG.
no_negociables = {
"saas_multitenant": ["multi_tenancy"],
"documentation_rag": ["hybrid_search", "metadata_filtering"],
"internal_search": ["scaling", "batch_operations"],
"prototype": [] # para prototipos, ninguna feature es blocker
}
mi_caso = "documentation_rag"
print(f"Features no negociables: {no_negociables[mi_caso]}")
# → Features no negociables: ['hybrid_search', 'metadata_filtering']
Paso 2: Elimina opciones que no cubran mínimos.
Caso: documentation_rag (hybrid search + metadata filtering)
ChromaDB: ❌ No tiene hybrid search → ELIMINADA
Pinecone: ⚠️ Hybrid via sparse vectors (requiere trabajo extra)
Weaviate: ✅ Hybrid nativo + filtering avanzado
Qdrant: ✅ Sparse vectors + filtering excelente
Milvus: ✅ Hybrid + filtering bueno
Candidatas: Weaviate, Qdrant, Milvus (Pinecone con reservas)
Paso 3: Recién después compara costo y operación entre las candidatas restantes.
🔧 Troubleshooting de evaluación
1. "Todos dicen que soportan todo"
Causa: Marketing vs realidad. "Soporta metadata filtering" puede significar filtros básicos de igualdad o un sistema completo con geo-filtering y nested objects.
Solución: Para cada feature "no negociable", busca:
- Documentación oficial con ejemplos de código
- Límites documentados (max filters por query, max tenants, etc.)
- Issues abiertos en GitHub relacionados con esa feature
- Benchmarks independientes (no del propio proveedor)
2. "No sé cuánto pesa hybrid search en mi caso"
Causa: No has medido la distribución de tipos de query.
Solución: Analiza 100 queries reales de tu sistema (o proyectadas):
- Cuenta cuántas contienen nombres propios, IDs o términos exactos
- Si > 20% son "específicas", hybrid search mejorará tu accuracy significativamente
- Si < 5% son específicas, pure semantic es suficiente
3. "Multi-tenancy me parece overkill"
Causa: Solo tienes un tenant hoy — pero ¿en 6 meses?
Solución: Si tu producto es B2B o SaaS, multi-tenancy no es opcional. Implementarla después es una migración costosa. Si tu producto es B2C (un solo tenant), metadata filtering con user_id es suficiente y multi-tenancy formal es innecesaria.
4. "¿Cuánta accuracy pierdo con quantization?"
Causa: Miedo a degradar la calidad del RAG por comprimir vectores.
Solución: La pérdida típica con Scalar Quantization es < 1% en recall@10. Con Product Quantization es 2-5%. Para RAG, esta pérdida es generalmente aceptable. Haz una prueba A/B: compara recall de 100 queries con y sin quantization antes de decidir.
✏️ Ejercicios
Ejercicio 1: Features no negociables
Para tu proyecto RAG (real o hipotético), define:
- Las 3 features más importantes de la lista de 6.
- El nivel mínimo aceptable para cada una.
- Qué proveedores sobreviven el filtro.
Solución de referencia (caso: RAG para soporte técnico SaaS)
-
Features no negociables:
- Multi-tenancy (aislamiento de datos por cliente)
- Hybrid search (queries con IDs de ticket + conceptuales)
- Metadata filtering (filtrar por producto, versión, idioma)
-
Nivel mínimo:
- Multi-tenancy: aislamiento nativo (no solo metadata filter)
- Hybrid search: built-in o sparse vectors
- Metadata filtering: AND/OR + rangos
-
Proveedores que sobreviven:
- ✅ Weaviate (multi-tenancy nativa, hybrid nativo, filtering avanzado)
- ⚠️ Pinecone (namespaces buenos, hybrid via sparse, filtering bueno)
- ⚠️ Milvus (partitions, hybrid, filtering bueno)
- ❌ ChromaDB (multi-tenancy limitada, sin hybrid)
- ⚠️ Qdrant (multi-tenancy via payload filter, sparse vectors)
Ejercicio 2: Tabla de scoring personalizada
Asigna puntuación (1-5) a cada proveedor para las 6 features, ponderando por importancia para tu caso:
| Feature | Peso | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|---|
| Metadata filtering | ||||||
| Hybrid search | ||||||
| Multi-tenancy | ||||||
| Batch operations | ||||||
| SDK quality | ||||||
| Scaling | ||||||
| Total ponderado |
Solución de referencia (caso: RAG documentation interno, equipo pequeño)
| Feature | Peso | ChromaDB | Pinecone | Weaviate | Qdrant | Milvus |
|---|---|---|---|---|---|---|
| Metadata filtering | 4 | 3 (12) | 4 (16) | 5 (20) | 5 (20) | 4 (16) |
| Hybrid search | 5 | 1 (5) | 3 (15) | 5 (25) | 4 (20) | 4 (20) |
| Multi-tenancy | 1 | 2 (2) | 4 (4) | 5 (5) | 3 (3) | 4 (4) |
| Batch operations | 3 | 3 (9) | 2 (6) | 4 (12) | 5 (15) | 5 (15) |
| SDK quality | 3 | 5 (15) | 4 (12) | 3 (9) | 5 (15) | 3 (9) |
| Scaling | 2 | 1 (2) | 5 (10) | 4 (8) | 4 (8) | 5 (10) |
| Total ponderado | 45 | 63 | 79 | 81 | 74 |
Resultado: Qdrant y Weaviate lideran. Para un equipo pequeño sin necesidad de multi-tenancy, Qdrant gana por filtering + SDK + batch.
Ejercicio 3: Implementar filtrado avanzado
Usando ChromaDB, implementa una query que:
- Busque documentos sobre "optimización de rendimiento"
- Filtre por
version >= 3ANDlanguage = "es" - Retorne los 3 resultados más relevantes con scores
Solución
import chromadb
client = chromadb.PersistentClient(path="./ejercicio_filter")
collection = client.get_or_create_collection(
name="docs",
metadata={"hnsw:space": "cosine"}
)
collection.add(
documents=[
"Optimización de queries en bases de datos",
"Mejora de rendimiento en APIs REST",
"Performance tuning para sistemas distribuidos",
"Guía de CSS para diseñadores",
"Cómo optimizar rendimiento en Python v2",
],
metadatas=[
{"version": 3, "language": "es"},
{"version": 4, "language": "es"},
{"version": 3, "language": "en"},
{"version": 2, "language": "es"},
{"version": 2, "language": "es"},
],
ids=["d1", "d2", "d3", "d4", "d5"]
)
results = collection.query(
query_texts=["optimización de rendimiento"],
n_results=3,
where={
"$and": [
{"version": {"$gte": 3}},
{"language": {"$eq": "es"}}
]
}
)
for doc, dist, meta in zip(
results["documents"][0],
results["distances"][0],
results["metadatas"][0]
):
score = 1 - dist
print(f"Score: {score:.4f} | v{meta['version']} | {doc}")
Resultado esperado: Solo d1 y d2 pasan el filtro (version >= 3 AND language = "es"). d3 falla por idioma, d4 y d5 por versión.
Ejercicio 4: Diseñar estrategia multi-tenant
Tu empresa tiene 3 clientes (Acme, Globex, Initech) con documentos separados. Diseña la estrategia de multi-tenancy para:
- Escenario A: ChromaDB (desarrollo)
- Escenario B: Pinecone (producción)
Para cada uno, escribe el código de inserción y query que garantice aislamiento.
Solución
# Escenario A: ChromaDB — collection per tenant
import chromadb
client = chromadb.PersistentClient(path="./multi_tenant")
tenants = ["acme", "globex", "initech"]
for tenant in tenants:
col = client.get_or_create_collection(name=f"docs_{tenant}")
col.add(
documents=[f"Documento interno de {tenant}"],
ids=[f"{tenant}_doc_1"]
)
acme_results = client.get_collection("docs_acme").query(
query_texts=["documento interno"], n_results=5
)
# Solo retorna docs de Acme — aislamiento por collection
# Escenario B: Pinecone — namespace per tenant
from pinecone import Pinecone
pc = Pinecone(api_key="...")
index = pc.Index("production-rag")
for tenant in ["acme", "globex", "initech"]:
index.upsert(
vectors=[{"id": f"{tenant}_1", "values": embedding, "metadata": {"text": "..."}}],
namespace=tenant
)
results = index.query(
vector=query_embedding,
top_k=5,
namespace="acme" # aislamiento por namespace
)
# Solo retorna docs del namespace "acme"
Diferencia clave: ChromaDB crea colecciones separadas (más overhead de gestión). Pinecone usa namespaces dentro del mismo index (más eficiente, con aislamiento nativo).
Ejercicio 5: Feature decision matrix
Dado el siguiente caso, recomienda un proveedor con justificación:
Caso: Empresa de e-commerce. 5M productos. Queries con nombre de producto exacto + conceptuales ("zapatos cómodos para correr"). 200 marcas (multi-tenant por marca). Equipo de 10 con 2 DevOps. Presupuesto medio.
Solución
Análisis de features no negociables:
- Hybrid search — Obligatorio (nombre exacto + conceptual) → Elimina ChromaDB
- Multi-tenancy — 200 marcas → Necesita soporte robusto → Elimina Qdrant (payload filter a 200 tenants es viable pero no ideal)
- Scaling — 5M vectores → Todas excepto ChromaDB
Candidatas finales: Weaviate, Pinecone, Milvus
Scoring:
| Feature | Weaviate | Pinecone | Milvus |
|---|---|---|---|
| Hybrid search | ✅ Nativo (alpha) | ⚠️ Sparse vectors | ✅ |
| Multi-tenancy 200 marcas | ✅ Nativo (offload) | ✅ Namespaces | ✅ Partitions |
| Scaling 5M | ✅ | ✅ | ✅ (overkill) |
| Equipo DevOps | ✅ Self-hosted viable | ✅ Zero ops | ⚠️ Complejo |
Recomendación: Weaviate — hybrid search nativo es el diferenciador principal para e-commerce. Multi-tenancy con offload de marcas inactivas reduce costos. El equipo tiene 2 DevOps que pueden operar self-hosted.
Alternativa: Pinecone si el equipo prefiere zero ops, aceptando que hybrid search requiere más trabajo con sparse vectors.
🔗 Conexión con proyecto (Decision Tree)
Esta cápsula te da los criterios técnicos para los nodos de decisión de tu decision tree:
¿Necesitas hybrid search?
├── SÍ → Weaviate (nativo) o Qdrant/Milvus (sparse)
└── NO → Cualquiera sirve
│
¿Necesitas multi-tenancy nativa?
├── SÍ → Weaviate o Pinecone
└── NO → Qdrant o ChromaDB (si escala < 1M)
Combina estos criterios con los de managed vs self-hosted (cápsula anterior) y costos (siguiente cápsula) para completar tu árbol de decisión.
📝 Resumen
- Metadata filtering es universal, pero la profundidad varía: Qdrant y Weaviate lideran con payload indexes y geo-filtering.
- Hybrid search es esencial para RAG con queries específicas — Weaviate es la opción más madura con fusión RRF nativa.
- Multi-tenancy es requisito de seguridad en SaaS — Weaviate tiene la implementación más completa con tenant offload.
- Batch operations importan en ingesta masiva — Pinecone es el más lento (100 vectors/request), Qdrant y Milvus los más rápidos.
- SDK quality afecta velocidad de desarrollo — ChromaDB gana en simplicidad, Qdrant en type safety y documentación.
- Scaling y quantization determinan el costo a largo plazo — Qdrant lidera en quantization, Milvus en escala absoluta.
- No hay proveedor que gane en todas las categorías; define tus "no negociables" primero y compara después.
📚 Recursos adicionales
- ChromaDB Filtering Docs — Where filters syntax
- Pinecone Metadata Filtering — Filter operators
- Weaviate Hybrid Search — Alpha, fusion, BM25
- Qdrant Filtering — Payload conditions
- Qdrant Quantization Guide — Scalar, PQ, Binary
- Milvus Multi-tenancy — Partition-based isolation
- ANN Benchmarks — Performance comparison independiente
- VectorHub (Superlinked) — Recursos educativos sobre vector search
Tiempo de lectura: 25-30 minutos Siguiente: 05-costos-tradeoffs.md