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

  1. ✅ Isolation perfecto

    • Físicamente separado (impossible data leakage)
    • No requiere metadata filtering
  2. ✅ Performance independiente

    • Query en tenant A NO afecta tenant B
    • No noisy neighbors
  3. ✅ Índices independientes

    • Cada tenant puede tener configuración HNSW distinta
    • Rebuild index de tenant A no afecta B
  4. ✅ Fácil de migrar

    • Puedes mover tenant A a otra DB sin afectar B/C

Desventajas

  1. ❌ Overhead de collections

    • Cada collection consume metadata (índice HNSW)
    • 1000 collections × 50 MB overhead = 50 GB
  2. ❌ No escala con muchos tenants

    • ChromaDB: Max 10K collections recomendado
    • Pinecone: Max 100 collections per project
  3. ❌ 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

  1. ✅ Escala con muchos tenants

    • 10K+ tenants en single collection (no overhead)
  2. ✅ Setup simple

    • No crear/eliminar collections dinámicamente
    • Solo agregar tenant_id field
  3. ✅ Flexible

    • Puedes agregar más metadata (region, tier)
  4. ✅ Económico

    • Menos overhead (single índice HNSW)

Desventajas

  1. ❌ Isolation NO perfecto

    • Bug en filtering = data leakage risk
    • Requiere testing exhaustivo
  2. ❌ Performance compartida

    • Bulk ingestion de tenant B afecta latency de tenant A
    • Noisy neighbors problem
  3. ❌ Índice compartido

    • Rebuild index afecta TODOS los tenants
  4. ❌ 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

  1. ✅ Balance entre estrategias 1 y 2

    • Isolation por namespace + por collection
  2. ✅ Compliance regional

    • EU tenants en namespace EU (GDPR)
    • US tenants en namespace US
  3. ✅ Performance tiers

    • Premium tenants en namespace dedicado (hardware mejor)
    • Free tenants en namespace compartido

Desventajas

  1. ❌ Complejidad arquitectónica

    • Manejar múltiples namespaces + collections
  2. ❌ 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ónCollection per TenantMetadata FilteringNamespace 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