Saltar al contenido
Volver al archivo
Ingeniería6 min de lecturaPaso 7 de 8

Los cinco patrones de resiliencia que evitan la caída en cascada

Cómo una dependencia secundaria lenta derriba el sistema entero en noventa segundos, y los cinco patrones que lo impiden.

· Gabriel Dias
resilienciaarquitecturasrebackend

El incidente tiene siempre la misma forma.

Una dependencia secundaria se pone lenta. No se cae, se pone lenta. Cada petición que la llama retiene un hilo esperando. Los hilos se acaban. Ahora no queda hilo ni para el flujo principal, que no tenía nada que ver con el problema.

Noventa segundos entre "la recomendación está lenta" y "el checkout está caído".

Lo que separa un sistema que falla bien de uno que falla mal son cinco patrones. Ninguno es complicado. Todos son fáciles de olvidar.

  1. Timeoutel más olvidadoUn poco por encima del p99 de la dependencia. Sin él, la llamada dura para siempre.
  2. Reintento con backoff y jittersolo lo seguroSin jitter, mil clientes vuelven juntos y creaste una ola.
  3. Circuit breakerfalla rápidoDependencia muerta: rechaza al instante y usa el plan B.
  4. Bulkheadpool separadoUna funcionalidad secundaria muere dentro de su propio pool.
  5. Descarte de cargapor prioridadMejor atender bien al ochenta por ciento que mal al cien.
Ninguno es complicado. Todos son fáciles de olvidar.

Patrón 1: Timeout

Es el más barato y el más olvidado. Llamada sin timeout es llamada que puede durar para siempre, reteniendo un hilo y una conexión.

Y el valor por defecto de muchas bibliotecas es treinta segundos, o infinito.

Cómo elegir el valor: mira el p99 de la dependencia y ponlo un poco por encima. Si el p99 es doscientos milisegundos, un timeout de un segundo es generoso. Un timeout muy por encima del p99 no protege a nadie; solo aplaza el problema.

La jerarquía tiene que tener sentido: el timeout del llamador tiene que ser mayor que el del llamado, si no el de dentro nunca dispara y pierdes la información de qué capa falló.

Suma el camino entero y compáralo con el plazo que el usuario tolera. Si la suma de los timeouts del camino crítico pasa de diez segundos y tu usuario se rinde a los tres, tu configuración es ficción.

Patrón 2: Reintento con backoff y jitter

El reintento parece inofensivo y es una de las formas más eficientes de derribar tu propio sistema.

Reintenta solo lo que es seguro reintentar. GET y DELETE, tranquilo. Un POST que crea un pedido, solo con clave de idempotencia. Sin ella, así es como duplicas un cobro.

Backoff exponencial: espera 100ms, después 200, después 400. Sin eso martilleas un servicio que ya está sufriendo.

El jitter es la parte que casi todo el mundo olvida, y es la más importante. Si mil clientes fallaron en el mismo segundo y todos esperan exactamente el mismo intervalo, vuelven todos juntos. Acabas de crear una ola sincronizada.

Jitter es aleatorizar: espera un valor al azar entre cero y el intervalo calculado. Una línea de código, y es la diferencia entre recuperarse y quedar atrapado en un ciclo de tormenta.

Máximo pequeño. Tres intentos.

Patrón 3: Circuit breaker

Un interruptor automático. Cuenta los fallos de una dependencia; al pasar de un límite, se abre y pasa a rechazar al instante, sin siquiera intentarlo. Después de un intervalo entra en estado medio abierto y deja pasar una petición de prueba. Si funciona, se cierra; si no, se abre de nuevo.

Por qué es esencial: cuando una dependencia está muerta, insistir solo empeora las cosas. Retienes recursos tuyos esperando algo que no va a responder.

Fallar rápido te devuelve tus hilos y permite usar el plan B: caché viejo, respuesta parcial, mensaje honesto.

Los parámetros que importan: el límite de fallos debe ser por tasa, no por conteo absoluto, para no abrirse por diez errores en un millón de peticiones. Y el tiempo en abierto debe ser lo bastante corto para recuperarse rápido cuando la dependencia vuelva.

El error común: circuit breaker sin plan B. Se abre, rechaza, y el usuario recibe un error igualmente, solo que más rápido. Eso sigue siendo mejor que quedarse colgado, pero la mitad del valor está en el fallback.

Patrón 4: Bulkhead

El nombre viene de los compartimentos estancos del barco. Una parte se inunda y el barco sigue flotando.

La idea: separa los pools de recursos por dependencia. Si la integración con el servicio de recomendación tiene su propio pool de diez conexiones y diez hilos, puede morir entera dentro de esos diez espacios, sin consumir los recursos que el checkout necesita.

Sin bulkhead, una funcionalidad secundaria lenta consume todos los hilos y derriba la principal. Ese es literalmente el mecanismo detrás de la mayoría de las caídas en cascada.

Cómo implementarlo: pool de hilos dedicado por dependencia, o un semáforo limitando la concurrencia por llamada externa. La segunda es más ligera y resuelve la mayoría de los casos.

Cómo probarlo: pon un proxy delante de una dependencia secundaria y añade treinta segundos de latencia artificial. Si el servicio entero se cae, no tienes bulkhead, y acabas de descubrirlo en un entorno controlado.

Patrón 5: Descarte de carga y degradación elegante

Cuando estás más allá de la capacidad, rechaza explícitamente con un 503, en vez de aceptarlo todo y degradar para todo el mundo.

Es contraintuitivo y es correcto: mejor atender bien al ochenta por ciento que atender mal al cien por cien. Un sistema que lo acepta todo y tarda cada vez más entra en colapso y no vuelve solo.

Y el descarte debe ser por prioridad: tira el informe antes que el pago. Eso exige que tus peticiones lleven alguna noción de importancia. Eso es una decisión de producto.

Junto a eso viene la degradación elegante: diseña antes qué funcionalidad puede desaparecer. La búsqueda por sinónimo cae y la búsqueda simple sigue. La recomendación se convierte en una lista fija. El envío calculado se convierte en estimación.

Eso es una decisión que se toma en tiempo de paz, no durante el incidente. Y es lo que más diferencia a un equipo maduro de un equipo que apaga incendios.

La checklist

Lista todas las llamadas externas que hace tu servicio. Para cada una:

  1. ¿Tiene timeout? ¿Qué valor, y de dónde salió?
  2. ¿Tiene reintento? ¿Es seguro? ¿Tiene backoff con jitter? ¿Está en un solo nivel?
  3. ¿Tiene circuit breaker? ¿Tiene plan B cuando se abre?
  4. ¿Tiene pool separado, o lo comparte con el camino crítico?
  5. ¿Qué ve el usuario cuando está caída?

Apuesto a que por lo menos una de esas llamadas está sin timeout o con el valor por defecto de treinta segundos de la biblioteca.

Lee esto después

Háblame

¿Dudas sobre el artículo? Escríbeme por WhatsApp

Sin formulario y sin lista de correo. Si no estás de acuerdo con algo que escribí, o quieres contarme cómo lo resolviste, la conversación es directa conmigo.

Abrir conversación