Si le preguntas a la dirección de AlpinaShop qué nivel de servicio espera de la tienda, la respuesta será "que no se caiga nunca". Es una respuesta comprensible y completamente inútil, por tres motivos: no se puede cumplir, no se puede medir, y no permite decidir nada.

No se puede cumplir porque el 100 % de disponibilidad no existe: falla el hardware, falla la red del cliente, falla el proveedor de DNS, falla un despliegue, se equivoca una persona. No se puede medir porque nadie ha definido qué cuenta como "caída". Y no permite decidir nada porque si el objetivo es la perfección, cualquier inversión en fiabilidad está justificada y ninguna es suficiente, lo cual en la práctica significa que las decisiones se toman por intuición.

La lección anterior terminó señalando exactamente eso: AlpinaShop paga la alta disponibilidad de Cloud SQL, ha duplicado los túneles VPN y mantiene el balanceador global sin ningún objetivo declarado que justifique esas tres decisiones con números. Y arrastra desde 07-04 una deuda calificada de crítica: nadie ha comprobado nunca que las copias de seguridad se puedan restaurar.

Esta lección convierte la fiabilidad en ingeniería. Al terminar sabrás definir SLI, SLO y SLA con precisión, calcular y usar un presupuesto de error para decidir cuándo se despliega y cuándo se para, crear SLO y alertas por consumo de presupuesto en Cloud Monitoring, elegir el nivel de alta disponibilidad de cada componente sabiendo lo que cuesta y lo que aporta, diseñar contra los modos de fallo reales, y tener un plan de recuperación ante desastres con RTO y RPO — ensayado, no escrito.

Contenido

  1. Qué significa fiabilidad y por qué la perfección no es un objetivo
  2. SLI, SLO y SLA: las tres siglas que todo el mundo confunde
  3. Elegir buenos SLI para AlpinaShop
  4. Los SLO de AlpinaShop, con sus consultas reales
  5. Presupuesto de error: qué es y para qué sirve de verdad
  6. SLO en Cloud Monitoring y alertas por consumo de presupuesto
  7. Arquitecturas de alta disponibilidad por niveles
  8. Modos de fallo y su mitigación
  9. Recuperación ante desastres: RTO, RPO y estrategias
  10. El plan de recuperación de AlpinaShop
  11. Backups que no se han probado no existen
  12. Práctica operativa: guardias, runbooks y post-mortem
  13. Pruebas de caos a escala razonable

  1. Qué significa fiabilidad y por qué la perfección no es un objetivo

Fiabilidad es la probabilidad de que un sistema haga lo que se espera de él durante un periodo determinado. No es una propiedad binaria: es una magnitud continua que se elige, se paga y se gestiona.

La curva que hay que tener en la cabeza:

Objetivo Coste relativo Qué exige
99 % 3 Un servidor bien configurado
99,9 % 8 Multizona, despliegue canario, reversión rápida
99,95 % 15 Lo anterior + guardias en horario ampliado
99,99 % 35 Multirregión, guardias 24×7, automatización de conmutación
99,999 % 95 Activo-activo, ingeniería dedicada, despliegues mucho más lentos

Cada nueve adicional multiplica el coste, y no de forma lineal. Pasar de 99 % a 99,9 % suele ser cuestión de buenas prácticas. Pasar de 99,99 % a 99,999 % requiere multirregión activo-activo, guardias 24×7, ingeniería dedicada y aceptar que despliegas mucho más despacio.

Y la otra mitad de la ecuación, la que casi nunca se calcula:

Disponibilidad Tiempo caído al mes Pedidos perdidos (AlpinaShop) Coste aproximado del corte
99 % 7 h 18 min ~12 ~780 €
99,5 % 3 h 39 min ~6 ~390 €
99,9 % 43 min 50 s ~1,2 ~78 €
99,95 % 21 min 55 s ~0,6 ~39 €
99,99 % 4 min 23 s ~0,1 ~8 €
99,999 % 26 s ~0 ~0 €

Con 1.200 pedidos al mes y un ticket medio de 65 €, pasar de 99,9 % a 99,99 % le ahorraría a AlpinaShop unos 70 € al mes en pedidos no perdidos — y le costaría multirregión activo-activo, es decir, varios cientos de euros mensuales más el trabajo de ingeniería.

La conclusión incómoda y correcta: para AlpinaShop, 99,99 % sería una mala inversión. Y saber decir eso con números en la mano, en lugar de perseguir nueves por inercia, es de lo más valioso que puede aportar un ingeniero.

Con dos matices que hay que hacer explícitos:

  1. No todo el tiempo vale igual. Cuarenta minutos de caída de madrugada en febrero cuestan casi nada; cuarenta minutos el viernes de la campaña de otoño pueden costar veinte veces más. La disponibilidad importa ponderada por tráfico.
  2. Hay costes que no son pedidos perdidos: reputación, clientes que no vuelven, soporte, y —en un incidente largo— el desgaste del equipo.

  1. SLI, SLO y SLA: las tres siglas que todo el mundo confunde

Sigla Qué es Naturaleza Ejemplo en AlpinaShop
SLI (Service Level Indicator) Lo que mides Un número, una proporción % de peticiones al catálogo con código < 500
SLO (Service Level Objective) El objetivo que te pones Interno, un umbral sobre el SLI 99,9 % en 28 días
SLA (Service Level Agreement) Lo que prometes por contrato Externo, con penalización 99,5 %, o se devuelve el 10 %

La relación entre los tres, que es lo que hay que interiorizar:

flowchart LR
    M["Metricas crudas<br/>Cloud Monitoring"] --> SLI["SLI<br/>buenas / validas"]
    SLI --> SLO["SLO<br/>umbral interno"]
    SLO --> EB["Presupuesto<br/>de error"]
    EB --> DEC["Decisiones:<br/>desplegar o estabilizar"]
    SLO -.->|"siempre mas exigente"| SLA["SLA<br/>compromiso externo"]

Tres reglas que evitan casi todos los errores:

  1. El SLO siempre es más exigente que el SLA. Si prometes 99,5 % y tu objetivo interno es 99,5 %, incumplirás el contrato la mitad de las veces. La práctica habitual es un margen holgado: SLA 99,5 %, SLO 99,9 %.
  2. El SLI se mide desde donde está el usuario, no desde donde es cómodo. Un servidor que responde perfectamente mientras el balanceador devuelve 502 tiene un SLI del 100 % y un usuario furioso.
  3. Un SLO sin consecuencia es un adorno. Si al incumplirlo no cambia nada, no es un objetivo: es un número en un panel.

Y una definición precisa de SLI que resuelve la mayoría de las dudas:

SLI = eventos buenos / eventos válidos

Todo lo difícil está en definir esos dos términos. ¿Cuenta como "malo" un 404? (No: el usuario pidió algo que no existe.) ¿Y un 429 del limitador de tasa? (Depende: si es un ataque, no; si es un cliente legítimo, sí.) ¿Y una petición de un robot rastreador? (Probablemente no debería ser un evento válido.) Esas decisiones son el 80 % del trabajo de definir un SLI, y hay que tomarlas por escrito.

  1. Elegir buenos SLI para AlpinaShop

Un buen SLI cumple cuatro condiciones:

Condición Significa Contraejemplo
Refleja la experiencia del usuario Si el SLI está bien, el usuario está contento CPU del servidor
Es medible con lo que ya tienes Sale de métricas existentes "Satisfacción del cliente"
Es predictivo Cuando se degrada, el negocio se resiente Número de logs
Es accionable Sabes qué hacer si baja "Salud general del sistema"

El error más común es medir lo fácil en lugar de lo importante. La CPU es facilísima de medir y no le importa a ningún cliente. Los "cuatro señales de oro" —latencia, tráfico, errores y saturación— son un buen punto de partida, pero solo las dos primeras y la tercera son candidatas a SLI; la saturación es una causa, no una experiencia.

Los cuatro SLI de AlpinaShop, elegidos recorriendo el viaje del cliente:

SLI 1 — Disponibilidad del catálogo

Eventos válidos: peticiones HTTP al balanceador con rutas de la tienda, excluyendo rastreadores conocidos. Eventos buenos: las que devuelven código < 500.

Decisiones tomadas y anotadas:

Caso ¿Válido? ¿Bueno? Razón
200 OK
404 producto inexistente El sistema funcionó correctamente
429 de Cloud Armor a un atacante No No es un usuario a quien servir
500, 502, 503 No Fallo nuestro
499 cliente cortó No Se fue el cliente, no falló el sistema
Petición de un rastreador No No es experiencia de usuario

SLI 2 — Latencia del catálogo

Eventos válidos: las mismas peticiones que en el SLI 1. Eventos buenos: las que se sirven en menos de 800 ms.

Ojo con el error clásico: un SLI de latencia no es "el p95 es menor que X". Es la proporción de peticiones más rápidas que X. La diferencia importa: un percentil no se puede combinar con un presupuesto de error, y una proporción sí.

El umbral de 800 ms no se elige por intuición: se mide la distribución actual, se comprueba dónde está el punto a partir del cual los usuarios abandonan, y se elige un valor que hoy se cumpla holgadamente pero que detecte una degradación real.

SLI 3 — Éxito del proceso de pago

El más importante de los cuatro, porque es el que toca el dinero.

Eventos válidos: intentos de pago iniciados. Eventos buenos: los que terminan en confirmación o en un rechazo legítimo de la pasarela (fondos insuficientes, tarjeta caducada).

El matiz que lo hace correcto: un rechazo de la pasarela por fondos insuficientes no es un fallo nuestro. Contarlo como error haría que el SLI dependiera de la solvencia de los clientes, no de la calidad del sistema. Lo que sí cuenta como malo: tiempos de espera, errores 5xx, excepciones y transacciones que quedan en estado indeterminado.

SLI 4 — Frescura de los datos analíticos

Distinto de los anteriores porque no es una petición: es un proceso.

Eventos válidos: ejecuciones programadas del pipeline de pedidos. Eventos buenos: las que dejan los datos en BigQuery con menos de 2 horas de retraso.

La tabla de SLI

SLI Qué mide Fuente Criticidad
Disponibilidad del catálogo ¿Se puede comprar? Logs del balanceador Crítica
Latencia del catálogo ¿Es usable? Logs del balanceador Alta
Éxito del pago ¿Se cobra? Métrica personalizada (06-04) Crítica
Frescura analítica ¿Decide bien Lucía? Métrica de Dataflow / Workflows Media

Nótese que no hay SLI de CPU, memoria, número de instancias ni tamaño de la cola. Todo eso se monitoriza —hace falta para diagnosticar— pero no es un objetivo de nivel de servicio, porque a ningún cliente le importa.

  1. Los SLO de AlpinaShop, con sus consultas reales

SLI SLO Ventana Presupuesto de error Justificación
Disponibilidad del catálogo 99,9 % 28 días móviles 40 min 19 s Equilibrio coste/valor del apartado 1
Latencia < 800 ms 99 % 28 días móviles 6 h 43 min Una petición lenta molesta; no impide comprar
Éxito del pago 99,95 % 28 días móviles 20 min 10 s Más exigente: aquí se pierde dinero directo
Frescura analítica < 2 h 99 % 28 días móviles 6 h 43 min Un retraso puntual no afecta a las decisiones

Por qué 28 días y no un mes natural: una ventana móvil de 28 días contiene exactamente cuatro semanas, así que siempre incluye el mismo número de fines de semana y de lunes. Con meses naturales, febrero y marzo no son comparables, y el presupuesto se "reinicia" el día 1, lo que crea el incentivo perverso de esperar al cambio de mes para desplegar lo arriesgado.

Las consultas reales, sobre las métricas de 06-04, en PromQL —que es el lenguaje que Cloud Monitoring admite hoy junto a MQL:

# SLI 1 - Disponibilidad del catalogo
sum(rate(loadbalancing_googleapis_com:https_request_count{
    monitored_resource="https_lb_rule",
    url_map_name="alpinashop-url-map",
    response_code_class!="500"
}[5m]))
/
sum(rate(loadbalancing_googleapis_com:https_request_count{
    monitored_resource="https_lb_rule",
    url_map_name="alpinashop-url-map"
}[5m]))
# SLI 2 - Latencia: proporcion de peticiones bajo 800 ms
sum(rate(loadbalancing_googleapis_com:https_total_latencies_bucket{
    url_map_name="alpinashop-url-map", le="800"
}[5m]))
/
sum(rate(loadbalancing_googleapis_com:https_total_latencies_count{
    url_map_name="alpinashop-url-map"
}[5m]))

Para el pago, se usa la métrica personalizada que la aplicación ya emite desde 06-04:

# En el codigo del catalogo, tras cada intento de pago
from opentelemetry import metrics

medidor = metrics.get_meter("alpinashop.pagos")
contador = medidor.create_counter(
    "pagos_intentos",
    description="Intentos de pago por resultado",
)

def registrar_pago(resultado: str, pasarela: str):
    # resultado: "confirmado" | "rechazado_legitimo" | "error_tecnico"
    contador.add(1, {"resultado": resultado, "pasarela": pasarela})
# SLI 3 - Exito del pago: confirmados + rechazos legitimos / total
sum(rate(custom_googleapis_com:pagos_intentos{
    resultado=~"confirmado|rechazado_legitimo"
}[5m]))
/
sum(rate(custom_googleapis_com:pagos_intentos[5m]))

La separación entre rechazado_legitimo y error_tecnico es una decisión de instrumentación que hay que tomar en el código, y es exactamente donde se ve si alguien ha pensado el SLI o lo ha copiado. Si la aplicación solo registra "éxito" y "fallo", el SLI de pago es irreparablemente ambiguo.

  1. Presupuesto de error: qué es y para qué sirve de verdad

Presupuesto de error = 100 % − SLO

Con un SLO del 99,9 % en 28 días, el presupuesto es del 0,1 %: 40 minutos y 19 segundos de indisponibilidad, o su equivalente en peticiones fallidas.

La tabla de "nueves" traducida a tiempo, que conviene tener a mano:

SLO Al día A la semana A los 28 días Al año
99 % 14 min 24 s 1 h 40 min 6 h 43 min 3 d 15 h
99,5 % 7 min 12 s 50 min 3 h 22 min 1 d 19 h
99,9 % 1 min 26 s 10 min 5 s 40 min 19 s 8 h 46 min
99,95 % 43 s 5 min 2 s 20 min 10 s 4 h 23 min
99,99 % 8,6 s 1 min 4 min 2 s 52 min 36 s
99,999 % 0,9 s 6 s 24 s 5 min 15 s

Para qué sirve de verdad

Aquí está la parte que transforma el concepto de curiosidad en herramienta de gestión. El presupuesto de error resuelve la tensión permanente entre desarrollo y operaciones:

Desarrollo quiere Operaciones quiere
Ritmo Desplegar rápido y a menudo Cambiar poco
Riesgo Probar cosas nuevas Estabilidad
Incentivo Funcionalidades Que no suene el teléfono

Sin un criterio objetivo, esa tensión se resuelve por autoridad, por cansancio o por quién grita más. Con presupuesto de error, se resuelve con un número:

Mientras quede presupuesto, se despliega. Cuando se agota, se para y se estabiliza.

Y esto convierte la fiabilidad en algo que desarrollo también quiere, porque un sistema frágil consume el presupuesto y le impide entregar funcionalidades. El incentivo deja de estar enfrentado.

La política de presupuesto de error de AlpinaShop

Escrita, acordada y en el README de alpinashop-infra:

Presupuesto consumido Estado Qué se puede hacer
< 50 % 🟢 Verde Todo normal. Se despliega a diario. Se puede experimentar
50-75 % 🟡 Amarillo Se despliega, pero con canario más largo y sin cambios de esquema los viernes
75-100 % 🟠 Naranja Solo correcciones y mejoras de fiabilidad. Nada de funcionalidades nuevas
> 100 % 🔴 Rojo Congelación de funcionalidades. Todo el equipo a estabilizar. Post-mortem obligatorio

Y la cláusula que hace que la política sea creíble:

Si el presupuesto se agota por un fallo de la plataforma de Google y no por nuestros cambios, se documenta y no se congela. El objetivo es corregir lo que está en nuestra mano, no castigar al equipo por lo que no controla.

Sin esa cláusula, la primera caída regional de Google convertiría la política en algo que el equipo dejaría de respetar — y una política que se ignora es peor que no tenerla.

El uso que casi nadie hace: gastar el presupuesto a propósito

El presupuesto de error no es un objetivo que minimizar. Si al final de los 28 días has consumido el 5 %, no has ganado: probablemente estabas siendo demasiado conservador y podías haber entregado más rápido, o tu SLO es demasiado laxo y no refleja lo que los usuarios notan.

Un presupuesto que nunca se toca es tan mala señal como uno que siempre se agota. El consumo sano ronda el 50-80 %.

  1. SLO en Cloud Monitoring y alertas por consumo de presupuesto

Cloud Monitoring tiene soporte nativo para SLO, y merece la pena usarlo en lugar de construirlo a mano.

Crear el SLO

gcloud monitoring slos create \
  --service=alpinashop-web-service \
  --slo-id=disponibilidad-catalogo \
  --display-name="Disponibilidad del catalogo 99.9%" \
  --goal=0.999 \
  --rolling-period=28d \
  --request-based-good-total-ratio \
  --project=alpinashop-prod

Y en Terraform, que es donde debe vivir:

resource "google_monitoring_slo" "disponibilidad_catalogo" {
  project      = var.proyecto_prod
  service      = google_monitoring_custom_service.tienda.service_id
  slo_id       = "disponibilidad-catalogo"
  display_name = "Disponibilidad del catalogo 99.9%"

  goal                = 0.999
  rolling_period_days = 28

  request_based_sli {
    good_total_ratio {
      good_service_filter = join(" AND ", [
        "metric.type=\"loadbalancing.googleapis.com/https/request_count\"",
        "resource.type=\"https_lb_rule\"",
        "resource.label.\"url_map_name\"=\"alpinashop-url-map\"",
        "metric.label.\"response_code_class\"!=\"500\"",
      ])
      total_service_filter = join(" AND ", [
        "metric.type=\"loadbalancing.googleapis.com/https/request_count\"",
        "resource.type=\"https_lb_rule\"",
        "resource.label.\"url_map_name\"=\"alpinashop-url-map\"",
      ])
    }
  }
}

Dos tipos de SLI que hay que distinguir:

Tipo Cómo cuenta Cuándo usarlo
Basado en peticiones (request_based) Proporción de peticiones buenas El habitual. Servicios con tráfico
Basado en ventanas (windows_based) Proporción de minutos "buenos" Servicios de bajo tráfico, o cuando importa la duración del corte más que el número de afectados

Para AlpinaShop, basado en peticiones. Con un matiz honesto: de madrugada, con tres peticiones por minuto, un solo error es el 33 % de ese minuto. Los SLI basados en peticiones son ruidosos con tráfico bajo, y por eso las alertas del apartado siguiente exigen ventanas largas para los umbrales suaves.

Alertas por consumo de presupuesto (burn rate)

Esta es la parte más valiosa del apartado, y sustituye a las alertas de umbral fijo de 06-04.

La tasa de consumo (burn rate) es cuántas veces más rápido de lo sostenible se está gastando el presupuesto:

  • Tasa 1 = a este ritmo, el presupuesto se agota exactamente al final de los 28 días. Es el ritmo sostenible.
  • Tasa 14,4 = se agota en 2 días.
  • Tasa 6 = se agota en poco menos de 5 días.

El problema de alertar sobre un umbral fijo de errores es conocido: si el umbral es sensible, hay falsas alarmas; si es laxo, no detecta degradaciones lentas. Las alertas de tasa de consumo resuelven las dos cosas con dos alertas complementarias.

Alerta Tasa Ventanas Consumo Acción
Rápida 14,4× 1 h y 5 min 2 % del presupuesto en 1 h 📟 Despertar a alguien
Lenta 6 h y 30 min 5 % del presupuesto en 6 h 🎫 Crear un ticket, horario laboral

Por qué dos ventanas por alerta. La ventana larga (1 h) evita falsas alarmas por un pico de treinta segundos. La ventana corta (5 min) hace que la alerta se apague rápido cuando el problema se resuelve — sin ella, una alerta con ventana de una hora seguiría sonando cincuenta minutos después de arreglado el fallo. Es un detalle pequeño con un impacto enorme en la vida del que está de guardia.

resource "google_monitoring_alert_policy" "consumo_rapido" {
  project      = var.proyecto_prod
  display_name = "SLO catalogo: consumo rapido del presupuesto (14.4x)"
  combiner     = "AND"

  conditions {
    display_name = "Tasa de consumo > 14.4 en 1h y en 5min"
    condition_threshold {
      filter = join(" AND ", [
        "select_slo_burn_rate(\"${google_monitoring_slo.disponibilidad_catalogo.name}\", \"3600s\")",
      ])
      threshold_value = 14.4
      comparison      = "COMPARISON_GT"
      duration        = "300s"
    }
  }

  notification_channels = [var.canal_movil_marta, var.canal_slack_incidentes]

  documentation {
    content = <<-EOT
      # Consumo rapido del presupuesto de error

      A este ritmo, el presupuesto de 28 dias se agota en menos de 2 dias.

      ## Primeros pasos
      1. Panel de campana: https://console.cloud.google.com/monitoring/dashboards/...
      2. Ultimo despliegue: `gcloud run revisions list --service=alpinashop-web --region=europe-west1`
      3. Si coincide con un despliegue reciente, REVERTIR PRIMERO:
         `gcloud run services update-traffic alpinashop-web --region=europe-west1 --to-revisions=REVISION_ANTERIOR=100`
      4. Errores por tipo: Error Reporting
      5. Runbook completo: alpinashop-infra/runbooks/catalogo-5xx.md
    EOT
    mime_type = "text/markdown"
  }
}

El bloque documentation es lo más importante de la política, y lo que casi todo el mundo deja vacío. Una alerta que llega al móvil a las 3 de la madrugada con el texto "Umbral superado" obliga a la persona a reconstruir el contexto medio dormida. Una que llega con los tres primeros pasos y el enlace al runbook convierte quince minutos de desorientación en dos de acción. Se desarrolla en el apartado 12.

  1. Arquitecturas de alta disponibilidad por niveles

La alta disponibilidad se construye eliminando puntos únicos de fallo, y se hace por niveles, cada uno con su precio.

Nivel Qué resiste Qué NO resiste Coste relativo
Una zona Fallo de una máquina Caída de la zona (mantenimiento, corte eléctrico)
Multizona (regional) Caída de una zona completa Caída de la región entera 1,5-2×
Multirregión Caída de una región Fallo global de la plataforma o error propio replicado 2,5-4×
Multinube Fallo global de un proveedor Errores propios 4-10×

La frecuencia relativa de cada evento es lo que debería guiar la decisión, y rara vez se mira:

Evento Frecuencia aproximada
Fallo de una VM concreta Semanal en flotas grandes
Degradación de una zona Unas pocas veces al año
Caída de una región completa Muy infrecuente, pero ocurre
Fallo global de un proveedor Extraordinario
Error humano o despliegue defectuoso La causa más común de todas

La última fila es la que cambia las prioridades: la mayoría de las caídas no las provoca la infraestructura, sino un cambio. Invertir en multirregión mientras no tienes despliegue canario ni reversión rápida es blindar la puerta y dejar la ventana abierta. AlpinaShop hizo lo correcto: primero pipeline y reversión (módulo 6 y 07-02), después arquitectura.

Componente a componente

Componente Nivel actual Cómo subir Coste extra ¿Merece la pena?
Balanceador global Multirregión por diseño ✅ Ya está
Cloud DNS Anycast global ✅ Ya está
Cloud Armor Global ✅ Ya está
Cloud CDN Global ✅ Ya está
Cloud Run Regional: reparte entre zonas solo Segundo servicio en europe-southwest1 + NEG ~+7 €/mes ⏸️ Documentado, no hecho
Cloud SQL HA regional (réplica en otra zona) Réplica de lectura en otra región ~+55 €/mes ⏸️ Evaluado, no hecho
Cloud Storage Regional Bucket birregional o multirregión ~+30 % ✅ Hecho en el bucket crítico
Firestore Multirregión ✅ Ya está
BigQuery Multirregión EU ✅ Ya está
Artifact Registry Regional Réplica Bajo ⏸️ No crítico
Secret Manager Replicación automática ✅ Ya está
VPN a Sabadell Dos túneles Segunda pasarela en Sabadell ~+30 €/mes ❌ No: la tienda no es crítica

Observación importante: una parte grande de la arquitectura de AlpinaShop ya es multirregión sin esfuerzo, porque los servicios globales de Google lo son por diseño. El balanceador, DNS, CDN, Armor, Firestore, BigQuery y Secret Manager no requieren ninguna decisión. Los dos componentes que son regionales y críticos son Cloud Run y Cloud SQL.

Cloud SQL: qué es exactamente HA regional

Merece detalle porque se paga el doble y mucha gente no sabe qué compra.

flowchart LR
    APP["Cloud Run"] --> IP["IP privada unica"]
    IP --> P["Instancia primaria<br/>europe-west1-b"]
    P <-->|"replicacion sincrona<br/>de disco"| S["Instancia en espera<br/>europe-west1-c"]
    P --> R["Replica de lectura<br/>alpinashop-pedidos-replica-informes"]
    R -.->|"solo lectura"| LU["Consultas de Lucia"]

    style S fill:#e8f0fe,stroke:#4285f4
Aspecto Detalle
Qué replica El disco, de forma síncrona. Cero pérdida de datos
Dónde Otra zona de la misma región
Conmutación Automática, típicamente 1-2 minutos
Qué ve la aplicación La misma IP. Reconecta sola si tiene reintentos
Coste El doble de la instancia
Qué no cubre Caída de la región completa; borrado accidental de datos; corrupción lógica

La última fila es la que hay que subrayar. HA regional protege contra fallos de infraestructura. No protege contra un DELETE FROM pedidos sin WHERE, que se replica al instante en la instancia en espera. Para eso están las copias de seguridad y la recuperación a un punto en el tiempo — apartado 11.

Cómo sería Cloud Run multirregión

Documentado en alpinashop-infra como diseño listo para activar:

# 1. Desplegar el mismo servicio en la segunda region
gcloud run deploy alpinashop-web \
  --image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
  --region=europe-southwest1 \
  [... mismas banderas ...]

# 2. NEG sin servidor en esa region
gcloud compute network-endpoint-groups create neg-catalogo-run-esw1 \
  --region=europe-southwest1 \
  --network-endpoint-type=serverless \
  --cloud-run-service=alpinashop-web

# 3. Anadirlo al MISMO servicio de backend
gcloud compute backend-services add-backend bs-catalogo-web \
  --global \
  --network-endpoint-group=neg-catalogo-run-esw1 \
  --network-endpoint-group-region=europe-southwest1

Tres comandos. El balanceador global envía cada petición a la región más cercana con capacidad, y si una región falla, deja de enviarle tráfico automáticamente.

Lo que impide activarlo hoy no es Cloud Run: es Cloud SQL. Con la base de datos solo en europe-west1, las instancias de Madrid tendrían que cruzar región en cada consulta —latencia y coste de tráfico— y si cae europe-west1, la base de datos cae igual. La alta disponibilidad la determina el componente menos disponible, y en AlpinaShop ese es Cloud SQL.

  1. Modos de fallo y su mitigación

Los sistemas distribuidos fallan de formas concretas y conocidas. Diseñar contra ellas es más rentable que añadir redundancia.

Dependencias y su clasificación

Lo primero es distinguir dos tipos, porque el tratamiento es opuesto:

Tipo Definición Ejemplo Si falla
Dura Sin ella el servicio no puede funcionar Cloud SQL para el catálogo Fallo del servicio
Blanda Aporta valor pero no es imprescindible El recomendador, la analítica El servicio debe seguir

El fallo de diseño más común es tratar una dependencia blanda como si fuera dura. Un recomendador que tarda 5 segundos no debe hacer que la página de producto tarde 5 segundos: debe desaparecer de la página.

Tiempos de espera

Toda llamada de red debe tener un tiempo de espera. Sin excepciones.

Un tiempo de espera ausente o demasiado largo es el mecanismo por el que un fallo pequeño se convierte en una caída total: las peticiones se acumulan esperando, se agotan los hilos, y el servicio deja de responder a todo, incluido lo que no dependía del sistema lento.

Llamada Tiempo de espera Razón
Cloud SQL, consulta normal 2 s Si tarda más, algo va mal
Cloud SQL, conexión 5 s
Pasarela de pago 10 s Es lenta por naturaleza
Recomendador (blanda) 300 ms Si no responde rápido, no se muestra
ERP de Sabadell por VPN 5 s Puede estar el enlace regular
Secret Manager 3 s Se cachea en memoria tras la primera lectura

Regla de oro: el tiempo de espera de una llamada siempre debe ser menor que el tiempo de espera del que llama. Si Cloud Run corta a los 60 s y tu consulta espera 90 s, ese código nunca se ejecuta.

Reintentos con retroceso exponencial y jitter

Reintentar está bien. Reintentar mal empeora el fallo:

# MAL: reintento inmediato. Ante un fallo, multiplica la carga sobre
# un servicio que ya esta sufriendo. Es como golpear a alguien caido.
for intento in range(5):
    try:
        return llamar()
    except Exception:
        continue

# BIEN: retroceso exponencial + jitter + limite + solo errores transitorios
import random, time

TRANSITORIOS = (503, 502, 504, 429)

def llamar_con_reintentos(fn, max_intentos=4, base=0.2, tope=8.0):
    for intento in range(max_intentos):
        try:
            return fn()
        except ErrorHTTP as e:
            if e.codigo not in TRANSITORIOS or intento == max_intentos - 1:
                raise                                   # no se reintenta lo permanente
            espera = min(tope, base * (2 ** intento))   # 0.2, 0.4, 0.8, 1.6...
            espera = espera * (0.5 + random.random())   # jitter: +/- 50%
            time.sleep(espera)

Los cuatro elementos, y por qué cada uno:

  1. Retroceso exponencial: dar tiempo a que el otro se recupere.
  2. Jitter: sin él, mil clientes que fallaron a la vez reintentan a la vez, creando una onda sincronizada que vuelve a tumbar el servicio justo cuando se estaba levantando. Es un fallo real y frecuente, y el jitter cuesta una línea.
  3. Límite de intentos: reintentar indefinidamente convierte un fallo en un ataque a tu propio sistema.
  4. Solo errores transitorios: reintentar un 400 o un 403 no lo va a arreglar nunca; solo gasta tiempo y recursos.

Interruptor de circuito

Cuando un servicio lleva fallando un rato, deja de llamarlo:

class Interruptor:
    """Cerrado = pasa. Abierto = falla rapido. Semiabierto = prueba con una."""
    def __init__(self, umbral=5, espera=30):
        self.umbral, self.espera = umbral, espera
        self.fallos, self.abierto_desde = 0, None

    def llamar(self, fn, alternativa=None):
        import time
        if self.abierto_desde:
            if time.time() - self.abierto_desde < self.espera:
                return alternativa() if alternativa else None    # falla rapido
            self.abierto_desde = None                            # semiabierto: se prueba
        try:
            r = fn()
            self.fallos = 0
            return r
        except Exception:
            self.fallos += 1
            if self.fallos >= self.umbral:
                self.abierto_desde = time.time()
            if alternativa:
                return alternativa()
            raise

El beneficio doble: el que llama deja de esperar (falla en microsegundos en vez de en segundos) y el que falla deja de recibir carga, lo que le permite recuperarse.

Degradación elegante

El principio que hace que un fallo parcial sea invisible:

La tienda sigue vendiendo aunque el recomendador falle.

@app.route("/producto/<sku>")
def producto(sku):
    p = catalogo.obtener(sku)          # dependencia DURA: si falla, error 500

    # Dependencia BLANDA: si falla, la pagina se sirve sin recomendaciones
    try:
        recomendados = interruptor_reco.llamar(
            lambda: recomendador.para(sku, timeout=0.3),
            alternativa=lambda: catalogo.mas_vendidos_de(p.categoria),   # plan B
        )
    except Exception:
        logging.warning(json.dumps({
            "severity": "WARNING", "message": "Recomendador no disponible",
            "sku": sku, "degradado": True,
        }))
        recomendados = []                                                 # plan C

    return render("producto.html", producto=p, recomendados=recomendados)

Los tres niveles son la clave del patrón: plan A el recomendador; plan B algo simple y local —los más vendidos de la categoría, que casi nadie distinguirá—; plan C nada, y la página se sirve igual. Un cliente puede comprar en los tres casos, que es lo único que importa.

Y el detalle que hace posible medirlo: el log lleva "degradado": true. Sin esa marca, el servicio funciona degradado durante semanas y nadie se entera, porque el SLI de disponibilidad sigue en verde. Una alerta sobre el porcentaje de peticiones degradadas es lo que evita que la degradación elegante se convierta en degradación silenciosa.

Limitación de carga

Cuando la demanda supera la capacidad, hay dos opciones: degradar para todos o rechazar a algunos y servir bien al resto. La segunda es casi siempre mejor.

Mecanismo Dónde Qué hace
Cloud Armor rate-based-ban Borde Corta abuso antes de llegar (03-05)
--max-instances de Cloud Run Servicio Cortafuegos de coste y de conexiones (07-02)
Cola con longitud máxima Aplicación Rechaza con 503 en lugar de acumular
Vertido de carga por prioridad Aplicación Rechaza rastreadores antes que compradores

El último es el más sofisticado y el más útil: cuando hay saturación, no todas las peticiones valen lo mismo. Un usuario en el proceso de pago vale mucho más que un rastreador indexando la página 47 del catálogo.

  1. Recuperación ante desastres: RTO, RPO y estrategias

Alta disponibilidad y recuperación ante desastres no son lo mismo, y confundirlos lleva a arquitecturas caras que no protegen de lo que de verdad pasa:

Alta disponibilidad Recuperación ante desastres
Protege de Fallos de infraestructura Desastres y errores humanos
Actuación Automática Habitualmente manual
Escala temporal Segundos o minutos Horas o días
Ejemplo Conmutación de zona en Cloud SQL Restaurar tras un borrado masivo

Un dato que reordena prioridades: la causa más frecuente de pérdida de datos no es un desastre natural ni un ataque. Es alguien ejecutando algo en el entorno equivocado. Y contra eso, la alta disponibilidad no sirve de nada: replica el error fielmente.

Las dos métricas

Métrica Pregunta Determina
RTO (Recovery Time Objective) ¿Cuánto tiempo puede estar caído? La estrategia
RPO (Recovery Point Objective) ¿Cuántos datos puedo perder? La frecuencia de copia
flowchart LR
    C1["Ultima copia<br/>03:00"] -.->|"RPO = datos perdidos"| D["DESASTRE<br/>10:15"]
    D -.->|"RTO = tiempo caido"| R["Servicio restaurado<br/>14:15"]

Con copias diarias a las 3:00 y un desastre a las 10:15, el RPO real son 7 horas y 15 minutos de datos perdidos. Para AlpinaShop, eso serían los pedidos de toda una mañana: inaceptable.

Las cuatro estrategias

Estrategia RTO RPO Coste Cómo
Copia y restauración Horas a días Horas Copias en otra región; se reconstruye
Luz piloto Decenas de min a horas Minutos €€ Núcleo mínimo encendido (BD replicada); el resto se levanta
Espera templada Minutos Segundos a min €€€ Todo desplegado a escala reducida
Activo-activo Segundos ~0 €€€€ Ambas regiones sirviendo
flowchart TB
    subgraph A["Copia y restauracion"]
      A1["Copias en otra region"] -.->|"horas"| A2["Se reconstruye todo"]
    end
    subgraph B["Luz piloto"]
      B1["Replica de BD encendida"] -.->|"minutos"| B2["Se levanta el resto"]
    end
    subgraph C["Espera templada"]
      C1["Todo desplegado, escala minima"] -.->|"minutos"| C2["Se escala"]
    end
    subgraph D["Activo-activo"]
      D1["Ambas regiones sirviendo"] -.->|"segundos"| D2["El balanceador redirige"]
    end

Cómo se elige, y no es por la tecnología: se calcula el coste de una hora de caída y se compara con el coste mensual de cada estrategia. Para AlpinaShop, una hora de caída en un día normal son unos 100 € de pedidos; en campaña, quizá 800 €. Con esos números, activo-activo —varios cientos de euros al mes, todos los meses— no se justifica.

  1. El plan de recuperación de AlpinaShop

Escrito, con responsables, y guardado también fuera de Google Cloud (porque si el desastre es de acceso a Google Cloud, el plan tiene que ser accesible).

Escenarios y respuesta

# Escenario Probabilidad RTO objetivo RPO objetivo Estrategia
1 Fallo de una zona Media 2 min 0 Automático (HA de Cloud SQL, Cloud Run multizona)
2 Despliegue defectuoso Alta 5 min 0 Reversión de revisión (07-02)
3 Borrado accidental de datos Media 1 h 5 min Recuperación a un punto en el tiempo
4 Corrupción lógica detectada tarde Baja 4 h Hasta 24 h Restaurar copia + reprocesar desde Pub/Sub
5 Caída de la región europe-west1 Baja 4 h 5 min Luz piloto en europe-southwest1
6 Ransomware / cuenta comprometida Baja 8 h 1 h Copias inmutables + reconstruir con Terraform
7 Pérdida del enlace con Sabadell Media 1 h 0 Pub/Sub retiene; la tienda sigue (DA-004)

El escenario 2 es el más probable con diferencia, y su RTO de 5 minutos ya está resuelto desde 07-02. Es un buen recordatorio de que el trabajo de fiabilidad más rentable ya estaba hecho antes de esta lección.

Configuración de copias de seguridad

resource "google_sql_database_instance" "pedidos" {
  name             = "alpinashop-pedidos"
  database_version = "POSTGRES_15"
  region           = "europe-west1"

  settings {
    tier              = "db-custom-2-7680"
    availability_type = "REGIONAL"          # HA: replica sincrona en otra zona

    backup_configuration {
      enabled                        = true
      start_time                     = "02:00"
      location                       = "eu"          # MULTIRREGION: sobrevive a la region
      point_in_time_recovery_enabled = true          # <<< lo mas importante
      transaction_log_retention_days = 7             # ventana de recuperacion puntual

      backup_retention_settings {
        retained_backups = 30
        retention_unit   = "COUNT"
      }
    }

    maintenance_window {
      day          = 2      # martes
      hour         = 4
      update_track = "stable"
    }
  }

  deletion_protection = true                 # 06-07
}

Las cuatro líneas que hacen el trabajo:

  • point_in_time_recovery_enabled = true es lo que convierte un RPO de 24 horas en un RPO de minutos. Sin ella, solo se puede restaurar al momento de la última copia; con ella, a cualquier instante dentro de la ventana de logs de transacciones.
  • location = "eu" guarda las copias fuera de la región. Copias en la misma región que muere con la región no son copias: son una ilusión.
  • transaction_log_retention_days = 7 define la ventana de recuperación puntual. Siete días cubre el caso "alguien borró algo el lunes y nos dimos cuenta el jueves".
  • deletion_protection impide el borrado accidental de la instancia entera.

Exportación de configuración

Los datos no son lo único que hay que poder reconstruir:

Qué Cómo Dónde
Infraestructura Terraform en alpinashop-infra GitHub + backend en alpinashop-terraform-estado
Secretos Secret Manager con replicación automática Gestionado por Google
Imágenes de contenedor Artifact Registry Con réplica
Políticas de IAM Export periódico de Cloud Asset Inventory (07-07) Bucket en otra región
Paneles y alertas JSON versionado (06-04) Git
Reglas de Cloud Armor Export a YAML (03-05) Git
El propio plan de DR Documento Fuera de GCP: Drive, papel, gestor de contraseñas

  1. Backups que no se han probado no existen

Aquí se paga la deuda que 07-04 marcó como crítica.

Una copia de seguridad no probada no es una copia de seguridad: es una esperanza con nombre técnico.

Los motivos por los que una restauración falla el día que hace falta, todos reales y todos frecuentes:

Motivo Cómo se descubre
La copia estaba vacía o incompleta Solo restaurando
Faltaban permisos para restaurar Solo intentándolo
El proceso tarda 6 h y el RTO era 1 h Solo cronometrándolo
El esquema restaurado no es compatible con la aplicación actual Solo arrancando la aplicación contra él
Nadie sabe hacerlo y la persona que sabía se fue Solo el día del desastre
La copia estaba en la misma región que murió El día del desastre

El ensayo de restauración de alpinashop-pedidos

Procedimiento completo, ejecutado trimestralmente por Marta, y cronometrado.

Paso 1 — Preparar y anotar la hora de inicio.

INICIO=$(date +%s)
echo "Ensayo de restauracion iniciado: $(date)"

gcloud sql backups list --instance=alpinashop-pedidos --project=alpinashop-prod \
  --format="table(id, windowStartTime, status, type, location)" --limit=5

Paso 2 — Restaurar a una instancia NUEVA.

Nunca sobre la de producción. La restauración crea una instancia aparte, lo que hace el ensayo totalmente seguro:

# Recuperacion a un punto en el tiempo: hace 2 horas
MOMENTO=$(date -u -d '2 hours ago' +%Y-%m-%dT%H:%M:%S.000Z)

gcloud sql instances clone alpinashop-pedidos alpinashop-pedidos-ensayo \
  --point-in-time="$MOMENTO" \
  --project=alpinashop-prod

Paso 3 — Verificar la integridad, que es lo que casi nadie hace.

Restaurar sin verificar solo demuestra que el comando funciona:

gcloud sql connect alpinashop-pedidos-ensayo --user=postgres --database=tienda
-- 1. Las tablas existen y tienen filas
SELECT schemaname, relname, n_live_tup
FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 10;

-- 2. El ultimo pedido es coherente con el momento restaurado
SELECT MAX(fecha_pedido) AS ultimo_pedido, COUNT(*) AS total FROM pedidos;

-- 3. Integridad referencial: no hay lineas huerfanas
SELECT COUNT(*) AS lineas_huerfanas
FROM lineas_pedido lp
LEFT JOIN pedidos p ON p.id = lp.pedido_id
WHERE p.id IS NULL;

-- 4. Comparar totales con produccion (deben cuadrar hasta el momento restaurado)
SELECT DATE(fecha_pedido) AS dia, COUNT(*) AS pedidos, SUM(importe_total) AS importe
FROM pedidos
WHERE fecha_pedido >= NOW() - INTERVAL '7 days'
GROUP BY dia ORDER BY dia;

Paso 4 — Arrancar la aplicación contra la copia restaurada.

Este es el paso que descubre los problemas de verdad:

gcloud run deploy alpinashop-web-ensayo \
  --image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
  --region=europe-west1 --no-traffic --tag=ensayo \
  --add-cloudsql-instances=alpinashop-prod:europe-west1:alpinashop-pedidos-ensayo \
  --set-env-vars=ENTORNO=ensayo

URL=$(gcloud run services describe alpinashop-web-ensayo --region=europe-west1 \
      --format='value(status.traffic[?tag==`ensayo`].url)')
curl -sf -H "Authorization: Bearer $(gcloud auth print-identity-token)" "$URL/salud"
curl -sf -H "Authorization: Bearer $(gcloud auth print-identity-token)" "$URL/producto/1"

Paso 5 — Cronometrar, limpiar y documentar.

FIN=$(date +%s)
echo "Tiempo total de restauracion: $(( (FIN - INICIO) / 60 )) minutos"

gcloud sql instances delete alpinashop-pedidos-ensayo --quiet
gcloud run services delete alpinashop-web-ensayo --region=europe-west1 --quiet

El resultado del primer ensayo real

Y aquí está el valor del ejercicio, porque el primer ensayo nunca sale bien:

Hallazgo Impacto
La restauración tardó 47 minutos, no los "unos minutos" que todos suponían El RTO de 1 hora para el escenario 3 es ajustado, no cómodo
El usuario de la aplicación no existía en la instancia clonada La aplicación no arrancó hasta crearlo a mano. Habría costado 20 minutos en un incidente real
Faltaba roles/cloudsql.admin en el grupo gcp-infra@ para clonar Marta tuvo que pedírselo a sí misma por otra vía
Las secuencias de PostgreSQL quedaron desincronizadas Los primeros inserts habrían fallado por clave duplicada
Nadie había documentado el procedimiento Marta lo reconstruyó sobre la marcha

Cinco problemas que habrían convertido una restauración de 1 hora en una de 3 horas, con la tienda parada y todo el mundo mirando. Y ninguno era detectable sin ensayar.

Las cinco correcciones se escribieron en un runbook, se automatizó la creación del usuario y el reajuste de secuencias en un script de post-restauración, y se ajustó el RTO del escenario 3 de 1 hora a 90 minutos, que es un número honesto.

El ensayo no sirve para confirmar que las copias funcionan. Sirve para encontrar los cinco problemas que impiden que funcionen.

  1. Práctica operativa: guardias, runbooks y post-mortem

Guardias sostenibles para un equipo de tres personas

La fiabilidad la sostienen personas, y un equipo quemado es el mayor riesgo de fiabilidad que existe. Con tres personas técnicas, una guardia 24×7 tradicional es inviable: serían dos semanas de cada tres de disponibilidad permanente.

El modelo realista de AlpinaShop:

Franja Cobertura Qué se atiende
L-V 8:00-20:00 Rotación semanal de una persona Todo
L-V 20:00-8:00 Solo alertas de página (tasa 14,4×) Caída total o pérdida de dinero
Fines de semana Ídem Ídem
Campaña de otoño Dos personas de guardia, con compensación acordada Todo

Y las reglas que lo hacen sostenible, que importan tanto como el calendario:

  1. Solo despierta lo que hay que arreglar ya. Si una alerta llega de madrugada y la acción es "mirarlo mañana", esa alerta no debe sonar de noche. Es la regla que más reduce el desgaste.
  2. Compensación real. Horas o dinero, acordado por escrito. La guardia no remunerada dura hasta que alguien se harta.
  3. Máximo dos noches interrumpidas seguidas. Si pasa, se releva.
  4. Toda alerta nocturna se revisa el día siguiente: ¿era necesaria? Si no, se ajusta o se elimina.
  5. Menos alertas es mejor. La fatiga de alertas (06-04) es un problema de fiabilidad: una persona que ha aprendido a ignorar el móvil ignorará también la que importaba.

Runbooks enlazados desde las alertas

Un runbook es el documento que dice qué hacer ante una alerta concreta. Vive en alpinashop-infra/runbooks/ y se enlaza desde el campo documentation de la política de alerta.

# Runbook: errores 5xx en el catálogo

**Alerta:** SLO catálogo - consumo rápido del presupuesto (14.4x)
**Gravedad:** Página (despierta)
**Última revisión:** 2026-08-05 · **Probado en simulacro:** sí

## 1. Confirmar (2 min)
- Panel de campaña: [enlace directo]
- ¿Afecta a todas las rutas o a una? Filtrar por `url_map_name` y ruta.
- Comprobación externa: `curl -sS -o /dev/null -w '%{http_code}' https://alpinashop.example/salud`

## 2. Causa más probable: un despliegue reciente (3 min)

gcloud run revisions list --service=alpinashop-web --region=europe-west1 --limit=5

Si la revisión activa se desplegó hace menos de 2 horas, **REVERTIR PRIMERO Y DIAGNOSTICAR DESPUÉS**:

gcloud run services update-traffic alpinashop-web --region=europe-west1
--to-revisions=REVISION_ANTERIOR=100

## 3. Si no fue un despliegue
| Síntoma | Causa probable | Acción |
| --- | --- | --- |
| `Memory limit exceeded` en logs | Memoria insuficiente | Subir `--memory`, revisar concurrencia |
| Errores de conexión a BD | Pool agotado o Cloud SQL degradado | Panel de Cloud SQL; conmutación |
| Latencia alta sin errores | Dependencia lenta | Cloud Trace, buscar el tramo lento |
| 502 del balanceador | Cloud Run no responde | `gcloud run services describe` |
| Solo desde una geografía | Cloud Armor | Revisar reglas recientes (03-05) |

## 4. Escalar
Si en 30 min no hay diagnóstico: avisar a Dani (backend) y abrir caso con soporte de Google.

## 5. Cerrar
- Comprobar que el SLI se recupera.
- Anotar inicio, fin y presupuesto consumido.
- **Post-mortem obligatorio si el corte superó 15 minutos.**

Las tres propiedades de un runbook útil: empieza por confirmar (no por diagnosticar), pone primero la causa más probable —un despliegue reciente, en el 70 % de los casos—, y dice explícitamente cuándo escalar, para que la persona de guardia no se quede sola dando vueltas a las 4 de la mañana.

Y una cuarta que se ve en la cabecera: "Probado en simulacro: sí". Un runbook que nadie ha ejecutado nunca es una hipótesis.

Post-mortem sin culpables

Tras un incidente significativo, un documento con estructura fija:

Sección Contenido
Resumen Qué pasó, en dos frases
Impacto Duración, usuarios afectados, presupuesto de error consumido, pedidos perdidos
Cronología Con horas: detección, diagnóstico, mitigación, resolución
Causa raíz Los "cinco porqués", hasta llegar a una causa sistémica
Qué fue bien Sí, esta sección importa
Acciones Con responsable y fecha. Concretas, no "tener más cuidado"

Sin culpables (blameless) no significa que no haya responsabilidades. Significa una cosa muy concreta:

Si la respuesta a "por qué pasó" es el nombre de una persona, el análisis se ha detenido demasiado pronto.

Ejemplo aplicado:

❌ "Dani desplegó sin probar."

✅ "Un cambio llegó a producción sin cobertura de pruebas para el caso de un carrito vacío.
   ¿Por qué? El pipeline no exige umbral de cobertura.
   ¿Por qué? Se configuró rápido en 06-01 y quedó pendiente.
   ¿Por qué no se detectó en el canario? El canario duró 10 min con tráfico bajo (07-02).
   ACCIONES:
   1. Umbral de cobertura del 70% en el pipeline (Dani, 2026-08-20)
   2. Canario mínimo de 30 min o 500 peticiones, lo que sea mayor (Marta, 2026-08-15)
   3. Prueba de regresión del carrito vacío (Dani, 2026-08-12)"

La razón práctica —no moral— de la ausencia de culpa: en una cultura donde se busca al culpable, la gente oculta los errores, y los errores ocultos no se corrigen. En una cultura sin culpa, la gente cuenta lo que pasó y el sistema mejora. Es una decisión de eficacia.

El registro de incidentes

Todos los post-mortem en un sitio, con etiquetas. Al cabo de un año permite ver patrones que ningún incidente aislado revela: "el 60 % de nuestros incidentes son despliegues" es una frase que cambia las prioridades de un equipo entero.

  1. Pruebas de caos a escala razonable

Ingeniería del caos es provocar fallos a propósito para verificar que el sistema los tolera. Netflix popularizó la idea matando instancias en producción; una pyme no necesita eso, pero sí necesita algo.

Todo sistema tiene pruebas de recuperación ante desastres. Unos las hacen a propósito; otros esperan a que ocurran.

Los experimentos de AlpinaShop, ordenados de menos a más agresivo:

# Experimento Dónde Hipótesis Frecuencia
1 Parar el recomendador dev La página se sirve con los más vendidos Mensual
2 Añadir 3 s de latencia a Cloud SQL dev El tiempo de espera corta y sale un 503 limpio, no una cascada Mensual
3 Conmutación manual de Cloud SQL prod, ventana planificada Corte < 2 min, la aplicación reconecta sola Trimestral
4 Restauración de copia prod → instancia nueva RTO < 90 min Trimestral
5 Cortar el túnel VPN a Sabadell prod La tienda sigue; Pub/Sub retiene Semestral
6 Simulacro de sala: "cae europe-west1" Papel El plan es ejecutable Anual
7 Revertir un despliegue a ciegas prod Cualquiera del equipo lo hace en < 5 min Trimestral

Las reglas que hacen esto responsable en lugar de temerario:

  1. Hipótesis escrita antes. Si no sabes qué esperas, no es un experimento: es romper cosas.
  2. Empezar en desarrollo.
  3. Radio de explosión pequeño, y ampliarlo solo cuando el anterior salió bien.
  4. Un botón de parada y la capacidad de abortar en cualquier momento.
  5. Nunca en campaña. Ni el viernes por la tarde.
  6. Avisar al equipo. El caos no anunciado con guardias reales es una broma pesada.

El experimento 3 merece un comentario, porque asusta y no debería: la conmutación de Cloud SQL va a ocurrir algún día, elegida por Google y en el peor momento. Provocarla un martes a las 10 de la mañana, con todo el mundo delante y con la posibilidad de arreglarlo, es infinitamente mejor que descubrir un viernes de campaña que la aplicación no reconecta porque le falta pool_pre_ping (07-02).

Y el experimento 7 es el más subestimado: que solo Marta sepa revertir un despliegue es un punto único de fallo humano. Que Dani y Lucía lo hayan hecho al menos una vez cada uno vale más que cualquier redundancia de infraestructura.

Errores Comunes y Consejos

  • Poner como objetivo "que no se caiga nunca". No se puede cumplir, medir ni usar para decidir. Elige un número y págalo.
  • Confundir SLO y SLA. El SLO siempre más exigente que el SLA, con margen holgado.
  • Medir el SLI desde el servidor. Se mide desde donde está el usuario: el balanceador o una comprobación externa.
  • Definir el SLI de latencia como "el p95 < X". Es "la proporción de peticiones < X". Un percentil no se combina con un presupuesto de error.
  • Contar los rechazos legítimos de la pasarela como errores. El SLI pasaría a depender de la solvencia de los clientes.
  • SLO sobre CPU o memoria. No son experiencia de usuario. Se monitorizan, no se convierten en objetivo.
  • Ventanas de mes natural. Crean el incentivo de esperar al día 1 para desplegar lo arriesgado. Ventana móvil de 28 días.
  • Un SLO sin política asociada. Si al incumplirlo no cambia nada, es un adorno.
  • Congelar por agotar el presupuesto cuando la culpa fue de la plataforma. La política pierde credibilidad y el equipo deja de respetarla.
  • Perseguir un presupuesto consumido al 5 %. Señal de exceso de conservadurismo o de un SLO demasiado laxo.
  • Alertas de umbral fijo sobre errores. O son ruidosas o no detectan degradaciones lentas. Usa tasa de consumo con dos ventanas.
  • Dejar vacío el campo documentation de la alerta. A las 3 de la madrugada, ese texto vale más que la propia alerta.
  • Llamadas de red sin tiempo de espera. Es el mecanismo por el que un fallo pequeño se convierte en caída total.
  • Reintentos sin jitter. Mil clientes reintentando a la vez tumban el servicio justo cuando se recuperaba.
  • Reintentar errores permanentes. Un 403 no mejora por insistir.
  • Tratar una dependencia blanda como dura. El recomendador no debe poder tumbar la ficha de producto.
  • Degradar sin marcarlo en los logs. El servicio funciona a medias durante semanas y el SLI sigue verde.
  • Copias en la misma región. No son copias: son una ilusión.
  • Creer que HA protege de un borrado accidental. Lo replica al instante en la instancia en espera.
  • No probar la restauración. Es la deuda más peligrosa porque es invisible hasta el día que importa.
  • Guardias sin compensación. Duran hasta que alguien se harta, y entonces te quedas sin guardias y sin persona.
  • Buscar culpables en un post-mortem. Los errores se ocultan, y los errores ocultos no se corrigen.
  • Consejo: empieza por un SLO de disponibilidad y nada más. Cuatro SLO desde el primer día es demasiado. Uno bueno vale más que cuatro copiados.
  • Consejo: la reversión rápida vale más que la redundancia. La causa más frecuente de caída es un cambio, no un fallo de hardware.
  • Consejo: que todo el equipo sepa revertir un despliegue. Es la redundancia más barata que existe.
  • Consejo: cronometra el ensayo de restauración. El número real siempre sorprende, y es el que da un RTO honesto.

Ejercicios

Ejercicio 1 — Definir el SLO de un servicio nuevo

Dani va a lanzar alpinashop-buscador (el servicio del ejercicio de 07-02). Define su SLI y su SLO completos:

  1. Qué eventos son válidos y cuáles buenos, resolviendo explícitamente al menos seis casos límite.
  2. El SLO y la ventana, justificados.
  3. El presupuesto de error resultante en minutos y en peticiones.
  4. Las dos alertas de tasa de consumo con sus umbrales.
  5. La política de qué se hace en cada estado del presupuesto.

Considera que el buscador es una dependencia blanda del catálogo: si falla, se puede mostrar un enlace a la navegación por categorías.

Ejercicio 2 — Diseñar un plan de recuperación con números

AlpinaShop lanza AlpinaPro, un servicio de suscripción para clubes de montaña: cuota mensual, contenido exclusivo y reservas de material. Datos:

  • 400 suscriptores, 25 €/mes cada uno: 10.000 €/mes de ingreso recurrente.
  • Base de datos propia alpinapro-suscripciones (PostgreSQL).
  • Integración con la pasarela para cobros recurrentes el día 1 de cada mes.
  • Los clubes reservan material con 48 h de antelación; una reserva perdida es una queja seria.
  • Dirección dice que "no se puede caer nunca", pero el presupuesto adicional es de 150 €/mes.

Diseña: los SLI y SLO del servicio, el RTO y RPO justificados con números de negocio, la estrategia de recuperación elegida dentro del presupuesto, y la configuración concreta de copias. Explica qué le respondes a dirección sobre el "no se puede caer nunca".

Ejercicio 3 — Analizar un incidente y escribir el post-mortem

Los hechos. Martes, 18:42. Salta la alerta de consumo rápido. Cronología reconstruida:

  • 18:30 — Cloud Build despliega la revisión alpinashop-web-00051 con canario al 10 %. Las pruebas de humo pasan.
  • 18:38 — Marta aprueba el paso al 100 % (llevaba prisa, se iba a las 18:45).
  • 18:42 — Alerta: tasa de consumo 22×. Errores 500 en el 35 % de las peticiones.
  • 18:44 — Marta ve la alerta ya en el coche. No puede actuar.
  • 18:51 — Dani, avisado por Marta, se conecta y revierte a la revisión 00050.
  • 18:53 — Los errores desaparecen.
  • Impacto: 11 minutos, ~230 peticiones fallidas, 4 pedidos perdidos, 27 % del presupuesto mensual consumido.
  • Causa técnica: la revisión nueva leía una variable de entorno TARIFA_ENVIO que solo estaba definida en alpinashop-dev.

Escribe el post-mortem completo siguiendo la estructura del apartado 12, con los cinco porqués llevados hasta una causa sistémica, y al menos cinco acciones concretas con responsable. Señala también qué hizo bien el equipo.

Soluciones

Solución 1 — Definir el SLO de alpinashop-buscador

1. Definición del SLI.

SLI de disponibilidad = peticiones de búsqueda con respuesta útil / peticiones de búsqueda válidas

Casos límite, resueltos y anotados:

Caso ¿Válido? ¿Bueno? Razón
200 con 12 resultados
200 con 0 resultados El sistema funcionó; simplemente no hay "piolet rosa fucsia"
400 consulta malformada El sistema rechazó correctamente una entrada inválida
500 excepción interna No Fallo nuestro
503 Elasticsearch caído No Es nuestra dependencia; el usuario no distingue
429 límite de tasa a un rastreador No No es un usuario a quien servir
Petición con User-Agent de bot No No es experiencia de usuario
Tiempo de espera agotado (> 3 s) No Peor que un error: el usuario espera y no recibe nada
Petición de la sonda de salud No No es tráfico real

El caso "0 resultados" es el que más discusión genera y es importante acertarlo. Contarlo como malo haría que el SLI midiera la calidad del catálogo en lugar de la fiabilidad del servicio, y bajaría cada vez que alguien busca algo que no vendemos. La calidad de la búsqueda se mide aparte, con métricas de producto —tasa de búsquedas sin resultado, tasa de clic— que no son un SLO.

SLI de latencia = búsquedas servidas en < 500 ms / búsquedas válidas

500 ms y no 800 ms como el catálogo, porque un buscador es interactivo: el usuario escribe y espera. La percepción de lentitud aparece antes.

2. SLO y ventana.

SLI SLO Ventana Justificación
Disponibilidad 99,5 % 28 días Menos exigente que el catálogo (99,9 %)
Latencia < 500 ms 99 % 28 días Coherente con el catálogo

La justificación del 99,5 % es la parte interesante del ejercicio. El buscador es una dependencia blanda: si falla, el catálogo muestra un enlace a la navegación por categorías y el cliente puede seguir comprando. Un servicio blando no necesita el mismo objetivo que uno crítico, y ponerle 99,9 % significaría gastar dinero e ingeniería en una fiabilidad que no cambia el resultado de negocio.

Este es el razonamiento que hay que llevarse: el SLO se deriva del impacto de negocio del fallo, no del cariño que se le tenga al servicio.

3. Presupuesto de error.

Con 99,5 % sobre 28 días: 0,5 % = 3 h 21 min 36 s.

En peticiones: si el buscador recibe unas 150.000 búsquedas válidas en 28 días, el presupuesto son 750 búsquedas fallidas.

Es cómodo, y a propósito: permite desplegar mejoras del buscador con frecuencia sin que un fallo puntual bloquee al equipo.

4. Las dos alertas.

Alerta Tasa Ventanas Consumo en la ventana Canal
Rápida 14,4× 1 h y 5 min 2 % (≈ 4 min) 🎫 Ticket, no página
Lenta 6 h y 30 min 5 % (≈ 10 min) 🎫 Ticket

Ninguna de las dos despierta a nadie, y esa es la consecuencia lógica de ser una dependencia blanda con SLO del 99,5 %. Levantarse a las 4 de la mañana porque el buscador falla, cuando la tienda sigue vendiendo, es exactamente el tipo de alerta que produce fatiga y hace que se ignore la que sí importa.

La excepción que hay que prever: si el buscador cae y además el catálogo empieza a fallar —porque el tiempo de espera no cortaba y se agotaron los hilos—, la alerta que sonará es la del catálogo, que sí es de página. El diseño de alertas debe reflejar el diseño de dependencias.

5. Política de presupuesto.

Consumido Estado Acción
< 60 % 🟢 Despliegue normal
60-90 % 🟡 Se sigue desplegando; se revisa la causa del consumo en la reunión semanal
> 90 % 🟠 Solo correcciones. Revisar si la dependencia de Elasticsearch necesita más fiabilidad
> 100 % 🔴 Evaluar si el interruptor de circuito del catálogo funciona bien, que es lo realmente importante: que el fallo del buscador no arrastre a la tienda

La fila roja es la clave del ejercicio. Cuando un servicio blando agota su presupuesto, la pregunta prioritaria no es "cómo lo hago más fiable" sino "¿está bien aislado?". Un buscador que falla el 2 % del tiempo sin afectar a nadie es un problema menor; un buscador que falla el 0,5 % del tiempo y cada fallo degrada la ficha de producto es un problema grave.

Solución 2 — Diseñar un plan de recuperación para AlpinaPro

Punto de partida: traducir el negocio a números.

Concepto Cálculo Valor
Ingreso recurrente mensual 400 × 25 € 10.000 €
Ingreso por hora (prorrateado) 10.000 / 730 ~13,70 €/h
Coste real de una hora de caída Ver abajo Muy superior a 13,70 €

Y aquí está el matiz que distingue un análisis bueno de uno superficial: en un servicio de suscripción, el ingreso no se pierde por estar caído. Los 400 suscriptores pagan igual. Lo que se pierde es otra cosa, y es peor:

Impacto real Estimación
Reservas de material perdidas Una queja seria por reserva; riesgo de baja
Bajas por mala experiencia Una baja son 25 €/mes × meses restantes de vida del cliente. Con 24 meses de vida media, una baja son 600 €
Cobros recurrentes fallidos el día 1 Crítico: si el sistema cae el día 1, no se cobra a nadie
Reputación en el nicho Los clubes de montaña se conocen entre sí

Conclusión: una caída de 4 horas que provoque 5 bajas cuesta 3.000 €, no 55 €. El análisis por ingreso prorrateado subestima el impacto en más de un orden de magnitud, y es un error muy común en servicios de suscripción.

SLI y SLO propuestos.

SLI SLO Justificación
Disponibilidad del portal 99,5 % 3 h 22 min al mes. Los clubes no consultan a diario
Éxito de reservas 99,9 % Una reserva perdida es una queja. Más exigente
Éxito del cobro recurrente 99,99 % Crítico y concentrado: falla el día 1 o no falla

El tercero es el más interesante y el que se suele olvidar. El cobro recurrente no es un servicio continuo: es un proceso que se ejecuta un día al mes. Un SLO mensual sobre él no tiene sentido; lo que hace falta es un objetivo distinto: "el 99,99 % de los cobros programados se ejecutan correctamente dentro de las 24 horas siguientes", con reintentos. Y un tratamiento arquitectónico específico: cola con reintentos y DLQ, no una ejecución única que o funciona o se pierde.

RTO y RPO justificados.

Escenario RTO RPO Justificación con números
Fallo de zona 5 min 0 Automático, no cuesta decidirlo
Despliegue defectuoso 5 min 0 Reversión de revisión
Borrado de datos 2 h 5 min Perder reservas es inaceptable. 5 min ≈ 0 reservas perdidas
Caída de región 6 h 15 min Baja probabilidad; 6 h de caída ≈ 3-5 bajas ≈ 2.400 €
Fallo el día 1 (cobros) 2 h 0 Se reintenta; el proceso debe ser idempotente

Estrategia dentro de 150 €/mes.

Presupuesto muy ajustado. Reparto:

Medida Coste/mes Qué cubre
Cloud SQL HA regional ~60 € Fallo de zona, automático, RPO 0
Recuperación a un punto en el tiempo + copias multirregión ~15 € Borrado accidental con RPO de 5 min; supervivencia a la región
Réplica de lectura en europe-southwest1 ~55 € Luz piloto: promocionable en minutos si cae la región
Uptime checks + alertas ~5 € Detección
Cloud Run (escala a cero) ~5 € Cómputo
Total ~140 €

Estrategia elegida: luz piloto. La base de datos replicada en la segunda región es lo único que se mantiene encendido; el cómputo en Cloud Run se levanta con tres comandos (apartado 7) porque la imagen ya existe y Cloud Run escala desde cero. RTO estimado: 30-60 minutos, mejor que el objetivo de 6 h.

Lo que no se hace y por qué: activo-activo costaría 400-600 €/mes para cubrir un evento de probabilidad muy baja, cuando el presupuesto entero son 150 €.

Configuración de copias.

resource "google_sql_database_instance" "alpinapro" {
  name             = "alpinapro-suscripciones"
  database_version = "POSTGRES_15"
  region           = "europe-west1"

  settings {
    tier              = "db-custom-1-3840"
    availability_type = "REGIONAL"

    backup_configuration {
      enabled                        = true
      start_time                     = "03:00"
      location                       = "eu"
      point_in_time_recovery_enabled = true
      transaction_log_retention_days = 7
      backup_retention_settings {
        retained_backups = 30
        retention_unit   = "COUNT"
      }
    }

    # Ventana de mantenimiento: NUNCA cerca del dia 1
    maintenance_window {
      day          = 3      # miercoles
      hour         = 4
      update_track = "stable"
    }
  }

  deletion_protection = true
}

resource "google_sql_database_instance" "alpinapro_replica_dr" {
  name                 = "alpinapro-suscripciones-dr-esw1"
  master_instance_name = google_sql_database_instance.alpinapro.name
  region               = "europe-southwest1"          # OTRA region
  database_version     = "POSTGRES_15"

  replica_configuration { failover_target = false }

  settings {
    tier              = "db-custom-1-3840"
    availability_type = "ZONAL"        # la replica de DR no necesita HA propia
  }
}

Dos decisiones a señalar: la ventana de mantenimiento el miércoles, deliberadamente lejos del día 1 —un mantenimiento de Google durante el ciclo de cobros sería el peor momento posible—, y la réplica de DR en modo ZONAL, porque pagar HA para una réplica que solo se usa en un desastre es duplicar el coste de un seguro.

Qué se le responde a dirección.

"No podemos garantizar que no se caiga nunca, y quien lo prometa no está diciendo la verdad. Lo que sí podemos hacer es decidir cuánto estamos dispuestos a que se caiga y pagarlo.

Con los 150 € al mes que tenemos, el compromiso es este: si falla una máquina o un centro de datos concreto, se recupera solo en cinco minutos y sin perder ningún dato. Si alguien borra algo por error, lo recuperamos en dos horas perdiendo como mucho cinco minutos de reservas. Y si cayera una región entera de Google —algo muy poco frecuente pero posible—, estaríamos funcionando en menos de una hora desde Madrid.

Para el proceso de cobro del día 1 tenemos un tratamiento aparte, porque es el momento crítico del mes: si algo falla, se reintenta automáticamente durante 24 horas y ningún cobro se pierde.

Lo que no cubrimos con este presupuesto es la caída simultánea de dos regiones o un problema global de Google. Cubrirlo costaría entre tres y cuatro veces más, todos los meses, para un escenario que quizá no ocurra nunca.

Y hay algo que quiero pedir además del presupuesto: probar todo esto cada trimestre. Un plan de recuperación que no se ensaya no funciona el día que hace falta, y lo sabemos porque nos pasó con la tienda: la primera vez que probamos restaurar la base de datos encontramos cinco problemas que habrían triplicado el tiempo de recuperación."

Solución 3 — Post-mortem del incidente del 18:42


Post-mortem: errores 500 en el catálogo tras el despliegue 00051

Fecha del incidente: martes 2026-08-04, 18:42-18:53 Autor: Marta Ruiz · Revisado por: Dani, Lucía Estado: acciones en curso · Clasificación: sin culpables

Resumen. Un despliegue promocionado al 100 % del tráfico contenía código que dependía de una variable de entorno inexistente en producción, provocando errores 500 en el 35 % de las peticiones durante 11 minutos.

Impacto.

Métrica Valor
Duración 11 min (18:42 – 18:53)
Peticiones fallidas ~230
Pedidos perdidos 4 (~260 € de ingreso)
Presupuesto de error consumido 27 % del mensual en 11 minutos
Estado del SLO tras el incidente 🟡 Amarillo (58 % consumido)

Cronología.

Hora Evento
18:30 Cloud Build despliega 00051, canario al 10 %. Pruebas de humo ✅
18:38 Marta aprueba el 100 %. Sale de la oficina a las 18:45
18:42 Alerta de consumo rápido (22×). 35 % de errores
18:44 Marta ve la alerta en el coche. No puede actuar
18:47 Marta llama a Dani
18:51 Dani revierte a 00050
18:53 Errores a cero. Detección: 4 min. Mitigación: 11 min

Causa raíz — los cinco porqués.

  1. ¿Por qué fallaron las peticiones? El código leía os.environ["TARIFA_ENVIO"], que no existía en producción, lanzando KeyError.
  2. ¿Por qué no existía? Se añadió a alpinashop-dev durante el desarrollo y no se propagó a producción.
  3. ¿Por qué no se propagó? Las variables de entorno de producción se gestionan en Terraform, pero el desarrollo las configura con gcloud a mano. No hay un mecanismo que garantice la paridad.
  4. ¿Por qué no lo detectó el canario? Las pruebas de humo solo comprueban /salud y /producto/1, y ninguna de esas rutas usa TARIFA_ENVIO (solo la del carrito). Además el canario duró 8 minutos con tráfico bajo.
  5. ¿Por qué se promocionó al 100 % con esa cobertura? Porque el proceso permite aprobar manualmente sin ninguna comprobación automática de la revisión candidata, y la aprobación se dio con prisa al final de la jornada.

Causa raíz sistémica: no existe verificación automática de que una revisión candidata tenga toda su configuración presente en el entorno de destino, ni una comprobación objetiva que deba pasar antes de la promoción al 100 %. La aprobación manual es el único control y depende del estado y la disponibilidad de una persona.

Qué fue bien — y esta sección importa tanto como la anterior:

  • La alerta funcionó: 4 minutos desde el primer error hasta la notificación. Exactamente lo que 06-04 y las alertas de tasa de consumo prometían.
  • La reversión fue trivial: un comando, 2 minutos, sin reconstruir nada. El trabajo de 07-02 se pagó solo en este incidente.
  • La escalada funcionó: Marta, sin poder actuar, avisó a Dani en 3 minutos en lugar de intentar arreglarlo conduciendo.
  • El presupuesto de error hizo su trabajo: convirtió "11 minutos, no fue para tanto" en "el 27 % del presupuesto mensual", que es un dato que obliga a actuar.
  • Nadie buscó culpables durante el incidente. Se arregló primero y se analizó después.

Acciones.

# Acción Responsable Fecha Previene
1 Todas las variables de entorno de todos los entornos gestionadas en Terraform. Prohibido gcloud run services update --set-env-vars fuera del pipeline Marta 2026-08-15 La divergencia entre entornos (porqués 2 y 3)
2 Paso de compilación que compara las variables de entorno declaradas en el código con las definidas en el entorno destino y falla si falta alguna Dani 2026-08-20 La causa directa
3 Pruebas de humo ampliadas: /salud, /producto/1, /carrito, /api/categorias, cubriendo toda ruta que lea configuración Dani 2026-08-12 El porqué 4
4 Canario mínimo de 30 minutos o 500 peticiones, lo que sea mayor, con verificación automática de tasa de error antes de habilitar la aprobación Marta 2026-08-18 El porqué 5
5 Ventana de despliegue: no se promociona a producción después de las 17:00 ni los viernes por la tarde Equipo Inmediato Aprobar con prisa al final del día
6 Que Lucía sepa revertir un despliegue. Práctica en la próxima prueba de caos Marta 2026-09-01 El punto único de fallo humano
7 Añadir este caso al registro de incidentes con la etiqueta configuracion-divergente Marta Inmediato Ver patrones

Nota sobre la acción 5. Podría parecer una regla burocrática que ralentiza al equipo, y merece justificarse: no se prohíbe desplegar por la tarde —el canario puede correr toda la noche al 10 %— sino promocionar al 100 % cuando no queda nadie disponible para reaccionar. La restricción no es sobre el riesgo del cambio, sino sobre la capacidad de respuesta. Esa distinción es la que hace que el equipo acepte la regla en lugar de saltársela.

Lo que este incidente NO fue. No fue un error de Dani por escribir código que lee una variable, ni de Marta por aprobar con prisa. Fue un sistema que permitía promocionar al 100 % del tráfico un cambio cuya configuración nadie había verificado, en un momento en el que nadie podía reaccionar. Cualquiera de los tres habría hecho lo mismo, y por eso las siete acciones son sobre el sistema y ninguna sobre las personas.


Conclusión

AlpinaShop ha dejado de aspirar a "que no se caiga nunca" y ha empezado a gestionar la fiabilidad con números.

Sabes que cada nueve multiplica el coste y has hecho la cuenta que casi nadie hace: pasar de 99,9 % a 99,99 % le ahorraría a AlpinaShop unos 70 € al mes en pedidos y le costaría varios cientos. Decir "99,99 % sería una mala inversión" con la tabla delante es una de las aportaciones más valiosas de un ingeniero, y requiere haber calculado también los dos matices: que no todo el tiempo caído vale igual y que hay costes que no son pedidos perdidos.

Distingues SLI, SLO y SLA con precisión, con las tres reglas que evitan casi todos los errores —el SLO siempre más exigente que el SLA, el SLI medido desde donde está el usuario, y un SLO sin consecuencia no es un objetivo— y con la definición operativa que resuelve las dudas: eventos buenos / eventos válidos, donde el trabajo de verdad está en decidir qué es cada cosa.

Has elegido cuatro SLI recorriendo el viaje del cliente —disponibilidad, latencia, éxito del pago y frescura analítica— resolviendo por escrito los casos límite: el 404 es bueno, el 429 a un atacante ni siquiera es válido, y un rechazo por fondos insuficientes no es un fallo nuestro, decisión que hay que tomar en el código instrumentando rechazado_legitimo frente a error_tecnico. Y sabes por qué no hay SLI de CPU: porque a ningún cliente le importa.

Dominas el presupuesto de error: la tabla de nueves traducida a minutos, la ventana móvil de 28 días que evita el incentivo perverso del mes natural, y sobre todo para qué sirve de verdad — resolver con un número la tensión entre desplegar rápido y no romper nada, con una política de cuatro estados y la cláusula de credibilidad de no congelar cuando el fallo fue de la plataforma. Con el uso que casi nadie hace: un presupuesto consumido al 5 % es tan mala señal como uno agotado.

Sabes crear SLO en Cloud Monitoring desde Terraform, distinguir SLI basados en peticiones de basados en ventanas, y montar alertas de tasa de consumo con dos umbrales y dos ventanas cada uno — la larga para evitar falsas alarmas, la corta para que la alerta se apague cuando el problema se arregla. Y sabes que el campo documentation, que casi todo el mundo deja vacío, vale más a las 3 de la madrugada que la alerta misma.

Tienes las arquitecturas de alta disponibilidad por niveles con la tabla componente a componente de AlpinaShop, la observación de que buena parte de la plataforma ya es multirregión sin esfuerzo porque los servicios globales de Google lo son, y las dos piezas que sí son regionales y críticas. Sabes exactamente qué compras con la HA de Cloud SQL —replicación síncrona de disco, otra zona, conmutación en 1-2 minutos— y qué no cubre: un DELETE sin WHERE se replica al instante. Y sabes que Cloud Run multirregión son tres comandos, pero que no se activa porque la disponibilidad la determina el componente menos disponible.

Diseñas contra los modos de fallo: dependencias duras y blandas —con el fallo de diseño más común, tratar una blanda como dura—, tiempos de espera en toda llamada de red con la regla de que el interno siempre sea menor que el externo, reintentos con retroceso exponencial y jitter para no crear ondas sincronizadas, interruptores de circuito, degradación elegante en tres niveles con la marca "degradado": true sin la cual la degradación se vuelve silenciosa, y limitación de carga con vertido por prioridad.

Sabes que alta disponibilidad y recuperación ante desastres no son lo mismo, y el dato que reordena prioridades: la causa más frecuente de pérdida de datos es alguien ejecutando algo en el entorno equivocado, y contra eso la HA no sirve porque replica el error. Manejas RTO y RPO y las cuatro estrategias con su coste, eligiendo por comparación con el coste real de una hora de caída.

Tienes el plan de AlpinaShop con siete escenarios —donde el más probable, un despliegue defectuoso, ya estaba resuelto desde 07-02—, la configuración de copias con las cuatro líneas que hacen el trabajo (point_in_time_recovery, location = "eu", retención de logs y deletion_protection) y la exportación de configuración, incluido el plan de DR guardado fuera de Google Cloud.

Y has pagado la deuda crítica de 07-04: el ensayo de restauración, cronometrado, con verificación de integridad y con la aplicación arrancada contra la copia. Con el resultado que importa: cinco problemas encontrados —47 minutos en lugar de "unos minutos", el usuario de la aplicación inexistente, permisos que faltaban, secuencias desincronizadas y ningún procedimiento escrito— que habrían triplicado el tiempo de recuperación real. El ensayo no confirma que las copias funcionan: encuentra los cinco problemas que impiden que funcionen.

Por último, tienes la práctica operativa: guardias sostenibles para tres personas con la regla que más desgaste evita —si la acción es "mirarlo mañana", esa alerta no suena de noche—, runbooks enlazados desde las alertas que empiezan por la causa más probable y dicen cuándo escalar, post-mortem sin culpables con la razón práctica y no moral de esa cultura, y pruebas de caos a escala razonable con sus seis reglas, donde el experimento más subestimado es que todo el equipo sepa revertir un despliegue.

Todo lo de esta lección funciona porque hay tres personas que lo sostienen. Y ahí está el límite: los SLO viven en un panel que alguien mantiene, la política de presupuesto de error se respeta porque el equipo la acordó, los runbooks se actualizan porque Marta se acuerda, y nada impide técnicamente que mañana alguien cree una VM con IP pública en una región de Estados Unidos, o desactive un log de auditoría, o borre por error un proyecto entero.

Eso ya no es fiabilidad: es gobierno. Y es lo único que queda por construir. La organización de AlpinaShop tiene hoy cinco proyectos, tres carpetas, decenas de cuentas de servicio, cuatro repositorios, un perímetro a medias y ninguna política que gobierne el conjunto — más una lista de deudas de 07-04 que empieza por "políticas de organización sin aplicar" y "logs de auditoría sin retención inmutable".

La última lección del módulo cierra el círculo: estructura de la organización, políticas que se heredan y se aplican solas, inventario de todo lo que existe, auditoría de verdad con retención inmutable, y zonas de aterrizaje desplegadas con Terraform — para que lo que se ha construido siguiendo a AlpinaShop no dependa de que tres personas se acuerden.

Curso de Google Cloud Platform (GCP)

Módulo 1: Introducción a Google Cloud Platform

Módulo 2: Servicios principales de GCP

Módulo 3: Redes y seguridad

Módulo 4: Datos y análisis

Módulo 5: Aprendizaje automático e IA

Módulo 6: DevOps y monitoreo

Módulo 7: Temas avanzados de GCP

Módulo 8: Proyecto final

© Copyright 2026. Todos los derechos reservados