Módulo 3: Features Esenciales para RAG
Cápsula 04: Multi-tenancy (Aislamiento de datos)
🎯 Objetivo de la cápsula
Entender estrategias de multi-tenancy para RAG SaaS, comparar trade-offs (collection per tenant vs metadata filtering), y diseñar arquitectura segura con isolation garantizado.
Al finalizar esta cápsula:
- ✅ Explicarás qué es multi-tenancy y por qué es crítico para SaaS
- ✅ Compararás 3 estrategias (collection, metadata, namespace)
- ✅ Identificarás security considerations (data leakage)
- ✅ Diseñarás multi-tenancy según escala
Tiempo estimado: 8-10 minutos
🏢 ¿Qué es Multi-tenancy?
Definición
Multi-tenancy = Múltiples usuarios/empresas (tenants) comparten infraestructura, pero sus datos están aislados lógicamente.
Ejemplo: RAG SaaS
SaaS Product: "KnowledgeBase AI"
- Customer A (Acme Corp): 50K documentos
- Customer B (Beta Inc): 30K documentos
- Customer C (Gamma LLC): 100K documentos
Total: 180K documentos en misma vector database
Requisito: Customer A NO puede ver datos de Customer B/C
Clave: Isolation lógico (no físico). Misma DB, pero queries filtradas por tenant.
🔒 ¿Por qué es crítico para RAG SaaS?
Problema 1: Data Leakage (Security)
Sin multi-tenancy:
# ❌ VULNERABLE: Busca en TODOS los tenants
results = db.query(
query_embedding=embed("Show me sales data"),
k=10
)
# Resultado: Customer A puede ver docs de Customer B, C
# ← GDPR/SOC2 violation!
Con multi-tenancy:
# ✅ SEGURO: Solo busca en tenant actual
results = db.query(
query_embedding=embed("Show me sales data"),
where={"tenant_id": current_user.tenant_id}, # Filter
k=10
)
# Resultado: Customer A solo ve sus propios docs
Problema 2: Performance Degradation
Sin isolation:
- Customer A (10K docs) + Customer B (1M docs) en mismo search space
- Query de Customer A: Busca en 1.01M docs (slow)
- Latency: 150ms (dominated por 1M docs de Customer B)
Con isolation:
- Query de Customer A: Busca en 10K docs (fast)
- Latency: 5ms
Ganancia: 30x speedup.
Problema 3: Noisy Neighbors
Escenario: Customer B ejecuta bulk ingestion (1M inserts)
Sin isolation:
- CPU/RAM spike afecta Customer A queries
- Customer A latency: 50ms → 500ms (10x degradación)
Con isolation (collection per tenant):
- Bulk ingestion de Customer B NO afecta Customer A
- Latency estable
🏗️ Estrategia 1: Collection per Tenant
Arquitectura
Vector Database
├─ Collection: tenant_a (50K vectores)
├─ Collection: tenant_b (30K vectores)
└─ Collection: tenant_c (100K vectores)
Query Customer A:
db.get_collection("tenant_a").query(...)
Clave: Cada tenant tiene collection dedicada (namespace físico).
Ventajas
-
✅ Isolation perfecto
- Físicamente separado (impossible data leakage)
- No requiere metadata filtering
-
✅ Performance independiente
- Query en tenant A NO afecta tenant B
- No noisy neighbors
-
✅ Índices independientes
- Cada tenant puede tener configuración HNSW distinta
- Rebuild index de tenant A no afecta B
-
✅ Fácil de migrar
- Puedes mover tenant A a otra DB sin afectar B/C
Desventajas
-
❌ Overhead de collections
- Cada collection consume metadata (índice HNSW)
- 1000 collections × 50 MB overhead = 50 GB
-
❌ No escala con muchos tenants
- ChromaDB: Max 10K collections recomendado
- Pinecone: Max 100 collections per project
-
❌ Complejidad de setup
- Crear/eliminar collections dinámicamente
- Manejar tenant creation/deletion
Cuándo usar
- ✅ <1000 tenants
- ✅ Tenants grandes (>10K docs cada uno)
- ✅ Strict isolation requerido (compliance)
- ✅ Managed service (Pinecone, Weaviate)
Ejemplo: Enterprise SaaS con 50-500 clientes grandes.
🏗️ Estrategia 2: Metadata Filtering (Shared Collection)
Arquitectura
Vector Database
└─ Collection: all_tenants (180K vectores)
├─ Doc 1: {tenant_id: "a", content: "..."}
├─ Doc 2: {tenant_id: "a", content: "..."}
├─ Doc 3: {tenant_id: "b", content: "..."}
└─ Doc 4: {tenant_id: "c", content: "..."}
Query Customer A:
collection.query(
...,
where={"tenant_id": "a"}
)
Clave: Todos los tenants en misma collection, isolation mediante metadata filter.
Ventajas
-
✅ Escala con muchos tenants
- 10K+ tenants en single collection (no overhead)
-
✅ Setup simple
- No crear/eliminar collections dinámicamente
- Solo agregar
tenant_idfield
-
✅ Flexible
- Puedes agregar más metadata (region, tier)
-
✅ Económico
- Menos overhead (single índice HNSW)
Desventajas
-
❌ Isolation NO perfecto
- Bug en filtering = data leakage risk
- Requiere testing exhaustivo
-
❌ Performance compartida
- Bulk ingestion de tenant B afecta latency de tenant A
- Noisy neighbors problem
-
❌ Índice compartido
- Rebuild index afecta TODOS los tenants
-
❌ Requiere pre-filtering support
- DB debe soportar efficient metadata filtering
- Post-filtering es ineficiente (busca en todo primero)
Cuándo usar
- ✅ >1000 tenants (muchos small tenants)
- ✅ Tenants pequeños (<10K docs cada uno)
- ✅ Self-hosted con RAM limitado
- ✅ DB con pre-filtering eficiente (Pinecone, Weaviate)
Ejemplo: SMB SaaS con 5000+ pequeñas empresas.
🏗️ Estrategia 3: Namespace (Hybrid)
Arquitectura
Vector Database
├─ Namespace: region_us
│ ├─ Collection: tenant_a
│ └─ Collection: tenant_b
└─ Namespace: region_eu
├─ Collection: tenant_c
└─ Collection: tenant_d
Query Customer A (US):
db.namespace("region_us")
.get_collection("tenant_a")
.query(...)
Clave: Agrupar tenants en namespaces (e.g., por región, tier).
Ventajas
-
✅ Balance entre estrategias 1 y 2
- Isolation por namespace + por collection
-
✅ Compliance regional
- EU tenants en namespace EU (GDPR)
- US tenants en namespace US
-
✅ Performance tiers
- Premium tenants en namespace dedicado (hardware mejor)
- Free tenants en namespace compartido
Desventajas
-
❌ Complejidad arquitectónica
- Manejar múltiples namespaces + collections
-
❌ No todos los DBs soportan
- Pinecone: Sí (namespaces nativos)
- ChromaDB: No (workaround con prefixes)
- Weaviate: Parcial (tenants)
Cuándo usar
- ✅ Multi-region deployment (compliance)
- ✅ Performance tiers (premium vs free)
- ✅ Mix de tenants grandes + pequeños
Ejemplo: Global SaaS con compliance regional + tiers.
📊 Comparación: Collection vs Metadata vs Namespace
| Dimensión | Collection per Tenant | Metadata Filtering | Namespace Hybrid |
|---|---|---|---|
| Isolation | ✅ Perfecto | ⚠️ Lógico (bug risk) | ✅ Perfecto |
| Scale (tenants) | ❌ <1K | ✅ >10K | ⚠️ <5K |
| Performance | ✅ Independiente | ❌ Compartido | ✅ Independiente |
| Setup | ❌ Complejo | ✅ Simple | ❌ Muy complejo |
| Cost (RAM) | ❌ Alto (overhead) | ✅ Bajo | ⚠️ Medio |
| Migration | ✅ Fácil | ❌ Difícil | ⚠️ Medio |
| Compliance | ✅ Estricto | ⚠️ Requiere audit | ✅ Estricto |
Decision Matrix
Tenants count:
< 100 → Collection per Tenant
100-1000 → Collection o Namespace (según budget)
> 1000 → Metadata Filtering
Compliance:
Strict (finance, health) → Collection per Tenant
Moderate → Namespace
Relaxed → Metadata Filtering
Budget:
High → Collection per Tenant (managed service)
Medium → Namespace (self-hosted)
Low → Metadata Filtering (single collection)
🔒 Security Considerations
1. Testing de Isolation
# Test crítico: Verificar NO data leakage
def test_tenant_isolation():
# Insert docs para tenant A y B
collection.add(
documents=["Secret A"],
metadatas=[{"tenant_id": "a"}]
)
collection.add(
documents=["Secret B"],
metadatas=[{"tenant_id": "b"}]
)
# Query como tenant A
results = collection.query(
query_embeddings=[...],
where={"tenant_id": "a"},
n_results=10
)
# Assert: NO debe devolver docs de tenant B
assert all(r['tenant_id'] == 'a' for r in results)
Crítico: Ejecutar este test en CI/CD (cada deploy).
2. Middleware de Isolation
# Enforce tenant_id en TODAS las queries
class TenantMiddleware:
def query(self, query_embedding, current_user, **kwargs):
# Inject tenant_id automáticamente
where = kwargs.get('where', {})
where['tenant_id'] = current_user.tenant_id
# Override where clause (no confiar en client)
kwargs['where'] = where
return self.db.query(query_embedding, **kwargs)
Clave: NUNCA confiar en client para enviar tenant_id correcto.
3. Audit Logging
# Log TODAS las queries con tenant_id
logger.info({
"action": "query",
"tenant_id": current_user.tenant_id,
"user_id": current_user.id,
"query": query_text,
"timestamp": time.now()
})
Compliance: SOC2, GDPR requieren audit trail.
✅ Checklist de comprensión
Verifica que entendiste esta cápsula:
-
¿Qué es multi-tenancy?
- Respuesta: Múltiples usuarios/empresas comparten infraestructura, pero datos están aislados lógicamente.
-
¿Cuál es ventaja principal de collection per tenant?
- Respuesta: Isolation perfecto (físico), performance independiente, no noisy neighbors.
-
¿Cuándo usar metadata filtering vs collection per tenant?
- Respuesta: Metadata filtering para >1000 tenants pequeños. Collection per tenant para <1000 tenants grandes o strict compliance.
-
¿Qué es el problema de "noisy neighbors"?
- Respuesta: Bulk ingestion/queries de tenant B afectan latency de tenant A (si comparten collection).
-
¿Qué test crítico ejecutar para security?
- Respuesta: Test que query de tenant A NO devuelve docs de tenant B (isolation test).
Si respondiste 4-5/5 correctamente → ✅ Listo para Cápsula 05 (Batch Operations)
🔗 Conexión con RAG
¿Cómo multi-tenancy afecta tu RAG SaaS?
Caso: KnowledgeBase AI (RAG SaaS)
Sin multi-tenancy (MVP):
- 10 customers
- Single collection (todos mezclados)
- Bug: Customer A ve docs de Customer B
- Result: Security breach → Perder clientes
Con multi-tenancy (Production):
- 500 customers
- Estrategia: Metadata filtering (escala bien)
- Security: Middleware enforce tenant_id
- Result: SOC2 certified, enterprise-ready
🚀 Siguiente paso
Ya conoces features de search (filtering, hybrid) y isolation (multi-tenancy). Ahora aprenderás feature operacional: Batch Operations.
Próxima cápsula: 05 - Batch Operations (Ingestion eficiente)
Aprenderás:
- Por qué single insert es ineficiente (2.7 horas para 1M docs)
- Optimal batch size (1000-5000)
- Bulk updates y deletes
- Rebuild index strategies
Clave: Batch operations son esenciales para ingerir grandes datasets eficientemente.
Tiempo de lectura: 8-10 minutos
Siguiente: 05-batch-operations.md