Módulo 7: Fiabilidad y el tradeoff de consistencia
8. Proyecto: elige y justifica el modelo de consistencia de Enlace y su SLO
Descripción
Llegó el momento de juntar las siete lecciones en un solo entregable. En este proyecto vas a hacer lo que hace un ingeniero cuando le toca cerrar la parte de fiabilidad y consistencia de un diseño: tomar decisiones y justificarlas con números. No "hagámoslo confiable y ya", sino un documento defendible que responde cuatro preguntas concretas, cada una respaldada por una lección del módulo y por aritmética que se ejecuta:
- El plan de fiabilidad: ¿cuáles son los single points of failure de Enlace y cómo los elimino? (lecciones 2 y 3)
- La clasificación CAP/PACELC: ¿qué es Enlace bajo partición y qué es el resto del tiempo? (lecciones 4 y 5)
- El modelo de consistencia: ¿qué garantía le pongo a cada operación, y por qué la eventual alcanza para
resolve? (lección 6) - El SLO: ¿qué SLI mido, qué objetivo apunto, cuánto error budget me da, y me lo permite mi techo de dependencias? (lección 7)
El entregable es un documento de diseño de fiabilidad —un diagrama de la topología con sus SPOF marcados, una tabla de decisiones de consistencia por operación, y una hoja de SLO con los números ejecutados—. Vas a construir una pequeña calculadora en Python que produce la hoja de SLO a partir de tus decisiones, correrla, y defender cada elección. Al terminar tendrás una plantilla que sirve para razonar la fiabilidad de cualquier sistema, no solo la de Enlace.
Conexión con el módulo: este es el capstone. Usa los SPOF y la redundancia de la lección 2, el failover de la 3, el CAP correcto de la 4, el PACELC de la 5, la consistencia eventual medida de la 6 y los nines/error budget de la 7 —todo a la vez— para producir un artefacto real. Es también el puente al capstone de la guía completa (módulo 8, module-08-project-design-enlace-end-to-end): las decisiones de fiabilidad y consistencia que produzcas aquí son uno de los componentes que ese diseño end-to-end integra con la estimación, la caché, las réplicas y el balanceo de los módulos anteriores.
El brief del proyecto
Eres el ingeniero a cargo de la fiabilidad y la consistencia de Enlace. El equipo te da los números-ancla —los mismos de toda la guía— y la topología escalada de los módulos 4 a 6, y te pide un documento de diseño. Estos son los datos de entrada, fijos:
| Dato de entrada | Valor | De dónde sale |
|---|---|---|
Lecturas por segundo (qps_read) | ~4,000 | ratio 100:1 (módulo 2) |
Escrituras por segundo (qps_write) | ~40 | módulo 2 |
| Ratio lectura:escritura | 100:1 | requisito canónico |
| Espacio de códigos | base62⁷ ≈ 3.5×10¹² | módulo 3 |
| Replication lag de peor caso | ~2 s | módulo 5 |
| Disponibilidad de dependencias | compute 99.95%, db 99.9%, CDN 99.99% | proveedores (dato del brief) |
Y estas son las decisiones que tú tomas (las palancas de diseño): qué componentes hacer redundantes, qué configuración de failover para cada uno, la clasificación CAP/PACELC, el modelo de consistencia por operación, y el SLI/SLO/SLA con su objetivo numérico. El proyecto consiste en elegir cada una, justificarla, y calcular las consecuencias.
Los pasos del diseño
Antes de ver la solución de referencia, aquí está el proceso. Hazlo tú primero; la solución está después para que compares.
Paso 1 — Mapea los SPOF y su eliminación
Dibuja la topología de Enlace (balanceador → apps → caché + primary/réplicas) y marca cada single point of failure. Para cada uno, decide la redundancia (lección 2) y la configuración de failover (lección 3: active-active para lo stateless, active-passive para la base de datos). Recuerda la regla N+1: una unidad de repuesto más de las que necesitas.
Paso 2 — Clasifica Enlace en CAP y PACELC
Decide la rama "if Partition" (¿AP o CP?) y la rama "Else" (¿EL o EC?) para el camino de resolve, y justifica cada una con el costo de equivocarse. Recuerda que la clasificación puede ser por operación: resolve (lectura) y la unicidad de shorten (escritura) no tienen por qué ser iguales.
Paso 3 — Elige el modelo de consistencia por operación
Para cada operación de Enlace —resolve, la lectura del creador recién después de shorten, y la unicidad del short_code al generarlo— elige el modelo (eventual, read-your-writes, fuerte-por-construcción) y justifícalo. Apóyate en la cuenta de la ventana de propagación de la lección 6 (cuántos códigos en vuelo, qué probabilidad de colisión).
Paso 4 — Fija el SLI, el SLO y el SLA, y verifica el techo
Elige el SLI (qué mides), el SLO (tu meta interna) y el SLA (lo que prometes, ≤ SLO). Calcula el error budget del SLO. Y —crucial— verifica el techo de dependencias: multiplica la disponibilidad de tus dependencias en serie y comprueba si te da margen para el SLO; si no, propón el cambio de arquitectura que lo suba (lección 2).
Solución de referencia
Aquí está un documento de diseño completo y defendible. No es la única respuesta correcta —otro SLO objetivo o una redundancia distinta también se defienden—, pero es una propuesta sólida con cada decisión justificada.
Entregable 1 — El plan de fiabilidad
La topología con sus SPOF marcados y su remedio:
┌──────────────────┐
clientes ─────────► │ load balancer │ SPOF ► redundante: 2 balanceadores
│ (x2, VIP flotante) │ con IP virtual flotante,
└────────┬─────────┘ failover a nivel de red (rapido)
┌───────────┼───────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ no-SPOF: stateless (modulo 6),
│ app 1 │ │ app 2 │ │ app 3 │ active-active, N+1 (una de mas).
└───┬───┘ └───┬───┘ └───┬───┘ Failover trivial: el LB saca la
└───────────┼───────────┘ caida por health check.
┌───────────┼───────────┐
▼ ▼ ▼
┌───────┐ ┌────────┐ ┌────────┐ base de datos: primary + replicas,
│ cache │ │primary │─►│replica │ active-passive. Failover: promover
│ (aside)│ │ │ │ (x N+1)│ una replica al dia (lecciones 3, 5).
└───────┘ └────────┘ └────────┘ Cache-aside: si la cache cae, las
apps van a la db (degrada, no tumba).
| Componente | ¿SPOF? | Remedio | Configuración de failover |
|---|---|---|---|
| Load balancer | Sí (en la puerta) | 2 balanceadores + IP virtual flotante | Failover de red, rápido (lo más crítico) |
| Servidores de app | No | Ya redundantes (stateless), N+1 | Active-active: el LB saca la caída (trivial) |
| Caché | Parcial | Cache-aside: si cae, las apps van a la db | Degrada la latencia, no tumba el servicio |
| Primary de la db | Sí | Réplicas listas para promover | Active-passive: promover réplica (a diseñar con cuidado por split-brain → guía de resiliencia) |
| Réplicas de lectura | No | N+1 réplicas | Active-active entre ellas: el LB de lecturas reparte |
Nota clave heredada de la asimetría de Enlace: durante un failover del primary, las réplicas siguen sirviendo lecturas, así que solo se degradan las escrituras (shorten, el 1% del tráfico). Un failover del primary es una degradación menor, no una caída total.
Entregable 2 — La clasificación CAP/PACELC
Enlace (camino de resolve) es PA/EL.
- if Partition → PA (disponible). Bajo una partición de red entre el primary y una réplica,
resolvesigue respondiendo con la copia que tenga, aunque pueda estar unos segundos vieja. Costo de la alternativa (CP): negarse a redirigir a un usuario que hizo clic —error o espera—, una experiencia mucho peor que una URL levemente desactualizada (lección 4). - Else → EL (baja latencia). Con la red sana —el 99.99% del tiempo— cada
resolvese sirve de la réplica más cercana en ~1 ms, sin coordinación. Pagar coordinación (EC) costaría ~14 ms extra por lectura, en 4,000 lecturas/s, para garantizar una exactitud que un redirect casi nunca necesita (lección 5).
Es la misma clase que Cassandra y DynamoDB, y no por casualidad: Enlace tiene el perfil arquetípico —lectura pesada, datos tolerantes al desfase, prioridad en responder siempre y rápido— para el que esos sistemas existen.
Entregable 3 — El modelo de consistencia, por operación
| Operación | Modelo | Justificación |
|---|---|---|
resolve (lectura general) | Eventual | Solo ~80 short_code en vuelo en cualquier instante; probabilidad 2×10⁻¹¹ de que una lectura al azar pegue a uno; y un código recién creado nadie más lo conoce todavía. La eventual sobra (lección 6). |
Lectura del creador tras shorten | Read-your-writes | El único caso stale real es el creador resolviendo su enlace antes de que propague. Se sirven sus lecturas desde el primary por un ratito. No hace falta consistencia fuerte para todos, solo esta garantía para el autor. |
Unicidad del short_code al generarlo | Fuerte por construcción | Aquí la eventual NO alcanza: dos códigos iguales para URLs distintas es corrupción irreversible. Se garantiza por diseño —rangos disjuntos por nodo (id_generator, módulo 3)— para que dos nodos nunca colisionen, sin coordinar en caliente. |
Contador de clicks (si existe) | Eventual | Un conteo unos segundos atrasado no le hace daño a nadie; coordinar cada incremento a 4,000/s sería un cuello de botella innecesario. |
La lección transversal: la consistencia se elige por operación, no para todo el sistema. Enlace es casi por completo eventual, con read-your-writes para el autor y una garantía fuerte-por-construcción solo en la unicidad.
Entregable 4 — La hoja de SLO, ejecutada
Primero la calculadora que produce los números:
# enlace_reliability_and_slo.py — la hoja de SLO, ejecutada
SECONDS_PER_YEAR = 365 * 24 * 3600
SECONDS_PER_MONTH = 30 * 24 * 3600
def downtime(a, period):
return (1 - a) * period
def human(seconds):
if seconds >= 3600: return f"{seconds/3600:.2f} h"
if seconds >= 60: return f"{seconds/60:.1f} min"
return f"{seconds:.1f} s"
def parallel(a, n):
return 1 - (1 - a) ** n
# --- El SLO elegido y su traduccion ---
slo = 0.999 # objetivo interno para resolve
print("SLO de Enlace en resolve:")
print(f" objetivo = {slo*100}% downtime = {human(downtime(slo, SECONDS_PER_YEAR))}/anio")
print(f" error budget = {human(downtime(slo, SECONDS_PER_MONTH))}/mes")
# --- El techo de dependencias, antes y despues de redundar la db ---
compute, db, cdn = 0.9995, 0.999, 0.9999
ceiling_before = compute * db * cdn
print(f"\nTecho de dependencias (en serie):")
print(f" antes: {ceiling_before*100:.3f}% -> {'ALCANZA' if ceiling_before>=slo else 'NO ALCANZA'} para SLO {slo*100}%")
db_redundant = parallel(0.999, 2) # dos db al 99.9% en paralelo (leccion 2)
ceiling_after = compute * db_redundant * cdn
print(f" db redundante ({db_redundant*100:.4f}%):")
print(f" despues: {ceiling_after*100:.3f}% -> {'ALCANZA' if ceiling_after>=slo else 'NO ALCANZA'} para SLO {slo*100}% con margen")
Qué esperar. Al correrlo:
SLO de Enlace en resolve:
objetivo = 99.9% downtime = 8.76 h/anio
error budget = 43.2 min/mes
Techo de dependencias (en serie):
antes: 99.840% -> NO ALCANZA para SLO 99.9%
db redundante (99.9999%):
despues: 99.940% -> ALCANZA para SLO 99.9% con margen
Y la hoja de SLO que resume la decisión:
| Elemento | Valor | Justificación |
|---|---|---|
| SLI | % de resolve con éxito en <100 ms, mensual | resolve es el 99% del tráfico y lo que le importa al usuario (ser redirigido rápido) |
| SLO | 99.9% (43.2 min/mes de error budget) | Serio pero alcanzable; absorbe failovers y despliegues. Perseguir 99.99% multiplicaría el costo sin necesidad para un acortador |
| SLA | 99.5% hacia clientes de pago | ≤ SLO, para tener colchón: un mes malo rompe el SLO (alarma interna) sin romper el SLA (sin multa) |
| Techo de dependencias | 99.84% en serie → no alcanza | Hay que redundar la db (el eslabón débil al 99.9%): sube el techo a 99.94%, ahora con margen sobre el SLO |
El hallazgo más importante del cálculo: el SLO de 99.9% no es sostenible con las dependencias en serie tal como están (techo 99.84%, por debajo del objetivo). La acción que lo arregla es la de la lección 2: hacer redundante la base de datos, el eslabón más débil, que sube el techo a 99.94% y da margen. Un SLO sin verificar el techo de dependencias es una promesa que no controlas; verificarlo convierte el proyecto en un diseño honesto.
Errores comunes
Elegir "todo fuerte" o "todo eventual" para simplificar (de granularidad). Qué pasa: alguien quiere una sola etiqueta para todo Enlace y aplica el mismo modelo a la lectura y a la generación de códigos —abriendo la puerta a colisiones si elige eventual, o encareciendo el resolve sin necesidad si elige fuerte—. Por qué pasa: una etiqueta única es más cómoda de comunicar. Cómo detectarlo: si operaciones con distinto costo de incoherencia (un redirect viejo es inofensivo; un código duplicado es corrupción) reciben el mismo modelo, estás mal calibrado. Cómo corregirlo: el entregable 3 es una tabla por operación, no una etiqueta única.
Prometer un SLO sin verificar el techo de dependencias (de arquitectura). Qué pasa: se fija un SLO de 99.9% y se olvida calcular el producto de las dependencias en serie, garantizando que el sistema romperá el objetivo por causas externas al propio código. Por qué pasa: se mira la disponibilidad del servicio propio y no la cadena completa. Cómo detectarlo: si tu SLO es mayor que el producto de las disponibilidades de todo lo que está en el camino crítico, ya perdiste. Cómo corregirlo: haz el cálculo del techo antes de comprometer el SLO (como en el entregable 4) y, si no alcanza, redunda o saca del camino el eslabón más débil.
Diseñar el failover del primary de forma ingenua (de fiabilidad). Qué pasa: el plan dice "si el primary no responde, promover una réplica automáticamente", sin ningún mecanismo contra el split-brain —el primary podría estar vivo pero aislado, y quedar dos primaries escribiendo—. Por qué pasa: la promoción automática se ve simple. Cómo detectarlo: si tu failover no nombra fencing ni consenso, tiene el agujero del split-brain. Cómo corregirlo: en este nivel de diseño, reconocer que el failover del primary necesita fencing y elección de líder por consenso, y marcarlo como frontera con la guía de resiliencia —no dejarlo como un promover y listo—.
Ejercicios
Ejercicio 1 — Defiende tu SLO. Un directivo te dice: "99.9% permite 8.76 horas de caída al año. Eso me parece muchísimo. Quiero que prometamos 99.999% (cinco nueves)". Responde con dos argumentos técnicos y de costo por los que cinco nueves no es apropiado para Enlace, apoyándote en la tabla de la lección 7 y en el techo de dependencias.
Ver solución
- El costo por nueve crece ~10× y no lo compra el caso de uso. Pasar de 99.9% (8.76 h/año) a 99.999% (5.3 min/año) son tres nueves más: cada uno divide el downtime entre diez y multiplica el esfuerzo (failover 100% automático sin intervención humana, redundancia multi-región, guardias 24/7). Para un acortador de URLs —donde una URL que tarda un momento en redirigir, muy de vez en cuando, no causa daño real— ese gasto es desproporcionado. Cinco nueves es el estándar de la telefonía de emergencia, no de un redirect.
- El techo de dependencias lo hace imposible sin rearquitectura mayor. Las dependencias en serie de Enlace dan un techo de 99.84% (o 99.94% redundando la db). Prometer 99.999% (que exige un techo de al menos 99.999%) requeriría hacer redundante cada dependencia en múltiples regiones y proveedores, un costo enorme —y aun así, 5.3 min/año no deja margen para ningún error humano—. Prometerlo sin ese trabajo sería firmar un SLA que romperemos el primer mes por causas fuera de nuestro control.
Contrapropuesta: 99.9% como SLO interno con la db redundante (techo 99.94%, con margen), y un SLA hacia clientes por debajo (99.5%) para tener colchón. Serio, alcanzable y honesto.
Ejercicio 2 — Recalcula el techo. El equipo de infraestructura mejora el proveedor de cómputo de 99.95% a 99.99%, pero la base de datos sigue siendo un primary único al 99.9% y el CDN 99.99%. (a) Calcula el nuevo techo de dependencias en serie. (b) ¿Alcanza para un SLO de 99.9%? (c) ¿Qué eslabón sigue mandando y qué harías?
Ver solución
- (a) Techo
= 0.9999 × 0.999 × 0.9999 = 0.998801, o sea 99.88%. - (b) No alcanza. 99.88% está por debajo del SLO de 99.9%. Mejorar el cómputo casi no movió el techo.
- (c) Sigue mandando la base de datos (el eslabón más débil, 99.9%), exactamente como la regla del eslabón débil de la lección 2: en el producto, el término más pequeño domina, así que mejorar el cómputo (que ya era fuerte) apenas cambia el resultado. Lo que hay que hacer es redundar la base de datos: dos al 99.9% en paralelo dan 99.9999%, y el techo sube a
0.9999 × 0.999999 × 0.9999 ≈ 0.99980, o sea 99.98%, ahora con margen sobre el SLO. Siempre redunda primero el eslabón más débil.
Ejercicio 3 — Diseña la fiabilidad de una feature nueva. Enlace agrega enlaces "premium" con expiración: un short_code que deja de funcionar en una fecha expires_at. Un cliente de pago exige que, en el instante exacto en que un enlace expira, deje de redirigir en todo el mundo. Analiza: (a) ¿qué modelo de consistencia exige ese requisito? (b) ¿choca con la clasificación PA/EL de Enlace? (c) propón un diseño que cumpla el requisito sin volver fuertemente consistente todo el resolve.
Ver solución
- (a) "En el instante exacto, en todo el mundo, deja de redirigir" es un requisito de consistencia fuerte (linearizabilidad): toda réplica, preguntada justo después de la expiración, debe coincidir en que el enlace ya no vale. Es lo contrario de la eventual, donde distintas réplicas podrían diferir por unos segundos.
- (b) Sí, choca. Enlace es PA/EL justamente porque
resolvetolera el desfase; un requisito de expiración instantánea y global pide coordinación que PA/EL evita a propósito. Cumplirlo con consistencia fuerte en cadaresolvemataría la baja latencia de las 4,000 lecturas/s. - (c) El diseño que reconcilia ambos: no volver fuerte todo el
resolve, sino acotar el requisito. Opciones: (1) Aceptar una ventana de tolerancia —negociar con el cliente que la expiración es efectiva "en segundos", no "en el nanosegundo exacto"—, lo que mantiene la eventual y probablemente satisface la intención real. (2) Si el instante exacto es innegociable, tratar la expiración como una escritura fuertemente consistente (invalidar elshort_codeen el primary y en la caché de forma coordinada) pero mantener elresolveeventual para todo lo demás: solo los enlaces premium con expiración pagan el costo de la coordinación, y solo en el momento de expirar, no en cada lectura. Es, otra vez, elegir la consistencia por operación y por caso: el 99.99% del tráfico sigue siendo eventual y rápido; solo esta feature específica paga por la garantía que de verdad necesita.
Resumen y siguiente paso
En este proyecto juntaste las seis lecciones en un documento de diseño defendible para Enlace. Produjiste los cuatro entregables: el plan de fiabilidad (los SPOF marcados en el diagrama —balanceador y primary— y su remedio con redundancia y failover, recordando que un failover del primary solo degrada las escrituras gracias a la asimetría 100:1); la clasificación CAP/PACELC (PA/EL, disponible bajo partición y rápido el resto del tiempo, justificado con el costo de cada alternativa); el modelo de consistencia por operación (eventual para resolve, read-your-writes para el creador, fuerte-por-construcción para la unicidad); y la hoja de SLO ejecutada (SLI de resolve, SLO de 99.9% con 43.2 min/mes de error budget, SLA por debajo para tener colchón, y la verificación del techo de dependencias que reveló que hay que redundar la base de datos para sostenerlo). El hallazgo que amarra todo: un SLO sin verificar el techo de dependencias es una promesa que no controlas.
Con esto cierras el módulo 7. Ya sabes razonar, con números y sin mitos, la fiabilidad de un sistema (SPOF, redundancia, failover), su tradeoff de consistencia (CAP, PACELC, fuerte contra eventual) y su promesa de disponibilidad (SLI/SLO/SLA, nines, error budget). Sabes encontrar el eslabón más débil, elegir la consistencia por operación, y traducir un porcentaje en horas de caída honestas.
Lo que sigue es el capstone de toda la guía. En el módulo 8 (module-08-project-design-enlace-end-to-end) vas a integrar todo lo que construiste a lo largo de ocho módulos —cómo abordar el problema (M1), la estimación de capacidad (M2), el modelo de datos y la generación de IDs (M3), la caché (M4), las réplicas y el sharding (M5), el balanceo y la falta de estado (M6), y la fiabilidad y consistencia que acabas de cerrar (M7)— en un solo diseño de Enlace de punta a punta: el diagrama, la tabla de capacidad, el camino de lectura y de escritura, y la lista de tradeoffs justificados. Las decisiones de fiabilidad y consistencia de este proyecto son una de las piezas que ese diseño final ensambla. Y desde ahí, la guía te apunta hacia sus hermanas del ecosistema —resiliencia, eventos, estilos arquitectónicos— para seguir profundizando en cada frontera que aquí marcamos.
Recursos
- Google SRE Book — Capítulo 3 (Embracing Risk) y Capítulo 4 (SLOs) — cómo un equipo real decide cuánta fiabilidad es suficiente (ni de más ni de menos) y cómo el error budget guía esa decisión; el marco que sostiene tu elección de SLO en el entregable 4.
- Designing Data-Intensive Applications, Martin Kleppmann — Capítulos 5 y 9 (repaso integrador) — los dos capítulos que cubren replicación (fiabilidad, failover, lag) y consistencia/consenso (linearizabilidad, CAP); leerlos juntos consolida las decisiones de los entregables 1 a 3 en un solo marco.
- System Design Primer — checklist de disponibilidad, consistencia y CAP — un repaso rápido de los patrones que usaste en el proyecto (redundancia, failover, CAP, consistencia eventual), útil como lista de verificación al defender tu documento de diseño en una entrevista.