Multitenancy: Cómo servir a miles de clientes con una sola aplicación

Fecha de publicación: 2025-01-15

Comparación de Modelos de Multitenancy

En el mundo del desarrollo de software SaaS, una de las decisiones arquitectónicas más importantes es cómo manejar múltiples clientes con una sola aplicación. Multitenancy es la respuesta elegante a este desafío: permite que la misma aplicación y infraestructura sirva a múltiples clientes independientes de manera eficiente y segura.

¿Qué es multitenancy?

Multitenancy es un modelo arquitectónico en el que la misma aplicación y, por lo general, la misma infraestructura de base de datos y servidores, atienden a varios clientes independientes a los que llamamos tenants o inquilinos.
Cada tenant:
  • Tiene sus propios usuarios, datos y configuraciones.
  • No puede acceder ni interferir con la información de otro tenant.
  • Siente que la aplicación es "suya", aunque en realidad la comparte con otros.

Analogía del edificio de oficinas

Imagina un edificio de oficinas:
  • El edificio = tu aplicación y la infraestructura
  • Cada oficina = un tenant (cliente)
  • Todos usan la misma estructura física (ascensores, electricidad, internet), pero cada oficina tiene llave propia, acceso controlado y decoración personalizada

Tipos de multitenancy

Aunque el concepto es único, la implementación puede variar bastante. Hay tres modelos principales que se usan en la industria:

1. Multitenancy con aislamiento lógico en la base de datos

En este modelo:
  • Hay una sola instancia de base de datos (p. ej., un PostgreSQL, MySQL, etc.)
  • Todas las tablas contienen los datos de todos los tenants
  • Cada fila tiene un campo tenant_id que identifica a qué cliente pertenece
Cómo se aísla la información:
  • A nivel de aplicación: todas las consultas filtran por tenant_id
  • A nivel de base de datos: se pueden usar políticas de Row-Level Security (RLS) para reforzar la seguridad
-- Ejemplo de tabla con tenant_id
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL,
    email VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Índice para optimizar consultas por tenant
CREATE INDEX idx_users_tenant_id ON users(tenant_id);

-- Row Level Security (RLS) para garantizar aislamiento
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Política que solo permite ver datos del tenant actual
CREATE POLICY tenant_isolation ON users
    USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
Ventajas
  • Muy eficiente: un solo pool de conexiones, un solo esquema que mantener
  • Menor coste operativo: menos instancias y menos backups que gestionar
  • Escalabilidad: más fácil añadir nuevos tenants
Desventajas
  • Riesgo mayor si hay un bug en las consultas: un fallo en el filtro tenant_id puede exponer datos de otro cliente
  • Migraciones de datos más complejas: un cambio de esquema afecta a todos los tenants a la vez
Ejemplos en la vida real
  • Slack: cada workspace es un tenant, pero los mensajes y usuarios viven en las mismas bases de datos distribuidas
  • Trello: múltiples tableros y equipos comparten infraestructura

2. Multitenancy con esquema dedicado

En este modelo:
  • Hay una sola instancia de base de datos física, pero cada tenant tiene su propio esquema dentro de la base
  • Ejemplo: en PostgreSQL, schema_tenant_1, schema_tenant_2, etc.

Modelo de Esquemas Dedicados

-- Creación de esquemas por tenant
CREATE SCHEMA tenant_company_a;
CREATE SCHEMA tenant_company_b;

-- Tablas en esquema dedicado
CREATE TABLE tenant_company_a.users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE tenant_company_b.users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Ventajas
  • Mejor aislamiento que el modelo de un solo esquema
  • Migraciones más controladas: puedes probar un cambio de esquema en un tenant antes de aplicarlo a todos
  • Posible personalización de datos y estructuras para tenants específicos
Desventajas
  • Gestión más compleja: crear un esquema nuevo por tenant, mantener scripts de migración separados
  • Escalabilidad limitada si tienes miles de tenants (demasiados esquemas en una sola base)
Ejemplos
  • Aplicaciones B2B con pocos clientes de gran tamaño que necesitan personalizaciones específicas
  • Plataformas de ERP que alojan a empresas grandes con requisitos distintos

3. Multitenancy con base de datos dedicada

En este modelo:
  • Cada tenant tiene su propia base de datos
  • Puede ser incluso en un servidor distinto

Modelo de Bases de Datos Dedicadas

Ventajas
  • Aislamiento casi total de datos
  • Facilidad para cumplir requisitos de compliance (ISO, HIPAA, GDPR) que exigen separación física
  • Escalabilidad independiente: puedes asignar más recursos a un cliente que lo necesite
Desventajas
  • Coste mucho mayor: más instancias que mantener y monitorizar
  • Mantenimiento y despliegues más complejos: las migraciones hay que repetirlas en todas las bases
  • La automatización se vuelve obligatoria para manejar muchos tenants
Ejemplos
  • Sistemas bancarios que no pueden mezclar datos por regulación
  • Aplicaciones médicas que manejan historiales clínicos y requieren segregación estricta

Comparativa rápida

ModeloAislamientoCosteEscalabilidadComplejidad
Lógico (campo tenant_id)Bajo-MedioBajoAltoBaja
Esquema dedicadoMedioMedioMedio-AltoMedia
Base dedicadaAltoAltoMedioAlta

Factores para elegir el modelo

  • Regulación y compliance: si tus clientes están en sectores con normas estrictas, el aislamiento físico puede ser obligatorio
  • Cantidad de tenants: para miles de clientes, el aislamiento lógico es más manejable
  • Necesidad de personalización: si un tenant requiere cambios grandes en la estructura de datos, mejor esquema o base dedicada
  • Presupuesto: bases dedicadas implican un coste operativo mucho más alto

Implementación práctica: Spring Boot con PostgreSQL

Veamos cómo implementar el modelo de aislamiento lógico con Spring Boot y PostgreSQL:
// Entidad con tenant_id
@Entity
@Table(name = "users")
data class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    @Column(name = "tenant_id", nullable = false)
    val tenantId: UUID,

    @Column(nullable = false)
    val email: String,

    @Column(nullable = false)
    val name: String,

    @CreationTimestamp
    val createdAt: LocalDateTime = LocalDateTime.now()
)
// Interceptor para establecer el contexto del tenant
@Component
class TenantInterceptor : HandlerInterceptor {

    override fun preHandle(
        request: HttpServletRequest,
        response: HttpServletResponse,
        handler: Any
    ): Boolean {
        val token = extractJwtToken(request)
        val tenantId = extractTenantFromToken(token)
        
        // Establecer tenant en el contexto de la aplicación
        TenantContext.setCurrentTenant(tenantId)
        
        return true
    }

    override fun afterCompletion(
        request: HttpServletRequest,
        response: HttpServletResponse,
        handler: Any,
        ex: Exception?
    ) {
        // Limpiar contexto después de la request
        TenantContext.clear()
    }
}
// Repository con filtro automático por tenant
@Repository
interface UserRepository : JpaRepository<User, Long> {
    
    @Query("SELECT u FROM User u WHERE u.tenantId = :tenantId")
    fun findByTenantId(@Param("tenantId") tenantId: UUID): List<User>
    
    @Query("SELECT u FROM User u WHERE u.tenantId = :tenantId AND u.email = :email")
    fun findByTenantIdAndEmail(
        @Param("tenantId") tenantId: UUID, 
        @Param("email") email: String
    ): User?
}

// Service que usa el contexto del tenant automáticamente
@Service
class UserService(private val userRepository: UserRepository) {
    
    fun getCurrentTenantUsers(): List<User> {
        val currentTenantId = TenantContext.getCurrentTenant()
        return userRepository.findByTenantId(currentTenantId)
    }
    
    fun createUser(email: String, name: String): User {
        val currentTenantId = TenantContext.getCurrentTenant()
        val user = User(
            tenantId = currentTenantId,
            email = email,
            name = name
        )
        return userRepository.save(user)
    }
}

Más ejemplos de empresas multitenant

  • Shopify: cada tienda es un tenant, datos aislados pero compartiendo backend y capas de infraestructura global
  • Netflix (en la capa de gestión de usuarios y cuentas): cada cuenta es un tenant con sus propios perfiles y datos de consumo
  • Atlassian (Jira, Confluence): instancias multitenant que alojan múltiples empresas, cada una con sus propios proyectos y configuraciones
  • Zoom: cada organización o cuenta empresarial es un tenant; las reuniones, usuarios y grabaciones se segmentan por account_id
  • Salesforce: pionero en SaaS multitenant, cada organización tiene su configuración personalizada pero comparte la misma infraestructura masiva

Buenas prácticas en multitenancy

Seguridad

  • Autenticación con contexto de tenant: incluir tenant_id en el token JWT
  • Validación doble: aplicar filtros en la aplicación y Row-Level Security en la base
  • Logging con tenant_id para auditoría y depuración rápida

Performance

  • Limitación de recursos por tenant: evitar que un cliente acapare CPU, memoria o ancho de banda
  • Índices optimizados por tenant_id en todas las tablas principales
  • Caching con claves que incluyan el tenant_id

Automatización

  • Scripts automatizados para el alta de nuevos tenants
  • Migraciones de base de datos automáticas y versionadas
  • Monitoreo por tenant para detectar problemas específicos

Observabilidad

  • Métricas segmentadas por tenant
  • Alertas configurables por cliente
  • Dashboards de uso y performance por tenant

Desafíos comunes y cómo resolverlos

Data Leakage (Filtración de datos)

El mayor riesgo: consultas que olvidan filtrar por tenant_id

Solución: Implementa Row-Level Security a nivel de base de datos como segunda línea de defensa, y tests automatizados que verifiquen el aislamiento.

Balanceamiento de carga por tenant

Algunos tenants pueden consumir más recursos que otros

Solución: Implementa throttling y rate limiting por tenant, y considera mover clientes pesados a infraestructura dedicada.

Migraciones complejas

Cambios de esquema que afectan a miles de tenants

Solución: Migraciones incrementales, feature flags por tenant, y estrategias de rollback granulares.

Arquitectura del Modelo de Aislamiento Lógico

Conclusión

El multitenancy es un patrón clave para construir aplicaciones SaaS escalables y eficientes. Elegir el modelo correcto depende de tu tipo de clientes, requisitos de seguridad y presupuesto. Ya sea que uses un solo esquema con tenant_id o una base de datos por cliente, el objetivo es el mismo: servir a muchos como si sirvieras a uno, garantizando seguridad, personalización y eficiencia.

Próximos pasos

En próximas entregas, profundizaré en cómo implementar un modelo multitenant lógico completo con Spring Boot, PostgreSQL y autenticación JWT, paso a paso, incluyendo:

  • Configuración de Row-Level Security en PostgreSQL
  • Implementación de interceptors y filtros automáticos
  • Estrategias de testing para aplicaciones multitenant
  • Monitoreo y observabilidad por tenant
¿Tienes experiencia implementando multitenancy? ¿Qué desafíos has encontrado? Me encantaría conocer tu perspectiva.