Lección 5: Manejo de errores y estrategias de reintento
Objetivos de aprendizaje:
- Distinguir los errores transitorios de los permanentes
- Dominar las estrategias de reintento y los algoritmos de retroceso
- Aprender a diseñar acciones de compensación y mecanismos de reversión
Requisitos: Lección 4: Gestión de estado y paso de contexto | Siguiente: Lección 6 >>
Los errores son la norma en los flujos de trabajo
Tu flujo de trabajo corre perfecto diez veces. En la undécima, en el paso 8, la API devuelve un 503. El flujo de trabajo se cae.
Entonces agregas un try-catch, atrapas el error, lo registras y sigues adelante. En la duodécima corrida, la conexión a la base de datos se agota por tiempo. El flujo de trabajo continúa, pero la escritura falló, y ahora tus datos quedaron inconsistentes.
El manejo de errores no es tan simple como «agregar un try-catch».
En un flujo de trabajo, el manejo de errores tiene que responder tres preguntas:1
- ¿Este error es temporal o permanente? (fluctuación de red contra permisos faltantes)
- ¿Deberías reintentar, saltar o abortar? (un reintento podría arreglarlo contra un reintento lo empeora)
- Si abortas, ¿cómo limpias los pasos que ya terminaron? (revertir la base de datos contra enviar un aviso de cancelación)
Si el paso que falló es opcional (digamos, enviar una notificación), sáltalo y sigue. Dejar que una falla no crítica no descarrile toda la corrida se llama degradación elegante. Pero si el paso que falló es crítico, saltarlo deja un estado inconsistente, así que deberías abortar en su lugar.
Sin respuestas, tu flujo de trabajo es o demasiado frágil (un error chico lo tumba) o demasiado peligroso (ignora los errores y sigue corriendo, dejando atrás un estado inconsistente).2
Clasificar los errores: transitorios contra permanentes
Los errores transitorios son temporales; un reintento podría tener éxito.3
Errores transitorios comunes:
- Tiempos de espera de red agotados
- Servicio temporalmente no disponible (503 Service Unavailable)
- Límite de tasa (429 Too Many Requests)
- Pool de conexiones a la base de datos agotado
- Conflictos temporales de bloqueo
Qué comparten: suelen venir de contención de recursos, fluctuación de red o sobrecarga temporal, y esperar un momento antes de reintentar tiende a funcionar.
Los errores permanentes no van a tener éxito al reintentar; necesitan un arreglo en el código o en la configuración.2
Errores permanentes comunes:
- Permisos faltantes (401 Unauthorized, 403 Forbidden)
- Recurso no encontrado (404 Not Found)
- Entrada mal formada (400 Bad Request)
- Errores de lógica de negocio (saldo insuficiente, inventario en cero)
- Bugs de código (puntero nulo, división por cero)
Qué comparten: vienen de una mala configuración, de bugs de código o de reglas de negocio violadas, y reintentar solo desperdicia recursos.
Cómo distinguirlos:
Estrategias de reintento
Ante errores transitorios, reintentar es tu primer movimiento. Pero reintentar tiene más miga de lo que parece.3
Estrategia 1: reintento con retardo fijo
El problema: si los errores vienen de un servicio sobrecargado, que todos los clientes reintenten a la vez empeora la sobrecarga (el efecto de estampida o thundering herd).
Estrategia 2: retroceso exponencial
El beneficio: cada reintento duplica el intervalo, lo que le da al servicio más tiempo para recuperarse en lugar de machacarlo.3
Estrategia 3: retroceso exponencial + jitter
El beneficio: el jitter (variación aleatoria) evita que varios clientes reintenten en el mismo instante exacto, y reparte la carga.3
Esta es la estrategia recomendada para producción.4
Estrategia 4: reintento selectivo
La idea clave: reintenta solo los errores transitorios. Lanza los permanentes de inmediato para no quemar ciclos en reintentos inútiles.5
El patrón Circuit Breaker
El problema: si un servicio falla una y otra vez (digamos, una base de datos caída) y cada petición reintenta tres veces, quemas recursos para nada y arrastras hacia abajo todo el flujo de trabajo. Y si ese servicio es una dependencia de otros servicios, la falla se propaga en cadena hasta convertirse en una falla en cascada.
Un circuit breaker: cuando la tasa de errores cruza un umbral, deja temporalmente de llamar al servicio que falla y falla rápido en su lugar, para evitar el desperdicio de recursos.1
Tres estados
Cerrado: funcionando con normalidad. Las peticiones pasan y el breaker sigue la tasa de errores.
Abierto: el servicio se considera no disponible. Las peticiones fallan rápido sin llamarlo.
Semiabierto: después de un tiempo de espera, pasan unas pocas peticiones de prueba. Si tienen éxito, el breaker vuelve a cerrado; si no, se queda abierto.
Implementación
Cuándo usarlo: llamadas a servicios externos, bases de datos, sistemas de archivos y otras dependencias que pueden fallar en masa.1
Acciones de compensación y reversión
El problema: el flujo de trabajo hizo tres escrituras (escribir en la base de datos, enviar un correo, actualizar el caché) y el paso 4 falló. ¿Cómo deshaces las tres primeras?2
Patrón 1: operaciones transaccionales
Cuándo encaja: todas las operaciones viven en la misma base de datos y esta admite transacciones.
El límite: no puede abarcar varios sistemas (digamos, base de datos + sistema de archivos + llamada a una API).
Patrón 2: acciones de compensación (el patrón Saga)
La idea: define una acción de compensación para cada operación y, ante una falla, corre las compensaciones para deshacer los pasos que ya se completaron.4
Puntos clave:
- Cada paso tiene un
forward (la acción) y un compensate (el deshacer).
- Ante una falla, corre las compensaciones de los pasos completados en orden inverso.
- Una compensación puede fallar ella misma; regístrala y márcala para que la vea una persona.4
Patrón 3: diseño idempotente
Idempotente: correrlo N veces tiene el mismo efecto que correrlo una vez.2
El beneficio: si un paso corre dos veces por un tropiezo de red (el primer intento se agotó por tiempo pero en realidad tuvo éxito), la idempotencia garantiza que no haya efectos secundarios duplicados.2
Capas del manejo de errores
Un buen flujo de trabajo maneja los errores en tres capas:
Capa 1: la operación individual
Capa 2: el paso del flujo de trabajo
Esta capa escribe cada falla en workflowState.errors con el nombre del paso, el mensaje de error y la marca de tiempo. Ese es tu registro de errores, y cuando estés depurando te apoyas en ese registro y no en tu memoria.
Capa 3: el flujo de trabajo completo
Tres capas de protección: reintento en la capa de la operación, registro en la capa del paso, recuperación y aviso en la capa del flujo de trabajo.
Siguiente: Lección 6: Flujos de trabajo del mundo real en la práctica — Junta todo para construir tres flujos de trabajo de nivel producción: refactorización de código, generación de documentación y automatización de pruebas