Un producto que requiere de servicios de terceros (entiéndase como “terceros” cualquier entidad que intervenga en el ciclo de desarrollo como personas independientes, empresas, equipos propios… que no están gestionados por el equipo principal) tiene que ser tolerante a errores y reaccionar ante cualquier adversidad que se presente en su contexto.
En el artículo anterior adelantábamos que cualquier integración con agentes externos tiene que ser controlada. En este artículo nos centramos en la siguiente premisa:
Debemos de ser capaces de reaccionar ante errores transitorios
Para poder reaccionar frente a este tipo de errores primero tenemos que entender e interiorizar a que nos estamos refiriendo con “transitorio”. Un error transitorio es aquel que ocurre de forma no esperada y es probable que desaparezca en un corto periodo de tiempo. Puede aparecer debido a:
- Errores de conexión debido a una caída momentánea del servicio.
- Errores de sobreexplotación debido a sobrecarga en el número de conexiones al mismo.
- Errores debido a una corrupción de datos durante la transmisión.
- Errores en los servicios debidos a problemas desconocidos pero que no se extienden en el tiempo.
Este tipo de errores que no ocurren de forma continua en el tiempo pueden ser subsanados siguiendo una simple estrategia “Prueba otra vez”.
Debemos de elegir el número de intentos adecuado dependiendo de cada situación. Habrá veces que seguir intentándolo de manera indefinida puede ser la mejor opción, pero puede ser que en otros casos el seguir intentándolo de manera indefinida pueda ocasionar tiempos de espera inaceptables, por lo que será necesario establecer un límite de intentos coherente.
Por ejemplo, un servicio que hemos contratado para realizar el análisis de textos para obtener su polaridad puede rechazar nuestras conexiones porque hemos realizado demasiadas llamadas en un lapso muy corto. La aproximación aquí podría ser reintentarlo más tarde con el fin de dejar “descansar” ese servicio.
Otro ejemplo, estamos utilizando un servicio gestionado por otro equipo dentro de nuestra propia empresa y requiere de un downtime de 1 minuto para tareas de mantenimiento. No podemos permitirnos que nuestro producto dependiente de ellos colapse y deje de dar servicio. Debemos de poder reintentar esas interacciones hasta que el servicio vuelva a estar disponible.
Variantes
Una vez hemos comprendido el concepto de error transitorio, existen diferentes aproximaciones a la hora de aplicar un patrón Retry. A continuación, se exponen algunas variantes que nos permiten actuar correctamente en situaciones ligeramente diferentes.
Retry Now
Esta variante se basa en la premisa de que:
Si un servicio ha fallado, es probable que si lo vuelvo a intentar inmediatamente estará disponible
Esta forma de manejar los errores transitorios es la más sencilla de utilizar. Si hemos detectado que la causa del error no es probable que se vuelva a producir (por ejemplo, corrupción de datos), podemos optar por volver a intentarlo inmediatamente.
Retry with a fixed delay
Esta variante se basa en la premisa de que:
Si un servicio ha fallado, es probable que vuelva a fallar si lo vuelves a intentar inmediatamente
Aquí nos encontramos con una situación un poco diferente, si detectamos que un servicio de terceros ha fallado por causas propias, no deberíamos de asumir que es capaz de recuperarse inmediatamente. Esto nos permite proporcionarle unos momentos de respiro no sobrecargándolo con demasiadas peticiones no satisfactorias.
Habrá veces que si un servicio falla, el realizar múltiples llamadas posteriores únicamente ocasionen un downtime más duradero.
Por ejemplo, si ha habido un problema de saturación de red, el seguir estableciendo más conexiones únicamente ocasionarán que la red se sature más y más y aumentar la severidad del problema.
Retry with a variable delay
Esta variante se basa en la premisa de que:
Si un servicio ha fallado recientemente, es probable que lo vuelva a hacer si no esperas lo suficiente
Ya, por último, nos encontramos con esta aproximación. Un error crítico en los sistemas de terceros puede no resolverse de manera inmediata, es más, puede tardar bastante en resolverse. De nada sirve que lo reintentemos cada N segundos. Estaremos malgastando bastantes recursos en realizar unas llamadas que es probable que no sean satisfactorias. En este caso, la forma de actuar es ligeramente diferente. Podemos utilizar unos tiempos de espera variables que se adapten a cada situación.
Por ejemplo, si un servicio de terceros se encuentra caído y cada llamada al servicio usa una cantidad de recursos considerable, podemos implementar un sistema variable basado en la ecuación:
En este caso, si establecemos como el tiempo de espera base “3 segundos” y la comunicación no es satisfactoria, tendremos los siguientes tiempos de espera:
Intento | Segundos de espera |
1 | 3 |
2 | 6 |
3 | 9 |
4 | 12 |
En el ejemplo estamos utilizando unos tiempos de espera que escalan linealmente.
Dependiendo de las necesidades podemos incluso necesitar definirnos nuestra propia función que calcule los tiempos de reintento. En la siguiente gráfica vemos los tiempos de reintento siguiendo una función exponencial:
Conclusiones
A lo largo de este artículo se han ofrecido diferentes aproximaciones para solucionar el problema de los errores transitorios. A partir de ellas se pueden extraer las siguientes conclusiones:
- Utiliza siempre un patrón “Retry” cuando se tenga que realizar una conexión con servicios de terceros.
- Identifica las necesidades de negocio y elige la mejor variante del patrón “Retry”.
- Establece un límite de intentos coherente que dependerá de la casuística de tu proyecto.
- Habrá veces que un “Fail Fast” sea la aproximación a utilizar y no debas de reintentar demasiadas veces una conexión.