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
- Ventajas de distribuir
- Escalabilidad: vertical frente a horizontal
- Tolerancia a fallos y disponibilidad: los "nueves"
- Flexibilidad, localidad y compartición de recursos
- La ley de Amdahl: el límite del escalado
- Desafíos de distribuir
- Cuándo NO distribuir
- Ejemplo de código: disponibilidad combinada en Kilómetro Cero
- Errores comunes y consejos
- Ejercicios
- Conclusión
- 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 |
- 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.
- 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:
- 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.
- 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.
- 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.
- 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:
Y cuando n tiende a infinito:
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.
- 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.
- 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.
- 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:
Componenteguarda un nombre y una disponibilidad entre 0 y 1;caida_anual_minla traduce a minutos de caída al año, que es la magnitud que entiende el negocio.en_seriemultiplica 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.en_paraleloyreplicaraplican 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).- 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.
- 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.
- ¿Cuántas consultas/s puede atender una instancia sin superar el 80 % de CPU (suponiendo relación lineal entre consultas y CPU)?
- ¿Cuántas instancias hacen falta para el pico, con redundancia N+1?
- 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 %).
- ¿Cuántos minutos de caída al año permite el objetivo del 99,95 %?
- ¿Qué disponibilidad mínima X necesita
pedidospara que la operación cumpla el objetivo? - Si una instancia de
pedidossolo 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).
- Calcula la aceleración con 6 máquinas y con 18 máquinas.
- ¿Cuál es el tiempo mínimo posible por muchas máquinas que se añadan?
- 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:
- Si 350 consultas/s consumen el 60 % de CPU, el 80 % corresponde a 350 × (80 / 60) ≈ 467 consultas/s por instancia.
- 2.100 / 467 = 4,5 → se necesitan 5 instancias para el pico, 6 con redundancia N+1.
- 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:
- 0,05 % de 525.600 minutos = 262,8 minutos (unas 4 h 23 min) al año.
- 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,
pedidosnecesita casi la misma disponibilidad que el objetivo global, porque los otros dos componentes ya "gastan" parte del presupuesto. - 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:
- 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.
- 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
- Conceptos Básicos de Sistemas Distribuidos
- Modelos de Sistemas Distribuidos
- Ventajas y Desafíos de los Sistemas Distribuidos
- Las Falacias de la Computación Distribuida
- Tiempo, Relojes y Ordenación de Eventos
- Del Monolito a la Plataforma Distribuida: el Caso Kilómetro Cero
Módulo 2: Comunicación en Sistemas Distribuidos
- Protocolos de Comunicación
- RPC y RMI
- gRPC y Serialización de Datos
- Mensajería y Colas de Mensajes
- Patrones de Comunicación Asíncrona
Módulo 3: Consistencia y Replicación
- Modelos de Consistencia
- El Teorema CAP y PACELC
- Algoritmos de Consenso
- Replicación de Datos
- Transacciones Distribuidas y Sagas
Módulo 4: Almacenamiento Distribuido
- Particionado de Datos y Hashing Consistente
- Sistemas de Archivos Distribuidos
- Almacenamiento de Objetos
- Bases de Datos Distribuidas
- Cachés Distribuidos
Módulo 5: Computación Distribuida
- Modelos de Computación Distribuida
- MapReduce y Hadoop
- Spark y Computación en Memoria
- Procesamiento de Flujos de Datos
- Planificación de Trabajos y Pipelines de Datos
Módulo 6: Seguridad en Sistemas Distribuidos
- Autenticación y Autorización
- Cifrado y Protección de Datos
- Gestión de Identidades
- Seguridad entre Servicios: mTLS y Gestión de Secretos
- Puertas de Enlace, Limitación de Tasa y Auditoría
Módulo 7: Monitoreo y Mantenimiento
- Monitoreo de Sistemas Distribuidos
- Logs Centralizados y Trazabilidad Distribuida
- Gestión de Fallos y Recuperación
- Patrones de Resiliencia: Timeouts, Reintentos y Circuit Breaker
- Automatización y Orquestación
- Pruebas en Sistemas Distribuidos e Ingeniería del Caos
