Arquitectura Multi-Tenancy
Diseña aplicaciones multi-tenant con bases de datos compartidas o aisladas, routing tenant-aware y estrategias de aislamiento de datos.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
La multi-tenancy es una arquitectura donde una única instancia de software sirve a múltiples clientes (tenants) manteniendo sus datos y configuración aislados. El compromiso es entre simplicidad operativa (todo compartido) y aislamiento de datos (todo separado). Elegir el modelo correcto afecta la escalabilidad, seguridad y cumplimiento.
Cuándo Usar
Usa este recurso cuando:
- Construyes aplicaciones SaaS que sirven a múltiples organizaciones
- Debes cumplir requisitos de compliance (SOC 2, HIPAA) que exigen segregación de datos
- Optimizas costos de infraestructura compartiendo compute entre tenants
- Escalas de cientos a miles de tenants con rendimiento predecible
Solución
Base de Datos Compartida con Tenant ID (PostgreSQL)
-- Row-Level Security asegura aislamiento de tenant
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
amount DECIMAL(10,2) NOT NULL
);
-- Habilitar RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Política: tenants solo ven sus propios datos
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant')::UUID);
Middleware Tenant-Aware (Node.js)
function tenantMiddleware(req, res, next) {
const tenantId = req.headers['x-tenant-id'] || req.subdomain;
if (!tenantId) {
return res.status(400).json({ error: 'Tenant ID requerido' });
}
// Establecer contexto de tenant para esta request
req.tenantId = tenantId;
// Aplicar a conexión de base de datos
db.query("SET app.current_tenant = $1", [tenantId]);
next();
}
Migración Schema-por-Tenant
from sqlalchemy import create_engine, MetaData
def migrate_tenant_schema(tenant_id: str):
engine = create_engine("postgresql://user:pass@localhost/db")
with engine.begin() as conn:
conn.execute("CREATE SCHEMA IF NOT EXISTS tenant_{}".format(tenant_id))
# Ejecutar migraciones dentro del schema del tenant
metadata = MetaData(schema="tenant_{}".format(tenant_id))
metadata.create_all(conn)
Explicación
Tres modelos de multi-tenancy:
| Modelo | Aislamiento | Costo | Complejidad |
|---|---|---|---|
| BD Compartida + Tenant ID | Bajo (requiere RLS) | Más bajo | Baja |
| Schema-por-tenant | Medio | Medio | Media |
| Base de datos por tenant | Alto | Más alto | Alta |
Estrategias de resolución de tenant:
- Subdominio: tenant1.app.com, tenant2.app.com
- Path: app.com/tenant1/, app.com/tenant2/
- Header: X-Tenant-ID en requests de API
- JWT claim: tenant embebido en token de auth
Variantes
| Enfoque | Ideal Para | Compromiso |
|---|---|---|
| Todo compartido | SaaS inicial | Más simple; aislamiento más débil |
| Compute compartido, storage aislado | SaaS mid-market | Balance de costo y compliance |
| Totalmente aislado | Enterprise/regulado | Mayor costo; aislamiento más fuerte |
| Cell-based | Escala global | Shards de tenants entre regiones |
Mejores Prácticas
- Nunca confíes en tenant ID del input del usuario: Siempre resuélvelo desde el contexto autenticado
- Indexa tenant_id primero: Cada query filtra por tenant; hazlo la columna líder
- Usa connection pooling con cuidado: Schema-por-tenant requiere switching de schema dinámico
- Backup por tenant: Schema-por-tenant hace trivial pg_dump por schema
- Cuotas de recursos: Limita CPU, storage y rate de API por tenant para prevenir vecinos ruidosos
Errores Comunes
- Filtro de tenant faltante: Un WHERE tenant_id = $1 olvidado expone todos los datos del cliente
- Caching sin scope de tenant: Las cache keys compartidas filtran datos entre tenants
- Jobs en background sin contexto de tenant: Las tareas programadas deben ejecutarse para cada tenant por separado
- Schemas hard-coded: Mezclar datos de tenant en código de aplicación crea agujeros de seguridad
- Logging sin awareness de tenant: Depurar problemas en producción requiere filtrar logs por tenant
Preguntas Frecuentes
P: ¿Puedo migrar de BD compartida a schema-por-tenant más tarde? R: Sí, pero requiere una migración significativa. Empieza con columnas tenant_id y RLS incluso si planeas dividir más tarde.
P: ¿Cómo manejo customizaciones específicas por tenant? R: Usa feature flags por tenant, configuración white-label, o UI metadata-driven. Evita branches de código separadas.
P: ¿El GDPR afecta el diseño de multi-tenancy? R: Sí. El derecho al olvido es más simple con schema-por-tenant (drop schema) que con tablas compartidas (borrar filas en muchas tablas).
Recursos Relacionados
ADR Template
A reusable template for Architecture Decision Records that capture context, decision, and consequences.
DocDatabase Schema Documentation Template
A template for documenting database schemas with entity relationships, field definitions, and migration history.
DocEngineering Handbook Template
A comprehensive template for team engineering handbooks covering standards, workflows, onboarding, and operational practices.
GuideREST API Design Guide
A comprehensive guide to designing clean, scalable, and maintainable REST APIs.
GuideDomain-Driven Design (DDD) — A Practical Guide
Learn DDD fundamentals: bounded contexts, entities, value objects, aggregates, and how to model complex business domains in code.