La escalabilidad es la capacidad de un sistema para gestionar un aumento de la carga (más usuarios, más peticiones, más datos) sin degradar su rendimiento de forma inaceptable. Es uno de los atributos de calidad más importantes en arquitectura de aplicaciones: un sistema que funciona perfectamente con 100 usuarios puede colapsar con 100.000 si no se ha diseñado para crecer. En esta lección aprenderás las dos estrategias fundamentales de escalado, cómo gestionar el estado para poder escalar, qué son los balanceadores de carga y sus algoritmos, cómo dividir los datos mediante sharding y, finalmente, las leyes matemáticas (Amdahl y la Ley Universal de Escalabilidad) que ponen límites reales a cuánto podemos escalar.

Contenido

  1. Escalado vertical (scale up) vs escalado horizontal (scale out)
  2. El problema del estado y las sesiones
  3. Balanceadores de carga y algoritmos de reparto
  4. Sharding: partición de datos
  5. Los límites teóricos: Ley de Amdahl y Ley Universal de Escalabilidad
  6. Errores comunes y consejos
  7. Ejercicios

  1. Escalado vertical (scale up) vs escalado horizontal (scale out)

Existen dos formas básicas de dar más capacidad a un sistema:

  • Escalado vertical (scale up): añadir más recursos a una única máquina (más CPU, más RAM, discos más rápidos). Es como cambiar tu coche por uno más potente.
  • Escalado horizontal (scale out): añadir más máquinas que trabajan en paralelo. Es como tener una flota de coches en lugar de uno solo.

La siguiente tabla compara ambas estrategias en los aspectos que más importan al arquitecto:

Aspecto Vertical (scale up) Horizontal (scale out)
Cómo se crece Máquina más grande Más máquinas
Límite máximo Físico (hardware más potente disponible) Prácticamente ilimitado
Coste Crece exponencialmente al final Crece de forma lineal
Punto único de fallo Sí (una sola máquina) No (redundancia natural)
Complejidad Baja (sin cambios en el código) Alta (estado distribuido, red)
Tiempo de inactividad al escalar Suele requerir reinicio Cero (añadir nodos en caliente)
Ideal para Bases de datos relacionales, cargas pequeñas/medias Servicios web sin estado, microservicios

En la práctica, los sistemas modernos combinan ambos: se escala verticalmente hasta un punto razonable de coste/beneficio y, a partir de ahí, se escala horizontalmente.

# Ejemplo de escalado horizontal en Kubernetes:
# pasamos de 3 a 10 réplicas de un servicio con un solo comando
kubectl scale deployment mi-servicio --replicas=10

Este comando le pide a Kubernetes que mantenga 10 copias (réplicas) idénticas del contenedor mi-servicio en ejecución. El orquestador se encarga de arrancar 7 instancias nuevas y repartirlas entre los nodos disponibles. No hace falta tocar el código: es escalado horizontal puro.

  1. El problema del estado y las sesiones

Para poder escalar horizontalmente, los nodos deben ser intercambiables: cualquier petición debe poder atenderse en cualquier instancia. El gran enemigo de esto es el estado guardado en memoria local de un nodo.

  • Servicio con estado (stateful): guarda datos del usuario en su propia memoria (por ejemplo, la sesión HTTP). Si la siguiente petición va a otro nodo, ese nodo no sabe nada del usuario.
  • Servicio sin estado (stateless): no guarda nada propio entre peticiones; toda la información necesaria viaja en la petición o vive en un almacén compartido (base de datos, caché distribuida).

La regla de oro es: diseña servicios sin estado y externaliza el estado.

// MAL: sesión en memoria local del nodo (stateful)
@RestController
public class CarritoController {
    // Este Map vive en la memoria de ESTE nodo concreto.
    // Si el balanceador manda la siguiente petición a otro nodo, el carrito "desaparece".
    private final Map<String, Carrito> carritos = new ConcurrentHashMap<>();
}

El problema del ejemplo anterior es que el carrito de la compra solo existe en el nodo que lo creó. Veamos la versión correcta, que externaliza el estado a un almacén compartido (Redis):

// BIEN: estado externalizado a Redis (stateless desde el punto de vista del nodo)
@RestController
public class CarritoController {

    private final RedisTemplate<String, Carrito> redis;

    public CarritoController(RedisTemplate<String, Carrito> redis) {
        this.redis = redis;
    }

    @GetMapping("/carrito/{id}")
    public Carrito ver(@PathVariable String id) {
        // El carrito se lee de Redis, accesible desde CUALQUIER nodo.
        return redis.opsForValue().get("carrito:" + id);
    }
}

Aquí cualquier nodo puede atender la petición porque el carrito vive en Redis, fuera de la memoria del proceso. Esto permite añadir o quitar nodos sin perder datos de usuario.

Cuando no se puede evitar el estado en el nodo, una solución intermedia es la afinidad de sesión (sticky sessions): el balanceador envía siempre al mismo usuario al mismo nodo. Funciona, pero rompe la intercambiabilidad y complica el escalado y la tolerancia a fallos (si cae el nodo, se pierde la sesión).

  1. Balanceadores de carga y algoritmos de reparto

Un balanceador de carga (load balancer) es el componente que recibe todas las peticiones entrantes y las reparte entre los nodos disponibles. Es la pieza que hace posible el escalado horizontal de cara al cliente: este ve una única dirección, pero detrás hay muchas máquinas.

Los algoritmos de reparto más habituales son:

Algoritmo Cómo reparte Cuándo usarlo
Round Robin Por turnos, uno tras otro Nodos homogéneos y peticiones similares
Weighted Round Robin Por turnos, pero con pesos Nodos de distinta potencia
Least Connections Al nodo con menos conexiones activas Peticiones de duración variable
IP Hash Según hash de la IP de origen Para mantener afinidad de sesión
Least Response Time Al nodo que responde más rápido Optimizar latencia

Veamos una configuración real de balanceo con Nginx:

# Definimos un grupo de servidores backend
upstream backend {
    least_conn;                      # Algoritmo: menos conexiones activas
    server 10.0.0.1:8080 weight=2;   # Nodo más potente: peso 2
    server 10.0.0.2:8080;            # Peso 1 por defecto
    server 10.0.0.3:8080;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;   # Reenvía las peticiones al grupo "backend"
    }
}

Línea a línea: upstream backend declara el conjunto de nodos. least_conn indica que Nginx mandará cada petición al nodo con menos conexiones abiertas en ese momento. El weight=2 del primer servidor hace que reciba aproximadamente el doble de tráfico porque es más potente. Finalmente, proxy_pass http://backend conecta el tráfico entrante con ese grupo.

Conviene distinguir el nivel de red en el que opera el balanceador:

  • Capa 4 (L4, transporte): reparte por IP y puerto, sin mirar el contenido. Muy rápido.
  • Capa 7 (L7, aplicación): entiende HTTP, puede enrutar por URL, cabeceras o cookies. Más inteligente, algo más lento.

  1. Sharding: partición de datos

Escalar los servidores de aplicación es relativamente fácil porque son sin estado. La base de datos es mucho más difícil porque tiene estado por definición. Cuando una única base de datos no puede con todo el volumen, se recurre al sharding: dividir los datos en fragmentos (shards), cada uno en un servidor distinto.

La clave es elegir una clave de partición (shard key) que reparta los datos de forma equilibrada:

Usuarios particionados por hash(user_id) % 3:

  hash(user_id) % 3 == 0  ->  Shard 0  (servidor A)
  hash(user_id) % 3 == 1  ->  Shard 1  (servidor B)
  hash(user_id) % 3 == 2  ->  Shard 2  (servidor C)

Cada usuario vive en un único shard, determinado por su user_id. Así, las consultas de un usuario solo golpean un servidor.

Estrategias habituales de sharding:

Estrategia Descripción Riesgo
Por rango Por intervalos de la clave (A-M, N-Z) Puntos calientes si la clave no es uniforme
Por hash Hash de la clave módulo N Re-particionar al cambiar N es costoso
Por directorio Una tabla de búsqueda decide el shard Punto único de fallo en el directorio
Geográfica Por región del usuario Cumple normativa de datos (p. ej. GDPR)

El gran inconveniente del sharding son las consultas que cruzan varios shards (por ejemplo, "dame todos los pedidos del último mes de todos los usuarios"): requieren consultar todos los servidores y combinar resultados, lo que es lento y complejo. Por eso el sharding solo se aplica cuando es estrictamente necesario.

graph TD
    C[Cliente] --> R[Router de Sharding]
    R -->|user_id par| S0[(Shard 0)]
    R -->|user_id impar| S1[(Shard 1)]

Este diagrama muestra cómo un router intermedio decide, a partir de la clave del usuario, a qué shard dirigir cada operación. El cliente no sabe que los datos están repartidos.

  1. Los límites teóricos: Ley de Amdahl y Ley Universal de Escalabilidad

No se puede escalar infinitamente. Hay leyes matemáticas que lo demuestran.

Ley de Amdahl

La Ley de Amdahl dice que la aceleración (speedup) de un sistema al añadir procesadores está limitada por la fracción del trabajo que no se puede paralelizar (la parte secuencial).

Speedup(N) = 1 / ( (1 - P) + P/N )

  P = fracción del trabajo que SÍ se puede paralelizar
  N = número de procesadores/nodos

Si el 10% del trabajo es secuencial (P = 0,9), aunque tengas infinitos nodos el speedup máximo es 1 / (1 - 0,9) = 10x. Es decir: la parte secuencial pone un techo absoluto. Por eso reducir esa fracción secuencial (bloqueos, secciones críticas, transacciones globales) es tan valioso.

Ley Universal de Escalabilidad (USL)

La Ley de Amdahl es optimista porque ignora el coste de coordinar los nodos entre sí. La Ley Universal de Escalabilidad (USL) de Neil Gunther añade ese coste y predice algo más realista y a veces sorprendente: a partir de cierto punto, añadir más nodos empeora el rendimiento.

C(N) = N / ( 1 + α(N-1) + βN(N-1) )

  α (alfa) = coste de contención (esperar recursos compartidos)
  β (beta) = coste de coherencia (sincronizar estado entre nodos)

El término βN(N-1) crece de forma cuadrática: cada nodo nuevo tiene que coordinarse con todos los demás. Cuando ese coste de coherencia (β) domina, la curva de rendimiento sube, alcanza un máximo y luego baja. La lección práctica es clara: minimiza la comunicación y la sincronización entre nodos si quieres escalar de verdad.

Errores Comunes y Consejos

  • Guardar estado en memoria local del nodo. Es el error número uno. Externaliza siempre las sesiones a Redis o similar antes de escalar horizontalmente.
  • Abusar de las sticky sessions. Parchea el síntoma pero no la causa; dificulta el reparto equilibrado y la tolerancia a fallos.
  • Escalar la aplicación pero olvidar la base de datos. De nada sirven 50 nodos web si todos golpean una única base de datos saturada. Identifica el verdadero cuello de botella.
  • Aplicar sharding demasiado pronto. El sharding añade enorme complejidad. Antes prueba réplicas de lectura, cachés e índices.
  • Ignorar la USL. Más nodos no siempre es más rendimiento. Mide siempre; no asumas escalado lineal.
  • Consejo: mide antes de escalar. El cuello de botella suele estar donde no lo esperas.

Ejercicios

  1. Escalado adecuado. Tienes una API REST sin estado que recibe picos de tráfico impredecibles y un coste de infraestructura ajustado. ¿Escalarías vertical u horizontalmente, y por qué?

  2. Diagnóstico de sesión. Un usuario informa de que, de forma aleatoria, su carrito de la compra "se vacía" tras añadir productos. La aplicación corre en 4 nodos detrás de un balanceador Round Robin. ¿Cuál es la causa más probable y cómo lo solucionarías?

  3. Cálculo con Amdahl. Un proceso tiene un 25% de trabajo secuencial (P = 0,75). ¿Cuál es el speedup máximo teórico aunque dispongas de infinitos nodos?

Soluciones

  1. Horizontal. Al ser sin estado, escala horizontalmente sin esfuerzo. El escalado horizontal con autoescalado permite añadir nodos solo durante los picos y quitarlos después, optimizando el coste frente a una máquina enorme siempre encendida. Además evita el punto único de fallo.

  2. La causa es estado de sesión guardado en la memoria local de cada nodo. Con Round Robin, peticiones consecutivas del mismo usuario van a nodos distintos que no conocen su carrito. Solución correcta: externalizar la sesión a un almacén compartido (Redis). Solución parche temporal: activar sticky sessions (IP Hash) para forzar que el usuario vuelva siempre al mismo nodo.

  3. Aplicando Amdahl con N tendiendo a infinito: Speedup = 1 / (1 - P) = 1 / (1 - 0,75) = 1 / 0,25 = 4x. Aunque tengas infinitos nodos, nunca irás más de 4 veces más rápido.

Conclusión

Has aprendido que escalar es elegir entre crecer hacia arriba (vertical) o hacia los lados (horizontal), que el escalado horizontal exige eliminar el estado de los nodos, que el balanceador y sus algoritmos reparten la carga, que el sharding parte los datos cuando la base de datos no puede más, y que las leyes de Amdahl y la USL ponen límites reales a cuánto podemos crecer. Un sistema escalable no se improvisa: se diseña sin estado, se mide constantemente y se minimiza la coordinación.

Pero escalar no basta: el sistema también debe seguir funcionando cuando algo falla. En la siguiente lección, Alta Disponibilidad y Tolerancia a Fallos, veremos cómo medir y garantizar que el servicio esté disponible aunque caigan componentes individuales.

Curso de Arquitectura de Aplicaciones

Módulo 1: Fundamentos de la Arquitectura de Aplicaciones

Módulo 2: Principios y Tácticas de Diseño

Módulo 3: Estilos y Patrones Arquitectónicos

Módulo 4: Arquitecturas Distribuidas y Microservicios

Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería

Módulo 6: Diseño Dirigido por el Dominio (DDD)

Módulo 7: Datos y Persistencia

Módulo 8: Arquitectura en la Nube y Despliegue

Módulo 9: Calidad, Seguridad y Observabilidad

Módulo 10: Evolución, Gobernanza y Casos Prácticos

© Copyright 2026. Todos los derechos reservados