Módulo 1: Por qué fallan los sistemas distribuidos
7. El mapa de los puntos de falla del checkout de Mercado
Descripción
Este módulo te dio el fenómeno (la falla parcial), el porqué (las falacias), el mecanismo (la cascada), su física (el pool exhaustion) y su origen (la distribución). Esta lección junta todo en un artefacto concreto y reutilizable: el mapa de los puntos de falla del checkout de Mercado. En vez de hablar de fallas en abstracto, vamos a recorrer el flujo real —orders → catalog, payments, shipping— y enumerar, de forma sistemática y exhaustiva, cada lugar donde puede romperse y cada forma en que puede hacerlo. Es la diferencia entre "sé que los sistemas distribuidos fallan" y "aquí está la lista de los 12 puntos exactos donde este checkout puede fallar, qué pasa en cada uno, y cuáles pueden tumbar todo".
La técnica es un inventario disciplinado. Cada salto de red (cada flecha del diagrama, cada llamada que sale de orders) es un punto de falla, y cada punto puede fallar de cuatro modos —los desenlaces de la lección 2 vueltos catálogo operativo: caído (down), lento (slow), respuesta errónea (wrong) y ambiguo (ambiguous)—. Cruzar saltos por modos da la rejilla completa de fallas posibles. Sobre esa rejilla anotamos dos cosas que deciden la gravedad: si el salto está en la ruta crítica (sin él no hay checkout) y si un fallo suyo puede cascadear (agotar el pool de orders). Vas a ejecutar ese inventario en Python: 3 saltos × 4 modos = 12 puntos de falla, de los cuales 2 de 3 saltos pueden disparar una cascada, y uno —payments— tiene el agravante de ser crítico y peligroso de reintentar. Y cerramos con la tabla más importante del módulo: el mapeo de cada modo de falla al patrón (M2-M7) que lo resuelve —el puente literal hacia el resto de la guía—.
Conexión con el módulo: las seis lecciones anteriores construyeron el entendimiento; esta lo operacionaliza sobre el caso. El mapa que produces aquí es la entrada directa del módulo 2 y siguientes: cuando llegues a "cómo poner un timeout", ya sabrás sobre qué salto y contra qué modo de falla. Y es el molde del mini-proyecto (lección 8), donde harás este mismo mapa para una variante nueva del checkout, con tus manos. Aquí lo hacemos juntos; allá lo haces tú.
La analogía: la lista de verificación antes de despegar
Piénsalo así. Antes de que un avión despegue, el piloto no confía en su intuición ni en "se ve bien". Recorre una lista de verificación —el checklist— punto por punto: flaps, timón, presión de combustible, altímetro, tren de aterrizaje… cada sistema, uno por uno, marcado. La lista no existe porque los pilotos sean olvidadizos; existe porque la memoria y la intuición saltan cosas, y en un avión una cosa saltada mata. El checklist convierte "revisé el avión" (vago, incompleto, dependiente del estado de ánimo) en "revisé estos 40 puntos específicos, y estos son los que necesitan atención". Es la diferencia entre una impresión y un inventario.
Mapear los puntos de falla de un flujo es hacer su checklist de pre-vuelo. "El checkout puede fallar" es la impresión vaga; el mapa es el inventario: estos 12 puntos, de estas formas, con esta gravedad. Y como el checklist del piloto, su valor está en la exhaustividad mecánica —recorrer todos los saltos por todos los modos, sin saltarse ninguno porque "ese seguro está bien"—, precisamente porque la lección 2 nos enseñó que "todo sano" es 1 de 27 estados: no puedes darte el lujo de asumir que un salto no fallará. Esta es la idea:
Un mapa de puntos de falla recorre, de forma sistemática, cada salto de red del flujo por cada modo de falla (caído, lento, erróneo, ambiguo), y anota para cada uno si está en la ruta crítica y si puede cascadear. Convierte "el sistema puede fallar" (una impresión) en "estos son los N puntos exactos, su gravedad, y el patrón que ataca a cada uno" (un inventario accionable). Es el checklist de pre-vuelo del flujo.
Los saltos y los modos
Empecemos por nombrar las dos dimensiones del mapa.
Los saltos (dónde puede fallar). En el checkout, orders hace tres llamadas de red, y cada una es un salto:
orders ──► catalog (leer la ficha del producto)
orders ──► payments (cobrar)
orders ──► shipping (crear el envio)
No todos los saltos pesan igual. catalog es una lectura y es degradable: si falla, orders podría seguir con datos en caché o con la ficha mínima —el checkout no depende de él para completarse—. payments y shipping están en la ruta crítica: sin cobrar y sin crear el envío, no hay checkout (a menos que degrades el envío, que es el módulo 7). Y payments tiene un agravante único: cobrar es no idempotente por naturaleza —reintentar a ciegas puede cobrar dos veces—, así que su modo ambiguo es especialmente peligroso.
Los modos (cómo puede fallar). Cada salto puede fallar de cuatro formas, que son los desenlaces de la lección 2 vueltos catálogo:
| Modo | Qué es | Peligro principal |
|---|---|---|
down | El servicio no responde; falla rápido (conexión rechazada) | Falla el checkout, pero libera el recurso rápido |
slow | El servicio responde tardísimo | Retiene el worker → cascada |
wrong | Responde, pero con datos malos (formato, valor incorrecto) | Corrompe el resultado en silencio |
ambiguous | No hay respuesta; no sabes si ocurrió | Reintentar puede duplicar |
Fíjate en que slow es el único marcado con "cascada": es el modo que retiene recursos y agota el pool. down falla rápido (menos peligroso para el pool); wrong y ambiguous son peligrosos por otras razones (corrupción, duplicación), pero no agotan el pool por sí mismos. Esta distinción es la que va a decidir qué patrón aplicar a cada punto.
El ejemplo trabajado: ejecutar el inventario
Ahora construyamos el mapa como datos y dejemos que Python lo recorra, marcando la ruta crítica y el riesgo de cascada:
# l07_inventory.py -- inventario de puntos de falla del checkout de Mercado.
# Cada HOP (salto de red) es un punto de falla. Cada uno puede fallar de
# varias formas: down (no responde), slow (responde tarde), wrong (responde
# mal), ambiguous (no sabemos si ocurrio). Marcamos si esta en la ruta
# critica (sin el, no hay checkout) y si un fallo suyo puede CASCADEAR.
HOPS = [
# caller, callee, sync, critical, on_retry_unsafe
("orders", "catalog", True, False, False), # lee la ficha (degradable)
("orders", "payments", True, True, True), # cobra (no idempotente!)
("orders", "shipping", True, True, False), # crea el envio
]
MODES = ["down", "slow", "wrong", "ambiguous"]
print(f"{'hop':<22}{'critical':>9}{'sync':>6}{'cascade?':>10} modes")
points = 0
cascade_points = 0
for caller, callee, sync, critical, unsafe in HOPS:
# una llamada sincronica, en la ruta critica, sin proteccion => cascada.
can_cascade = sync and critical
cascade_points += 1 if can_cascade else 0
points += len(MODES)
hop = f"{caller} -> {callee}"
print(f"{hop:<22}{str(critical):>9}{str(sync):>6}{str(can_cascade):>10}"
f" {MODES}")
print(f"\ntotal failure points (hops x modes) = {len(HOPS)} x {len(MODES)} = {points}")
print(f"hops that can trigger a cascade = {cascade_points} of {len(HOPS)}")
print("note: payments is on the critical path AND retry-unsafe "
"(charging twice) -> needs idempotency (M4)")
Qué esperar. Corriendo python l07_inventory.py con Python 3.14.0:
hop critical sync cascade? modes
orders -> catalog False True False ['down', 'slow', 'wrong', 'ambiguous']
orders -> payments True True True ['down', 'slow', 'wrong', 'ambiguous']
orders -> shipping True True True ['down', 'slow', 'wrong', 'ambiguous']
total failure points (hops x modes) = 3 x 4 = 12
hops that can trigger a cascade = 2 of 3
note: payments is on the critical path AND retry-unsafe (charging twice) -> needs idempotency (M4)
Léelo despacio, porque es el checkout entero visto como superficie de ataque:
- Doce puntos de falla en un flujo de tres llamadas. Tres saltos por cuatro modos dan 12 formas en que este checkout —que en el diagrama se ve tan simple— puede romperse. Y esto ya simplifica (no cuenta las combinaciones de varios saltos fallando a la vez, que la lección 2 contó en 27 estados). El punto es visceral: un flujo "sencillo" tiene una docena de modos de falla, y cada uno necesita una respuesta de diseño. Ignorar 11 de ellos y programar solo para el éxito es lo que produce los incidentes.
- Dos de los tres saltos pueden cascadear.
paymentsyshippingestán en la ruta crítica y son síncronos, así que su modoslowpuede agotar el pool deorders—la cascada—.catalogno cascadea de la misma forma porque es degradable (aunque, ojo, siorderslo llama síncrono y sin timeout, uncataloglento también retiene workers; "degradable" es una decisión de diseño que aún no tomamos, no una protección automática). El mapa te dice dónde concentrar el blindaje primero: los saltos críticos y síncronos. paymentses el punto más delicado del mapa. Es el único que junta dos agravantes: está en la ruta crítica (no puedes saltártelo) y es no idempotente (reintentarlo a ciegas cobra dos veces). Su modoslowpuede cascadear (necesita timeout, M2, y bulkhead, M6) y su modoambiguouspuede duplicar cobros (necesita idempotencia, M4). Un solo salto, tres patrones. El mapa lo destaca para que no lo trates como a los demás.
De cada falla a su patrón: el puente a la guía
Aquí está el pago de haber hecho el mapa: cada celda de la rejilla (salto × modo) apunta a un patrón concreto que la ataca, y esos patrones son, uno a uno, los módulos que siguen. Esta es la tabla que convierte el problema en un plan:
| Salto | Modo de falla | Qué le pasa al checkout | Patrón que lo ataca | Módulo |
|---|---|---|---|---|
| cualquiera | slow | Retiene worker → cascada | Timeout + bulkhead | M2, M6 |
payments, shipping | down | Falla el checkout (crítico) | Circuit breaker (dejar de golpear) | M5 |
catalog | down | Sin ficha → se puede seguir | Degradación (ficha mínima/caché) | M7 |
shipping | down | Sin envío → se puede diferir | Degradación (envío diferido) | M7 |
| cualquiera | ambiguous (transitorio) | ¿Ocurrió? → reintentar con cuidado | Retry + backoff + jitter | M3 |
payments | ambiguous | ¿Cobré? → no duplicar | Idempotencia (idempotency key) | M4 |
| cualquiera | wrong | Datos malos → validar/fallback | Validación + fallback | M7 |
Léela de derecha a izquierda y verás el índice de la guía: cada módulo del 2 al 7 existe para tapar una columna de este mapa. El módulo 2 (timeout) ataca slow —el modo que cascadea, por eso va primero—. El 3 (retry) y el 4 (idempotencia) atacan ambiguous, en ese orden porque reintentar sin idempotencia es peligroso. El 5 (breaker) ataca down cuando el servicio lleva rato muerto. El 6 (bulkhead) refuerza al 2 aislando los pools. El 7 (degradación) ataca down y wrong de las dependencias que se pueden esquivar. El mapa no es solo un diagnóstico: es la tabla de contenidos ejecutable del resto de tu aprendizaje.
Un diagrama del checkout con cada salto anotado con su modo más peligroso y su patrón:
graph LR
sf[storefront] -->|checkout| orders
orders -->|"catalog: slow/down<br/>→ timeout + degrade (M2,M7)"| catalog
orders -->|"payments: slow/ambiguous<br/>→ timeout + idempotency (M2,M4)"| payments
orders -->|"shipping: slow/down<br/>→ timeout + breaker + degrade (M2,M5,M7)"| shipping
Errores comunes
Mapear solo el modo "down" y olvidar "slow". Qué pasa: se hace el inventario pensando "¿qué pasa si cada dependencia se cae?", y se omite "¿qué pasa si se pone lenta?". Por qué es un error: slow es el modo que cascadea —el más destructivo—, y es justo el que la intuición salta porque "al menos está respondiendo". Cómo detectarlo: si tu mapa solo tiene una columna "¿disponible sí/no?", te falta la mitad peligrosa. Cómo corregirlo: incluye siempre los cuatro modos; el mapa de esta lección pone slow primero entre los peligrosos por algo.
Tratar todos los saltos por igual. Qué pasa: se aplica el mismo blindaje (o ninguno) a catalog, payments y shipping, como si fueran intercambiables. Por qué es un error: difieren en gravedad —catalog es degradable, payments es crítico y no idempotente, shipping es crítico pero diferible—, y cada uno pide un conjunto distinto de patrones. Cómo detectarlo: si tu plan es "ponerle timeout a todo y ya", estás ignorando que payments además necesita idempotencia y shipping necesita degradación. Cómo corregirlo: el mapa anota criticidad y idempotencia por salto justamente para diferenciar el tratamiento.
Confundir el mapa con la solución. Qué pasa: se hace un mapa precioso de puntos de falla y se siente que "ya está resuelto". Por qué es un error: el mapa es el diagnóstico, no el tratamiento —sabes dónde y cómo puede fallar, pero todavía no has puesto un solo timeout—. Cómo detectarlo: si tienes el mapa pero el código sigue llamando a shipping sin protección, mapeaste el problema y no lo tocaste. Cómo corregirlo: el mapa es la entrada de los módulos 2-7; su valor se realiza cuando cada punto marcado recibe su patrón. Diagnosticar sin tratar es la mitad del trabajo.
Ejercicios
Ejercicio 1 — Amplía el mapa. Mercado agrega un cuarto salto al checkout: orders → inventory (reservar stock), que es síncrono, está en la ruta crítica (no puedes vender sin stock) y es no idempotente (reservar dos veces aparta stock de más). Agrega esta fila al inventario mentalmente: ¿cuántos puntos de falla totales hay ahora? ¿Cuántos saltos pueden cascadear? ¿Qué patrones necesita inventory?
Ver solución
Con cuatro saltos y cuatro modos: 4 × 4 = 16 puntos de falla totales (antes 12).
Saltos que pueden cascadear: ahora 3 de 4 (payments, shipping e inventory, todos síncronos y críticos; solo catalog no, por degradable). Antes eran 2 de 3.
Patrones que necesita inventory:
- Timeout + bulkhead (M2, M6) contra su modo
slow—es síncrono y crítico, así que su lentitud cascadea—. - Idempotencia (M4) contra su modo
ambiguous—como es no idempotente (reservar dos veces aparta stock de más), reintentar a ciegas es peligroso, igual quepayments—. - Circuit breaker (M5) contra su modo
downsostenido.
Nota que inventory termina pareciéndose mucho a payments: crítico, no idempotente, cascadea. Es un patrón que se repite —las operaciones que mutan estado y son críticas son siempre las más delicadas—, y por eso el mapa marca la idempotencia por separado.
Ejercicio 2 — Ordena el blindaje. Tienes tiempo para blindar un solo salto del checkout original antes de un evento de alto tráfico. Con el mapa en la mano, ¿cuál eliges y con qué patrón, y por qué ese antes que los otros?
Ver solución
El candidato más fuerte es blindar shipping (o payments) contra su modo slow con un timeout (M2).
El razonamiento sigue el mapa: bajo alto tráfico, el riesgo número uno es la cascada, y el modo que la dispara es slow sobre un salto crítico y síncrono. Un timeout ataca exactamente eso —convierte la espera indefinida en un corte rápido, evitando el pool exhaustion—, y protege no solo a ese salto sino, al no dejar caer a orders, a todo lo que está por encima (el storefront). Es el patrón de mayor "retorno" por unidad de esfuerzo, y por eso es el módulo 2.
Entre shipping y payments, shipping es un poco mejor candidato para el primer timeout porque es más propenso a ponerse lento (integra transportistas externos) y es degradable (puedes diferir el envío), mientras que payments además necesitará idempotencia, que es más trabajo. Pero cualquiera de los dos críticos, con timeout, es la respuesta correcta: atacar el modo que cascadea, en un salto crítico, es siempre la primera prioridad. (Si dijiste catalog, reconsidéralo: es degradable, así que su falla es la menos urgente.)
Ejercicio 3 — Justifica una celda del mapeo. En la tabla de patrones, la celda "payments + ambiguous" apunta a idempotencia (M4), no a "retry + backoff (M3)", aunque ambos modos ambiguos suelen resolverse reintentando. ¿Por qué payments necesita idempotencia y no le basta con reintentar como a los demás?
Ver solución
Porque payments.charge() es no idempotente por naturaleza: cobrar es una operación que muta estado con efecto en el mundo real (le quita dinero a alguien). Cuando el modo es ambiguous —orders no recibió respuesta y no sabe si el cobro ocurrió—, reintentar a ciegas (lo que haría un retry simple del M3) puede cobrar dos veces: si el primer intento sí cobró y solo se perdió la confirmación, el reintento hace un segundo cobro real.
La solución no es dejar de reintentar (a veces hay que hacerlo, porque quizá el primer intento no llegó), sino hacer que el reintento sea seguro: enviar una idempotency key —un identificador único de esa operación de cobro— de modo que payments, si ve una key que ya procesó, devuelva el resultado del cobro original sin cobrar de nuevo. Eso es la idempotencia (M4). El retry (M3) sigue siendo necesario para decidir cuándo reintentar; la idempotencia (M4) es lo que hace que reintentar no duplique. Por eso van juntos y en ese orden: primero aprendes a reintentar bien (M3), luego a hacer que reintentar sea inofensivo para operaciones como el cobro (M4). El mapa señala que payments, por ser crítico y mutante, necesita ambos, mientras que un salto de solo lectura como catalog se conforma con el retry.
Resumen y siguiente paso
En esta lección convertiste todo el módulo en un artefacto: el mapa de los puntos de falla del checkout de Mercado. Recorriste, de forma sistemática, cada salto de red (orders → catalog/payments/shipping) por cada modo de falla (down, slow, wrong, ambiguous), anotando criticidad y riesgo de cascada. El inventario ejecutado dio 12 puntos de falla (3 saltos × 4 modos) en un flujo de apenas tres llamadas, con 2 de 3 saltos capaces de cascadear, y destacó a payments como el punto más delicado —crítico y no idempotente—. Es el checklist de pre-vuelo del checkout: la impresión "puede fallar" vuelta inventario accionable.
Y cruzaste el puente hacia el resto de la guía: la tabla que mapea cada modo de falla a su patrón —slow → timeout + bulkhead (M2, M6); down crítico → circuit breaker (M5); down degradable → degradación (M7); ambiguous → retry (M3) o idempotencia (M4); wrong → validación (M7)—. Esa tabla es, literalmente, la tabla de contenidos ejecutable de los módulos 2 a 7: cada uno tapa una columna de tu mapa.
Antes de avanzar deberías poder: enumerar los saltos del checkout y los cuatro modos de falla; construir la rejilla salto × modo y contar los puntos de falla; explicar por qué slow es el modo que cascadea y por qué payments es el salto más delicado; y mapear un modo de falla dado al patrón (M2-M7) que lo ataca.
Lo que sigue es hacerlo tú. En la lección 8, el mini-proyecto, recibes una variante del checkout de Mercado —con una dependencia nueva, fraud— y produces su mapa de modos de falla completo con Python, del estilo de un análisis FMEA (Failure Mode and Effects Analysis): para cada dependencia, su modo de falla, su efecto en el checkout, su riesgo de cascada y el patrón que la ataca. Es tu graduación del módulo: pasas de leer el mapa a dibujarlo, que es lo que un arquitecto hace cuando llega a un sistema nuevo y le preguntan "¿por dónde puede fallar esto?".
Recursos
- Release It!, 2ª ed., de Michael Nygard — Pragmatic Bookshelf — el capítulo sobre integration points es la fuente directa de esta lección: cada punto de integración (cada salto) es un lugar donde el sistema puede fallar, y Nygard los cataloga por modo igual que aquí. Su lista de "los puntos de integración son el asesino número uno de la estabilidad" es el corazón de este mapa.
- "Failure Mode and Effects Analysis (FMEA)" — Wikipedia — la técnica formal de ingeniería (nacida en la aeronáutica y la industria) de enumerar sistemáticamente modos de falla y sus efectos. El mini-proyecto de la lección 8 es una versión ligera de esto; vale la pena conocer el método completo.
- Google SRE Book — "Embracing Risk" — cómo pensar la confiabilidad como un inventario de riesgos que se priorizan (no todo se blinda igual), que es exactamente lo que el mapa te permite hacer: decidir qué salto blindar primero.
- AWS Well-Architected — Reliability Pillar — el marco de AWS para diseñar sistemas confiables, con su propio enfoque de identificar puntos de falla y aplicar el patrón adecuado a cada uno. Complementa la tabla "falla → patrón" de esta lección con la perspectiva de un proveedor de nube.