En las dos lecciones anteriores hemos visto qué es un sistema distribuido y qué modelos nos permiten describirlo. Ahora toca responder a la pregunta que cualquier arquitecto de Kilómetro Cero se haría tras la caída de la Semana de la Vendimia: ¿merece la pena distribuir? La respuesta honesta es "depende", y esta lección da las herramientas para que ese "depende" sea un cálculo y no una corazonada.

Vamos a cuantificar las ventajas (cuánta capacidad ganamos al añadir máquinas, cuántos minutos de caída al año supone cada "nueve" de disponibilidad, cuánto acelera realmente el paralelismo) y a poner nombre a los desafíos (complejidad, heterogeneidad, consistencia, fallos parciales, latencia, seguridad, observabilidad y coste). Terminaremos con una tabla de situaciones en las que no conviene distribuir, porque saber cuándo no hacerlo es tan importante como saber cómo hacerlo.

Contenido

  1. Ventajas de distribuir
  2. Escalabilidad: vertical frente a horizontal
  3. Tolerancia a fallos y disponibilidad: los "nueves"
  4. Flexibilidad, localidad y compartición de recursos
  5. La ley de Amdahl: el límite del escalado
  6. Desafíos de distribuir
  7. Cuándo NO distribuir
  8. Ejemplo de código: disponibilidad combinada en Kilómetro Cero
  9. Errores comunes y consejos
  10. Ejercicios
  11. Conclusión

  1. Ventajas de distribuir

Las ventajas de un sistema distribuido son la otra cara de los motivos por los que existen (lección 01-01). Las agrupamos en cinco, y las tres primeras las cuantificaremos:

Ventaja Qué aporta Cómo se mide
Escalabilidad Atender más carga añadiendo máquinas Peticiones por segundo, usuarios concurrentes
Tolerancia a fallos y disponibilidad Seguir funcionando aunque fallen componentes Porcentaje de tiempo disponible ("nueves")
Rendimiento por localidad Servir a cada usuario desde cerca Latencia (milisegundos)
Flexibilidad y modularidad Evolucionar cada parte por separado Frecuencia de despliegue, tamaño de los equipos
Compartición de recursos Aprovechar hardware, datos y servicios comunes Coste por unidad de trabajo

  1. Escalabilidad: vertical frente a horizontal

Escalar es aumentar la capacidad de un sistema para atender más carga. Hay dos caminos:

  • Escalado vertical (scale up): sustituir la máquina por una más potente (más CPU, más memoria, discos más rápidos).
  • Escalado horizontal (scale out): añadir más máquinas y repartir la carga entre ellas.
Criterio Vertical Horizontal
Cambios en el software Ninguno (o casi) Necesita que la aplicación pueda repartirse (sin estado local, balanceo)
Límite El de la máquina más grande que exista (o que puedas pagar) En teoría, ninguno; en la práctica, la ley de Amdahl (apartado 5)
Coste Crece más que linealmente: una máquina el doble de potente cuesta bastante más del doble Aproximadamente lineal: dos máquinas cuestan el doble que una
Disponibilidad No mejora: sigue habiendo un único punto de fallo Mejora: la caída de una máquina no tumba el sistema
Elasticidad Cambiar de máquina implica parada y planificación Se pueden añadir y quitar máquinas según la demanda (en la nube, en minutos)
Tiempo de aplicación Días o semanas (comprar, migrar) Minutos (si el sistema ya está preparado)

Cálculo de capacidad para la Semana de la Vendimia

Recordemos los datos del incidente (lección 01-01): el servidor único empezó a degradarse a unos 380 peticiones/s y cayó por encima de 700 peticiones/s. Marketing prevé que la próxima campaña, la "Semana del Queso Artesano", con Quesería Montblanc como protagonista, alcance un pico de 1.200 peticiones/s.

Hagamos las cuentas para las dos opciones:

Opción vertical. Necesitamos una máquina que aguante 1.200 pet/s con un margen de seguridad del 30 %: 1.560 pet/s, es decir, unas 4 veces la capacidad actual (380 pet/s "cómodas"). Un servidor cuatro veces más potente que el actual (32 vCPU, 128 GB) no cuesta 4 veces más, sino típicamente 5-6 veces más, y seguirá siendo un único punto de fallo. Además, no está claro que la aplicación aproveche linealmente los núcleos adicionales (apartado 5).

Opción horizontal. Medimos que una instancia de la aplicación Python, en una máquina modesta (4 vCPU, 16 GB), atiende cómodamente 200 pet/s. Entonces:

capacidad necesaria  = 1.200 pet/s × 1,3 (margen)   = 1.560 pet/s
instancias necesarias = ceil(1.560 / 200)           = 8 instancias
instancias con redundancia (N+1)                    = 9 instancias

Nueve máquinas pequeñas durante la semana de campaña y, el resto del año, dos o tres. Esta elasticidad es imposible con el escalado vertical.

Pero atención: el cálculo asume que la capa de aplicación es el cuello de botella. En el incidente real, PostgreSQL agotó sus conexiones a 610 pet/s. Si ponemos 9 instancias de aplicación delante de la misma base de datos, simplemente la ahogaremos antes. El escalado horizontal de la aplicación obliga a escalar también los datos (caché, réplicas de lectura, particionado), que es materia del Módulo 4. La lección: el escalado horizontal no es una propiedad de "el sistema", sino de cada componente, y el sistema escala tanto como su componente menos escalable.

  1. Tolerancia a fallos y disponibilidad: los "nueves"

La disponibilidad de un sistema es la fracción del tiempo en que funciona correctamente. Se expresa como porcentaje y, coloquialmente, por su número de nueves. Es fundamental traducir cada nivel a tiempo de caída para entender qué significa realmente:

Disponibilidad Nueves Caída máxima al año Caída máxima al mes Caída máxima a la semana
99 % dos 3 días 15 h 36 min (87,6 h) 7 h 18 min 1 h 41 min
99,9 % tres 8 h 46 min 43 min 50 s 10 min 5 s
99,99 % cuatro 52 min 34 s 4 min 23 s 1 min
99,999 % cinco 5 min 15 s 26 s 6 s

El cálculo es directo: un año tiene 365 × 24 × 60 = 525.600 minutos; el 0,1 % de esa cifra son 525,6 minutos (8 h 46 min); el 0,01 %, 52,56 minutos.

Dos observaciones importantes:

  1. Cada nueve adicional multiplica el coste. Pasar de 99,9 % a 99,99 % no es "un poco mejor": significa que en todo el año solo puedes permitirte 52 minutos de caída, incluyendo despliegues, actualizaciones del sistema operativo, fallos de hardware y errores humanos. Exige redundancia en todos los niveles, despliegues sin parada y guardias 24×7.
  2. La disponibilidad se combina. Si una petición de Ana para comprar un queso atraviesa cuatro componentes en serie (web, pedidos, inventario, pagos), y cada uno tiene un 99,9 %, la disponibilidad de la compra no es 99,9 %, sino aproximadamente 99,6 %: las indisponibilidades se suman. Por el contrario, si un componente tiene dos réplicas independientes al 99,9 % y basta con que una funcione, la disponibilidad combinada sube al 99,9999 %. El ejemplo de código del apartado 8 hace estos cálculos.

Con el monolito, Kilómetro Cero tenía una disponibilidad real del 99,3 % el último año (unas 61 horas de caída, entre la Semana de la Vendimia, actualizaciones y un fallo de disco). El objetivo tras la reestructuración es alcanzar el 99,9 % en la compra y el 99,95 % en la consulta del catálogo.

  1. Flexibilidad, localidad y compartición de recursos

Flexibilidad y modularidad

Cuando cada servicio es una unidad de despliegue independiente, los equipos pueden trabajar sin pisarse. En Kilómetro Cero, el equipo de reparto necesita desplegar varias veces al día para ajustar el algoritmo de rutas, mientras que el de pagos, sujeto a auditorías, prefiere despliegues quincenales muy controlados. En el monolito, ambos ritmos son incompatibles: cada despliegue es de todo. Distribuir permite que cada servicio tenga su ritmo, su tecnología (Python para casi todo, quizá otro lenguaje para el procesamiento de posiciones en tiempo real) y su equipo responsable.

Rendimiento por localidad

La velocidad de la luz en fibra óptica es de unos 200.000 km/s. Un viaje de ida y vuelta entre Barcelona y un centro de datos en Fráncfort (unos 1.100 km) tarda, como mínimo, 11 ms; con los saltos de enrutamiento reales, 25-35 ms. Entre Barcelona y Virginia (EE. UU.), unos 90-110 ms. Si cargar una página del catálogo requiere 10 peticiones secuenciales, la diferencia entre servir desde cerca y desde lejos es de más de un segundo. Por eso los sistemas grandes replican datos y servicios en varias regiones, y usan CDN para el contenido estático (las fotos de los productos, en el caso de Kilómetro Cero). Servir a cada ciudad desde un nodo cercano es una ventaja que un sistema centralizado no puede ofrecer.

Compartición de recursos

Un sistema distribuido permite que muchos usuarios y servicios compartan recursos costosos: una base de datos, un clúster de procesamiento analítico, un almacén de fotos. El servicio analitica de Kilómetro Cero necesitará, unas horas al día, una capacidad de cálculo enorme para procesar las ventas; el resto del día, esa capacidad puede dedicarse a otras cosas. Compartir es más eficiente que dedicar.

  1. La ley de Amdahl: el límite del escalado

Es tentador pensar que con el doble de máquinas se obtiene el doble de rendimiento. Casi nunca es así, y la razón la formuló Gene Amdahl en 1967: toda tarea tiene una parte que no se puede paralelizar, y esa parte limita la aceleración máxima.

Si una fracción p del trabajo es paralelizable (y por tanto 1 − p es secuencial), la aceleración S al usar n máquinas es:

S(n) = 1 / ((1 − p) + p / n)

Y cuando n tiende a infinito:

S(∞) = 1 / (1 − p)

Apliquémoslo a Kilómetro Cero. Supongamos que, al procesar un pedido, el 90 % del trabajo (validar, calcular precios, generar la confirmación, renderizar) es paralelizable entre instancias, pero el 10 % (la escritura en la única base de datos PostgreSQL, que serializa las transacciones sobre el stock) es inherentemente secuencial. Entonces:

Máquinas (n) Aceleración S(n) con p = 0,9 Aceleración "ingenua" (n) Eficiencia S(n)/n
1 1,00 1 100 %
2 1,82 2 91 %
4 3,08 4 77 %
8 4,71 8 59 %
16 6,40 16 40 %
64 8,77 64 14 %
∞ 10,00 ∞ 0 %

Con 8 máquinas conseguimos menos de 5 veces la capacidad, y nunca superaremos 10 veces por muchas máquinas que añadamos, mientras ese 10 % siga siendo secuencial. Esto conecta con el apartado 2: las 9 instancias de aplicación no darán 9 × 200 = 1.800 pet/s si la base de datos es el componente secuencial. La única forma de romper el techo es reducir la fracción secuencial: cachear las lecturas del catálogo (04-05), particionar los datos (04-01), o convertir en asíncronas las operaciones que no necesitan ser inmediatas (02-04). Buena parte de la arquitectura distribuida consiste, en el fondo, en atacar el (1 − p) de Amdahl.

  1. Desafíos de distribuir

Cada ventaja tiene su factura. Estos son los ocho desafíos que aparecerán, uno por uno, en el resto del curso:

Desafío En qué consiste Cómo se manifiesta en Kilómetro Cero Dónde se trata
Complejidad Más piezas, más interacciones, más formas de fallar. El razonamiento pasa de "una traza de pila" a "un grafo de llamadas entre procesos" Un pedido que antes era una función ahora atraviesa 5 servicios y 2 colas Todo el curso; especialmente 08-01
Heterogeneidad Distintos lenguajes, sistemas operativos, versiones, formatos de datos, redes reparto recibe posiciones de móviles Android e iOS por 4G y wifi; analitica usa herramientas de otro ecosistema 02-03 (serialización), 07-05 (orquestación)
Consistencia de datos Con varias copias, ¿cuál es la verdad? ¿Qué pasa si dos nodos escriben a la vez? Ana y Marc compran la última unidad de queso desde dos ciudades Módulo 3 completo
Fallos parciales Algunas partes fallan mientras otras siguen; no siempre se sabe qué ha pasado pagos cobra a Lucía pero la confirmación a pedidos se pierde 01-04, 02-05, 07-03, 07-04
Latencia Cada llamada por red cuesta milisegundos; encadenar muchas llamadas suma segundos Cargar el carrito hace 12 llamadas a servicios distintos: 12 × 20 ms = 240 ms 01-04, 04-05
Seguridad Cada comunicación por red puede ser interceptada o suplantada; más superficie de ataque ¿Cómo sabe inventario que quien le pide descontar stock es realmente pedidos? Módulo 6 completo
Depuración y observabilidad El registro de logs está repartido; reproducir un fallo exige correlacionar eventos de muchas máquinas con relojes distintos "El pedido de Marc se quedó en 'pendiente' y nadie sabe en qué servicio se atascó" Módulo 7 (07-01, 07-02)
Coste operativo Más máquinas, más despliegues, más monitorización, más personas de guardia El equipo pasa de administrar un servidor a administrar un clúster con decenas de contenedores 07-05, 08-03

Merece la pena detenerse en el de consistencia, porque es el más contraintuitivo. En el monolito, la transacción de PostgreSQL garantiza que si Ana compra la última unidad, Marc verá stock cero. Con pedidos e inventario separados, y quizá con inventario replicado en varias ciudades, esa garantía desaparece por defecto y hay que reconstruirla con técnicas específicas (consenso, transacciones distribuidas, sagas) que tienen su coste en latencia y complejidad. El Módulo 3 se dedica por entero a este problema; aquí basta con saber que existe y que no tiene solución gratuita.

  1. Cuándo NO distribuir

Un buen arquitecto sabe decir "no". Estas son situaciones en las que distribuir es, casi con seguridad, un error:

Situación Por qué no distribuir Alternativa
La carga cabe holgadamente en una máquina y no se prevé que crezca Se asume toda la complejidad sin obtener la ventaja principal Monolito bien estructurado; escalado vertical si hace falta
El equipo es pequeño (menos de ~8 personas) Los microservicios existen en buena parte para que equipos distintos no se pisen; un equipo pequeño no tiene ese problema y sí paga la complejidad Monolito modular con límites claros entre módulos
Los datos exigen transacciones fuertes entre casi todas las entidades Las transacciones distribuidas son lentas y complejas; si todo está relacionado con todo, no hay límites naturales para partir Una única base de datos relacional, posiblemente con réplicas de lectura
No se dispone de capacidad de operación (monitorización, despliegue automático, guardias) Un sistema distribuido sin observabilidad es una caja negra que fallará sin que nadie sepa por qué Invertir primero en operación (Módulo 7) y distribuir después
El requisito de disponibilidad es modesto (99 % basta) Un monolito con copias de seguridad y un procedimiento de restauración ya lo consigue Alta disponibilidad simple: máquina de reserva y restauración
No se ha identificado el cuello de botella Distribuir "a ciegas" suele mover el problema en lugar de resolverlo (como las 9 instancias contra una única base de datos) Medir primero, distribuir el componente que realmente limita

Kilómetro Cero, tras la Semana de la Vendimia, cumple los criterios para distribuir: la carga ya no cabe en una máquina, hay varios equipos, los dominios (catálogo, pedidos, reparto...) tienen límites naturales y la disponibilidad del 99,3 % es inaceptable para el negocio. Pero conviene observar que no todos los componentes tienen que distribuirse de la misma forma ni al mismo tiempo: la lección 01-06 propone un camino gradual.

  1. Ejemplo de código: disponibilidad combinada en Kilómetro Cero

Vamos a escribir un pequeño simulador que calcula la disponibilidad de un flujo de negocio a partir de la disponibilidad de los componentes que atraviesa. Las dos reglas son:

  • Componentes en serie (la operación necesita que todos funcionen): la disponibilidad combinada es el producto de las disponibilidades. Si A tiene 0,999 y B tiene 0,999, A y B juntos tienen 0,999 × 0,999 = 0,998.
  • Componentes en paralelo (basta con que uno funcione, por ejemplo, réplicas): la indisponibilidad combinada es el producto de las indisponibilidades. Si A y A' tienen 0,999 cada uno, la probabilidad de que fallen los dos a la vez es 0,001 × 0,001 = 0,000001, así que la disponibilidad es 1 − 0,000001 = 0,999999.

La segunda regla asume que los fallos son independientes, cosa que en la realidad no siempre es cierta (si las dos réplicas comparten alimentación o red, fallan a la vez). Lo comentaremos tras el código.

from dataclasses import dataclass

MINUTOS_ANO = 365 * 24 * 60   # 525.600 minutos


@dataclass
class Componente:
    """Un servicio o recurso con una disponibilidad conocida (0..1)."""
    nombre: str
    disponibilidad: float

    def caida_anual_min(self) -> float:
        return (1 - self.disponibilidad) * MINUTOS_ANO


def en_serie(nombre: str, *componentes: Componente) -> Componente:
    """Todos deben funcionar: se multiplican las disponibilidades."""
    total = 1.0
    for c in componentes:
        total *= c.disponibilidad
    return Componente(nombre, total)


def en_paralelo(nombre: str, *replicas: Componente) -> Componente:
    """Basta con que una funcione: se multiplican las INdisponibilidades."""
    prob_todas_caidas = 1.0
    for r in replicas:
        prob_todas_caidas *= (1 - r.disponibilidad)
    return Componente(nombre, 1 - prob_todas_caidas)


def replicar(componente: Componente, n: int) -> Componente:
    """Atajo: n réplicas independientes e idénticas de un componente."""
    replicas = [Componente(f"{componente.nombre}-{i+1}", componente.disponibilidad)
                for i in range(n)]
    return en_paralelo(f"{componente.nombre} x{n}", *replicas)


def informe(c: Componente) -> None:
    minutos = c.caida_anual_min()
    horas, resto = divmod(minutos, 60)
    print(f"{c.nombre:<42} {c.disponibilidad*100:8.4f} %   "
          f"caída/año: {int(horas):3d} h {resto:4.1f} min")


if __name__ == "__main__":
    # Componentes que atraviesa la compra de un producto en Kilómetro Cero
    web = Componente("web", 0.9995)
    pedidos = Componente("pedidos", 0.999)
    inventario = Componente("inventario", 0.999)
    pagos = Componente("pagos", 0.995)     # depende de una pasarela externa
    postgres = Componente("postgresql", 0.9995)

    print("=== Escenario 1: una instancia de cada componente, en serie ===")
    compra_v1 = en_serie("compra (todo en serie)", web, pedidos, inventario, pagos, postgres)
    for c in (web, pedidos, inventario, pagos, postgres):
        informe(c)
    print("-" * 80)
    informe(compra_v1)

    print("\n=== Escenario 2: replicamos los servicios (2 réplicas cada uno) ===")
    compra_v2 = en_serie(
        "compra (servicios x2)",
        replicar(web, 2), replicar(pedidos, 2), replicar(inventario, 2),
        replicar(pagos, 2), postgres,
    )
    informe(compra_v2)

    print("\n=== Escenario 3: además, PostgreSQL con réplica de respaldo ===")
    compra_v3 = en_serie(
        "compra (todo x2)",
        replicar(web, 2), replicar(pedidos, 2), replicar(inventario, 2),
        replicar(pagos, 2), replicar(postgres, 2),
    )
    informe(compra_v3)

    print("\n=== ¿Cuántas réplicas de 'pagos' hacen falta para 99,99 %? ===")
    for n in range(1, 5):
        informe(replicar(pagos, n))

La salida es la siguiente:

=== Escenario 1: una instancia de cada componente, en serie ===
web                                         99.9500 %   caída/año:   4 h 22.8 min
pedidos                                     99.9000 %   caída/año:   8 h 45.6 min
inventario                                  99.9000 %   caída/año:   8 h 45.6 min
pagos                                       99.5000 %   caída/año:  43 h 48.0 min
postgresql                                  99.9500 %   caída/año:   4 h 22.8 min
--------------------------------------------------------------------------------
compra (todo en serie)                      99.2018 %   caída/año:  69 h 55.2 min

=== Escenario 2: replicamos los servicios (2 réplicas cada uno) ===
compra (servicios x2)                       99.9473 %   caída/año:   4 h 37.1 min

=== Escenario 3: además, PostgreSQL con réplica de respaldo ===
compra (todo x2)                            99.9973 %   caída/año:   0 h 14.5 min

=== ¿Cuántas réplicas de 'pagos' hacen falta para 99,99 %? ===
pagos x1                                    99.5000 %   caída/año:  43 h 48.0 min
pagos x2                                    99.9975 %   caída/año:   0 h 13.1 min
pagos x3                                   100.0000 %   caída/año:   0 h  0.1 min
pagos x4                                   100.0000 %   caída/año:   0 h  0.0 min

Analicemos el programa y sus resultados:

  1. Componente guarda un nombre y una disponibilidad entre 0 y 1; caida_anual_min la traduce a minutos de caída al año, que es la magnitud que entiende el negocio.
  2. en_serie multiplica disponibilidades. Observa el escenario 1: ningún componente individual baja del 99,5 %, pero la compra completa queda en el 99,2 %, casi 70 horas al año. Cuantos más componentes atraviese una operación, peor. Este es el coste oculto de "descomponer en servicios": cada frontera de red que se añade a un flujo síncrono resta disponibilidad.
  3. en_paralelo y replicar aplican la regla de la redundancia. El escenario 2 muestra el efecto: replicar los cuatro servicios lleva la compra del 99,2 % al 99,95 %, y ahora el componente que domina es el que no se ha replicado, PostgreSQL (4 h 23 min de las 4 h 37 min totales).
  4. El escenario 3 replica también la base de datos y alcanza el 99,997 %. Pero cuidado: replicar una base de datos no es tan simple como replicar un servicio sin estado. Hay que mantener las copias coherentes, y eso es todo el Módulo 3. El número es una cota superior optimista.
  5. El último bloque responde a una pregunta muy práctica: pagos, que depende de una pasarela externa con solo el 99,5 %, pasa al 99,9975 % con dos réplicas. Eso, sin embargo, solo es cierto si las dos réplicas fallan de forma independiente. Si ambas dependen de la misma pasarela externa y es esta la que cae, las dos caen a la vez y la réplica no sirve de nada. El modelo de este script no captura fallos correlacionados; en la realidad, siempre hay que preguntarse qué comparten las réplicas (red, alimentación, proveedor, versión de software con el mismo error).

Errores Comunes y Consejos

  • Sumar disponibilidades en lugar de multiplicarlas. "Cada servicio tiene 99,9 %, así que el sistema tiene 99,9 %" es falso. Con cinco componentes en serie al 99,9 %, el sistema tiene 99,5 %. Cada salto de red en un flujo síncrono es un factor multiplicador menor que uno.
  • Creer que las réplicas fallan de forma independiente. Dos réplicas en el mismo rack, con la misma versión de software y la misma dependencia externa, tienen fallos muy correlacionados. La redundancia real exige diversidad (zonas distintas, despliegues escalonados).
  • Escalar el componente equivocado. Añadir instancias de aplicación cuando el cuello de botella es la base de datos no sirve de nada (Amdahl). Hay que medir antes de escalar.
  • Confundir "más rápido" con "más escalable". Un sistema distribuido suele ser más lento para una única petición (latencia de red) y más capaz para muchas peticiones (rendimiento total). Si lo que importa es la latencia de una operación aislada, la distribución puede empeorarla.
  • Perseguir nueves sin que el negocio los necesite. Cada nueve cuesta aproximadamente un orden de magnitud más. Pregunta siempre cuánto cuesta cada hora de caída para el negocio y compara con lo que cuesta evitarla.
  • Consejo: documenta la disponibilidad objetivo de cada operación de negocio (no de cada servicio) y calcula, con la regla de la serie, qué disponibilidad exige eso a cada componente. Así se descubren rápidamente los componentes que necesitan redundancia.

Ejercicios

Ejercicio 1: Planificar la capacidad

El equipo de Kilómetro Cero mide que una instancia del servicio catalogo atiende 350 consultas/s con un 60 % de CPU, y que a partir del 80 % de CPU la latencia se dispara. Para la Semana del Queso Artesano se prevén 2.100 consultas/s al catálogo.

  1. ¿Cuántas consultas/s puede atender una instancia sin superar el 80 % de CPU (suponiendo relación lineal entre consultas y CPU)?
  2. ¿Cuántas instancias hacen falta para el pico, con redundancia N+1?
  3. Si la mitad de las consultas pudieran resolverse desde una caché en memoria sin llegar al servicio, ¿cuántas instancias harían falta?

Ejercicio 2: Presupuesto de caída

El negocio decide que la operación "consultar el estado de mi pedido" debe tener un 99,95 % de disponibilidad. La operación atraviesa, en serie: la puerta de enlace (99,99 %), el servicio pedidos (X) y su base de datos (99,98 %).

  1. ¿Cuántos minutos de caída al año permite el objetivo del 99,95 %?
  2. ¿Qué disponibilidad mínima X necesita pedidos para que la operación cumpla el objetivo?
  3. Si una instancia de pedidos solo consigue el 99,9 %, ¿bastan dos réplicas independientes?

Ejercicio 3: Amdahl en el procesamiento de ventas

El servicio analitica procesa las ventas del día en un trabajo que tarda 60 minutos en una máquina. Un análisis muestra que 6 minutos corresponden a leer y consolidar el resultado final en un único fichero (secuencial) y los 54 restantes a cálculos por ciudad (paralelizables).

  1. Calcula la aceleración con 6 máquinas y con 18 máquinas.
  2. ¿Cuál es el tiempo mínimo posible por muchas máquinas que se añadan?
  3. Modifica el script del apartado 8 (o escribe uno nuevo) con una función amdahl(p, n) y úsala para imprimir una tabla de aceleraciones para n = 1, 2, 4, 8, 16, 32.

Soluciones

Solución 1:

  1. Si 350 consultas/s consumen el 60 % de CPU, el 80 % corresponde a 350 × (80 / 60) ≈ 467 consultas/s por instancia.
  2. 2.100 / 467 = 4,5 → se necesitan 5 instancias para el pico, 6 con redundancia N+1.
  3. Si la caché absorbe la mitad, llegan 1.050 consultas/s al servicio: 1.050 / 467 = 2,25 → 3 instancias (4 con N+1). Fíjate en que una caché (lección 04-05) reduce las máquinas a la mitad: reducir el trabajo suele ser más rentable que añadir máquinas.

Solución 2:

  1. 0,05 % de 525.600 minutos = 262,8 minutos (unas 4 h 23 min) al año.
  2. La disponibilidad en serie es 0,9999 × X × 0,9998 ≥ 0,9995. Despejando: X ≥ 0,9995 / (0,9999 × 0,9998) = 0,9995 / 0,99970002 ≈ 0,99980 (99,98 %). Es decir, pedidos necesita casi la misma disponibilidad que el objetivo global, porque los otros dos componentes ya "gastan" parte del presupuesto.
  3. Dos réplicas independientes al 99,9 %: 1 − (0,001)² = 0,999999 (99,9999 %), muy por encima del 99,98 % necesario. Sí bastan, siempre que el fallo de una no arrastre a la otra y que el balanceador que las reparte no se convierta en el nuevo punto único de fallo (habría que incluirlo en la cadena en serie).

Solución 3:

  1. La fracción paralelizable es p = 54 / 60 = 0,9. Con n = 6: S = 1 / (0,1 + 0,9 / 6) = 1 / 0,25 = 4,0 (el trabajo tarda 15 minutos). Con n = 18: S = 1 / (0,1 + 0,9 / 18) = 1 / 0,15 = 6,67 (9 minutos). Triplicar las máquinas solo mejora el tiempo de 15 a 9 minutos.
  2. El tiempo mínimo es la parte secuencial: 6 minutos (aceleración máxima 1 / 0,1 = 10). Para bajar de ahí hay que atacar la consolidación final, por ejemplo, escribiendo resultados parciales por ciudad en paralelo (el patrón que hace MapReduce, lección 05-02).
def amdahl(p: float, n: int) -> float:
    """Aceleración de Amdahl con fracción paralelizable p y n máquinas."""
    return 1 / ((1 - p) + p / n)


if __name__ == "__main__":
    p = 54 / 60
    tiempo_base_min = 60
    print(f"{'n':>4} {'aceleración':>12} {'tiempo (min)':>13} {'eficiencia':>11}")
    for n in (1, 2, 4, 8, 16, 32):
        s = amdahl(p, n)
        print(f"{n:>4} {s:>12.2f} {tiempo_base_min / s:>13.1f} {s / n * 100:>10.0f} %")

Salida:

   n  aceleración  tiempo (min)  eficiencia
   1         1.00          60.0        100 %
   2         1.82          33.0         91 %
   4         3.08          19.5         77 %
   8         4.71          12.7         59 %
  16         6.40           9.4         40 %
  32         7.80           7.7         24 %

La columna de eficiencia (qué fracción de cada máquina se aprovecha) es la que debería mirar quien paga la factura: con 32 máquinas se está aprovechando menos de la cuarta parte de cada una.

Conclusión

Distribuir un sistema aporta ventajas reales y medibles: escalabilidad horizontal (con un cálculo de capacidad que, en el caso de la Semana del Queso Artesano, sustituye una máquina enorme por nueve modestas y elásticas), disponibilidad (donde hemos aprendido a traducir los "nueves" a minutos de caída y a combinar componentes en serie y en paralelo), localidad (servir a cada ciudad desde cerca), flexibilidad (cada equipo con su ritmo) y compartición de recursos. Pero también hemos visto que el escalado tiene un techo, la ley de Amdahl, marcado por la fracción secuencial del trabajo —normalmente, el acceso a los datos—, y que cada frontera de red que añadimos a un flujo síncrono multiplica la disponibilidad por un factor menor que uno.

Los desafíos —complejidad, heterogeneidad, consistencia, fallos parciales, latencia, seguridad, observabilidad y coste operativo— son el índice del resto del curso. Y la tabla de "cuándo no distribuir" recuerda que la respuesta correcta a muchos problemas sigue siendo un monolito bien hecho.

Muchos de estos desafíos nacen de suposiciones falsas que los programadores hacemos sin darnos cuenta sobre la red. La siguiente lección, Las Falacias de la Computación Distribuida, las pone nombre una a una y muestra, con código, qué pasa cuando las creemos ciertas.

Curso de Arquitecturas Distribuidas

Módulo 1: Introducción a los Sistemas Distribuidos

Módulo 2: Comunicación en Sistemas Distribuidos

Módulo 3: Consistencia y Replicación

Módulo 4: Almacenamiento Distribuido

Módulo 5: Computación Distribuida

Módulo 6: Seguridad en Sistemas Distribuidos

Módulo 7: Monitoreo y Mantenimiento

Módulo 8: Casos de Estudio y Aplicaciones

© Copyright 2026. Todos los derechos reservados