· nervico-team · arquitectura-cloud  · 12 min read

AWS para SaaS: implementando el patrón multi-tenant

Guía técnica para implementar arquitecturas multi-tenant en AWS: modelos de aislamiento, patrones de datos, gestión de identidad por tenant y costes reales de cada enfoque.

Guía técnica para implementar arquitecturas multi-tenant en AWS: modelos de aislamiento, patrones de datos, gestión de identidad por tenant y costes reales de cada enfoque.

Multi-tenancy es lo que permite que un producto SaaS sea rentable. Sin ella, cada cliente requiere su propia instancia de infraestructura, su propio despliegue y su propia gestión operativa. Con 10 clientes, eso es incómodo. Con 1.000, es insostenible.

Pero implementar multi-tenancy correctamente en AWS es más complejo de lo que parece. Las decisiones que tomas en las primeras semanas (cómo aislar datos entre tenants, cómo gestionar identidades, cómo distribuir la carga) determinan si tu SaaS escala hasta miles de clientes o se convierte en una pesadilla operativa a partir del centésimo.

Este artículo cubre los tres modelos principales de multi-tenancy en AWS, con sus trade-offs reales en coste, complejidad y aislamiento, y los patrones concretos que hemos visto funcionar en productos SaaS reales.

Qué significa multi-tenant en la práctica

Definición técnica

Un sistema multi-tenant es una instancia de software que sirve a múltiples clientes (tenants) desde una infraestructura compartida. Cada tenant tiene sus propios datos, su propia configuración y, en muchos casos, su propia personalización. Pero comparten el mismo código, los mismos servidores y, potencialmente, la misma base de datos.

La alternativa es single-tenant: una instancia completa del sistema para cada cliente. Es más simple de implementar pero exponencialmente más cara de operar.

Los tres ejes de decisión

Toda arquitectura multi-tenant se define por tres decisiones fundamentales:

  1. Aislamiento de compute: Cada tenant tiene su propia instancia de la aplicación, o comparten la misma instancia.
  2. Aislamiento de datos: Cada tenant tiene su propia base de datos, su propio esquema dentro de una base de datos compartida, o comparten tablas con una columna tenant_id.
  3. Aislamiento de red: Cada tenant tiene su propia VPC, o comparten subnets dentro de la misma VPC.

El nivel de aislamiento que elijas afecta directamente a tres cosas: el coste por tenant, la complejidad operativa y el nivel de seguridad. No hay una opción universalmente correcta.

Modelos de aislamiento en AWS

Modelo silo: aislamiento total por tenant

Cada tenant tiene su propia infraestructura dedicada: su propia VPC, sus propias instancias de compute, su propia base de datos y sus propias colas de mensajes.

Tenant A: VPC-A → ECS/EKS-A → RDS-A → S3 bucket-A
Tenant B: VPC-B → ECS/EKS-B → RDS-B → S3 bucket-B
Tenant C: VPC-C → ECS/EKS-C → RDS-C → S3 bucket-C

Ventajas:

  • Aislamiento máximo: Un problema en Tenant A (pico de carga, error de datos, breach de seguridad) no afecta a Tenant B.
  • Personalización completa: Cada tenant puede tener su propia versión del software, su propia configuración de infraestructura, incluso su propia región de AWS.
  • Cumplimiento regulatorio: Necesario cuando los clientes exigen que sus datos estén en infraestructura dedicada (sector financiero, sanitario, gobierno).

Desventajas:

  • Coste elevado: El coste mínimo por tenant incluye al menos una instancia de compute y una instancia de base de datos. En AWS, eso son 50-200 dolares/mes como mínimo por tenant.
  • Complejidad operativa: Cada nuevo tenant requiere provisionar infraestructura completa. Con 100 tenants, tienes 100 instancias de RDS que mantener, 100 clusters de ECS, 100 VPCs.
  • Despliegues lentos: Actualizar el software requiere desplegar en N entornos independientes. Sin automatización robusta, los despliegues se convierten en el cuello de botella.

Cuándo usarlo: Clientes enterprise que pagan más de 5.000 dolares/mes y exigen aislamiento contractual. Sectores regulados que requieren demostrar separación física de datos.

Automatización con Terraform:

# Módulo de tenant con aislamiento completo
module "tenant" {
  source = "./modules/tenant-silo"

  for_each = var.tenants

  tenant_id   = each.key
  tenant_name = each.value.name
  region      = each.value.region
  db_instance = each.value.db_instance_class

  vpc_cidr    = "10.${each.value.network_index}.0.0/16"

  tags = {
    TenantId    = each.key
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

Modelo pool: infraestructura completamente compartida

Todos los tenants comparten la misma infraestructura. La separación se implementa exclusivamente a nivel de aplicación, usando un identificador de tenant (tenant_id) en cada consulta, cada mensaje y cada operación.

Todos los tenants: VPC compartida → ECS compartido → RDS compartida → S3 compartido
                                                         |
                                               tenant_id en cada fila

Ventajas:

  • Coste mínimo por tenant: El coste marginal de añadir un nuevo tenant es prácticamente cero. No hay infraestructura adicional que provisionar.
  • Operaciones simplificadas: Un solo cluster, una sola base de datos, un solo despliegue. Actualizar el software afecta a todos los tenants simultáneamente.
  • Escalado eficiente: La infraestructura se dimensiona por carga total, no por tenant individual. Los recursos se comparten entre tenants de forma natural.

Desventajas:

  • Noisy neighbor: Un tenant que genera un pico de carga afecta al rendimiento de todos los demás. Sin throttling por tenant, un cliente puede saturar la base de datos para todos.
  • Riesgo de fuga de datos: Un bug en una consulta que olvida el filtro WHERE tenant_id = X puede exponer datos de un tenant a otro. Este es el riesgo más grave.
  • Complejidad en la aplicación: Cada capa de la aplicación debe ser consciente del tenant_id. Cada consulta, cada cache key, cada mensaje en cola debe incluirlo.

Cuándo usarlo: Productos con muchos tenants que pagan poco (planes de hasta 100 dolares/mes). Startups en fase early que necesitan minimizar costes de infraestructura.

Patrón de datos con DynamoDB:

PK (Partition Key)     | SK (Sort Key)        | Datos
-----------------------|----------------------|------------------
TENANT#acme            | USER#u001            | {name, email...}
TENANT#acme            | USER#u002            | {name, email...}
TENANT#acme            | ORDER#o001           | {total, status...}
TENANT#globex          | USER#u001            | {name, email...}
TENANT#globex          | ORDER#o001           | {total, status...}

Con este diseño, una consulta con PK = TENANT#acme solo devuelve datos de ese tenant. El aislamiento está garantizado por el diseño de la clave primaria.

Patrón con PostgreSQL (Row-Level Security):

-- Habilitar RLS en la tabla
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- Crear política que filtra por tenant_id automáticamente
CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.current_tenant')::uuid);

-- En la aplicación, antes de cada consulta:
SET app.current_tenant = 'acme-uuid-here';
SELECT * FROM orders; -- Solo devuelve pedidos de 'acme'

Row-Level Security en PostgreSQL es la forma más segura de implementar aislamiento en modelo pool con bases de datos relacionales. El filtro se aplica a nivel de motor de base de datos, no a nivel de aplicación. Incluso si el código de la aplicación tiene un bug, la base de datos no devolverá datos de otro tenant.

Modelo bridge: aislamiento selectivo

El modelo bridge combina elementos del silo y del pool. La capa de compute es compartida, pero los datos están aislados (base de datos por tenant o esquema por tenant).

Todos los tenants: VPC compartida → ECS compartido → RDS-A (tenant A)
                                                   → RDS-B (tenant B)
                                                   → RDS-C (tenant C)

Ventajas:

  • Aislamiento de datos real: Cada tenant tiene su propia base de datos. No hay riesgo de fuga de datos por un bug en una consulta.
  • Coste intermedio: La capa de compute se comparte (reduce coste), pero cada tenant tiene su propia base de datos (aumenta aislamiento).
  • Backup y restauración por tenant: Puedes restaurar la base de datos de un tenant específico sin afectar a los demás.

Desventajas:

  • Gestión de conexiones: La aplicación debe mantener un pool de conexiones por cada base de datos de tenant. Con 500 tenants, son 500 connection pools.
  • Migraciones de esquema: Cada actualización de esquema debe ejecutarse en todas las bases de datos de tenant. Sin automatización, es un proceso propenso a errores.
  • Coste de base de datos: Una instancia RDS db.t4g.micro cuesta 12,41 dolares/mes. Con 100 tenants, son 1.241 dolares/mes solo en bases de datos.

Cuándo usarlo: Productos con tenants de tamaño medio (100-5.000 dolares/mes) que requieren aislamiento de datos pero no justifican infraestructura dedicada completa.

Gestión de identidad multi-tenant

Amazon Cognito como identity provider

Cognito permite gestionar la autenticación y autorización de usuarios dentro de un contexto multi-tenant. Hay dos enfoques principales:

Un User Pool por tenant (modelo silo):

Tenant A → User Pool A → App Client A
Tenant B → User Pool B → App Client B

Cada tenant tiene su propio User Pool con sus propias políticas de contraseña, sus propios proveedores de identidad (SAML, OIDC) y su propia configuración de MFA. Máximo aislamiento, pero el límite por defecto es 1.000 User Pools por cuenta de AWS.

Un User Pool compartido con atributos custom (modelo pool):

User Pool compartido → custom:tenant_id = "acme"
                     → custom:tenant_id = "globex"

Todos los usuarios están en el mismo User Pool. El tenant_id se almacena como atributo personalizado y se incluye en el token JWT. La aplicación lee el tenant_id del token y filtra los datos.

{
  "sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "user@acme.com",
  "custom:tenant_id": "acme",
  "custom:role": "admin",
  "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_XXXXX",
  "exp": 1694300000
}

Recomendación: Para la mayoría de SaaS, empieza con un User Pool compartido. Es más simple de operar y escala hasta decenas de miles de tenants. Migra a un User Pool por tenant solo cuando los clientes exigen proveedores de identidad federados (SAML con su propio Active Directory).

Tenant-aware API Gateway

Cada petición a tu API debe identificar al tenant. Las opciones más comunes:

  • Subdominio: acme.tuproducto.com, globex.tuproducto.com. El tenant se extrae del header Host.
  • Path: /api/v1/tenants/acme/orders. Explícito pero más verboso.
  • Header: X-Tenant-ID: acme. Limpio pero requiere que el cliente envíe el header.
  • Token JWT: El tenant_id está en el token. La API lo extrae automáticamente. Es el enfoque más seguro porque el tenant_id está firmado y no puede ser manipulado.

Patrones de datos multi-tenant en AWS

DynamoDB: aislamiento por partition key

DynamoDB es ideal para multi-tenancy porque el partition key define el ámbito de acceso. Si cada tenant tiene su propio prefijo de partition key, las consultas están aisladas por diseño.

Ventajas para multi-tenancy:

  • No hay coste fijo por tenant (pago por uso).
  • El escalado es automático.
  • El aislamiento por partition key es natural.

Limitación: Una sola partición de DynamoDB soporta 3.000 RCU y 1.000 WCU. Si un tenant concentra todas sus operaciones en una sola partition key (hot partition), puede afectar al rendimiento. La solución es usar partition keys más granulares.

Aurora: aislamiento por esquema

Amazon Aurora permite crear un esquema (schema) por tenant dentro de la misma instancia de base de datos.

-- Crear esquema por tenant
CREATE SCHEMA tenant_acme;
CREATE SCHEMA tenant_globex;

-- Crear tablas en cada esquema
CREATE TABLE tenant_acme.orders (
  id SERIAL PRIMARY KEY,
  product_name VARCHAR(255),
  total DECIMAL(10,2),
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE tenant_globex.orders (
  id SERIAL PRIMARY KEY,
  product_name VARCHAR(255),
  total DECIMAL(10,2),
  created_at TIMESTAMP DEFAULT NOW()
);

Ventajas: Aislamiento real a nivel de base de datos. Puedes hacer backup de un esquema individual. Las migraciones de esquema se aplican por tenant.

Limitaciones: Aurora PostgreSQL soporta miles de esquemas, pero cada esquema consume metadata. Con más de 5.000 esquemas, las operaciones de catálogo se ralentizan. Para volúmenes mayores, considera base de datos por tenant o modelo pool con RLS.

S3: aislamiento por prefijo y bucket policy

Para archivos y objetos, S3 permite aislamiento por prefijo o por bucket:

# Aislamiento por prefijo (modelo pool)
s3://mi-saas-data/tenants/acme/uploads/
s3://mi-saas-data/tenants/globex/uploads/

# Aislamiento por bucket (modelo silo)
s3://mi-saas-acme/uploads/
s3://mi-saas-globex/uploads/

El aislamiento por prefijo es más económico (un solo bucket), pero requiere bucket policies o IAM policies cuidadosamente configuradas para evitar que un tenant acceda a los archivos de otro.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::mi-saas-data/tenants/${aws:PrincipalTag/TenantId}/*"
    }
  ]
}

Onboarding automatizado de tenants

Cada nuevo tenant debe tener su infraestructura provisionada automáticamente. Un flujo típico:

Registro → Cognito (crear usuario) → Step Functions → Provisionar recursos
                                          |
                                    ├── Crear esquema en Aurora
                                    ├── Crear prefijo en S3
                                    ├── Crear entrada en tabla de configuración
                                    ├── Configurar DNS (si subdominio)
                                    └── Enviar email de bienvenida

Step Functions para orquestación:

{
  "StartAt": "CreateDatabaseSchema",
  "States": {
    "CreateDatabaseSchema": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789:function:create-tenant-schema",
      "Next": "CreateS3Prefix"
    },
    "CreateS3Prefix": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789:function:create-tenant-s3",
      "Next": "ConfigureTenantSettings"
    },
    "ConfigureTenantSettings": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789:function:configure-tenant",
      "Next": "SendWelcomeEmail"
    },
    "SendWelcomeEmail": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:123456789:function:send-welcome",
      "End": true
    }
  }
}

La automatización del onboarding no es opcional. Si provisionar un nuevo tenant requiere pasos manuales, no escalarás más allá de decenas de clientes sin un equipo de operaciones dedicado.

Costes reales por modelo

ConceptoPool (100 tenants)Bridge (100 tenants)Silo (100 tenants)
Compute (ECS Fargate)200-500 $/mes200-500 $/mes5.000-15.000 $/mes
Base de datos (RDS/Aurora)50-200 $/mes1.200-5.000 $/mes5.000-20.000 $/mes
S310-50 $/mes10-50 $/mes10-50 $/mes
Cognito0 $/mes (50K MAUs free)0-275 $/mes0-275 $/mes
Networking50-100 $/mes50-100 $/mes500-2.000 $/mes
Total310-850 $/mes1.460-5.650 $/mes10.510-37.325 $/mes
Coste por tenant3,10-8,50 $/mes14,60-56,50 $/mes105-373 $/mes

Estos números explican por qué la mayoría de productos SaaS empiezan con modelo pool y migran a bridge o silo solo para clientes enterprise que pagan lo suficiente para justificarlo.

Throttling y fair usage por tenant

En un modelo pool, necesitas proteger a los tenants entre sí. Sin throttling, un tenant puede consumir todos los recursos y degradar el servicio para los demás.

API Gateway usage plans: Configura límites de peticiones por API key o por tenant.

Application-level throttling: Implementa rate limiting en tu aplicación usando Redis o DynamoDB como contador.

import boto3
from datetime import datetime

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('tenant-rate-limits')

def check_rate_limit(tenant_id, limit_per_minute=100):
    current_minute = datetime.utcnow().strftime('%Y-%m-%dT%H:%M')
    key = f"{tenant_id}#{current_minute}"

    response = table.update_item(
        Key={'pk': key},
        UpdateExpression='ADD request_count :inc',
        ExpressionAttributeValues={':inc': 1, ':limit': limit_per_minute},
        ConditionExpression='attribute_not_exists(request_count) OR request_count < :limit',
        ReturnValues='UPDATED_NEW'
    )
    return True  # Dentro del límite

Errores comunes en multi-tenancy

Error 1: no incluir tenant_id en todas las capas

Si el tenant_id solo está en la base de datos pero no en la caché, un tenant puede ver datos cacheados de otro. Si no está en las colas de mensajes, un worker puede procesar mensajes de un tenant con el contexto de otro.

Regla: El tenant_id debe propagarse a través de toda la cadena de llamadas, incluyendo cache keys, mensajes en cola, logs y métricas.

Error 2: empezar con modelo silo sin necesidad

El modelo silo es atractivo porque es conceptualmente simple: cada tenant tiene todo separado. Pero el coste operativo crece linealmente con el número de tenants. Hemos visto productos SaaS que gastan el 80% de su tiempo de ingeniería gestionando infraestructura en lugar de desarrollando producto.

Error 3: no medir consumo por tenant

Si no mides el consumo de recursos por tenant, no puedes identificar noisy neighbors, no puedes facturar por uso, y no puedes tomar decisiones informadas sobre cuándo mover un tenant a infraestructura dedicada.

Conclusión

La multi-tenancy no es una decisión binaria. Es un espectro entre aislamiento total y recursos completamente compartidos. La posición correcta en ese espectro depende de tu modelo de negocio, el tamaño de tus clientes y sus requisitos de seguridad y compliance.

Para la mayoría de productos SaaS en AWS, el camino práctico es empezar con modelo pool, implementar Row-Level Security en la base de datos y throttling por tenant desde el principio, y ofrecer modelo silo como opción premium para clientes enterprise.

Si estás diseñando una arquitectura multi-tenant y necesitas ayuda para tomar las decisiones correctas desde el inicio, nuestro equipo de consultoría AWS ha implementado estos patrones en productos SaaS reales. También puedes solicitar una auditoría gratuita para evaluar tu arquitectura actual.

Back to Blog

Related Posts

View All Posts »