Diseño del sistema: prepárese para lo peor

Nov 28 2022
Al desarrollar una nueva aplicación o una nueva función, una de las cosas importantes a las que debe prestar atención es la capacidad de su aplicación para recuperarse automáticamente en caso de fallas. Su código puede ser excelente y cumplir con los requisitos comerciales PERO, ¿es lo suficientemente bueno como para hacerlo también en momentos de fallas inesperadas? ¿Su aplicación tiene la resiliencia suficiente para superar automáticamente esas fallas? A veces, no se le indicará implícitamente que implemente mecanismos de resistencia y respaldo como parte de los requisitos comerciales, ya que los escenarios que se supone que debe manejar pueden ser muy técnicos y estar impulsados ​​por el desarrollo.
Foto de David Pupaza en Unsplash

Al desarrollar una nueva aplicación o una nueva función, una de las cosas importantes a las que debe prestar atención es la capacidad de su aplicación para recuperarse automáticamente en caso de fallas. Su código puede ser excelente y cumplir con los requisitos comerciales PERO, ¿es lo suficientemente bueno como para hacerlo también en momentos de fallas inesperadas? ¿Su aplicación tiene la resiliencia suficiente para superar automáticamente esas fallas?

A veces, no se le indicará implícitamente que implemente mecanismos de resistencia y respaldo como parte de los requisitos comerciales, ya que los escenarios que se supone que debe manejar pueden ser muy técnicos y estar impulsados ​​por el desarrollo. Aquellos mecanismos relacionados con la familia de requisitos no funcionales.

¿Cuáles son esos “fallos inesperados” de los que hablamos?

Bueno, hay varios escenarios que pueden ocurrir y que llevarán a su aplicación a un estado de falla/inestabilidad. Por ejemplo:

  • Tiempo de espera/Servicio no disponible de una API externa que usa su aplicación
  • Errores en terceros que están siendo utilizados por su aplicación
  • Fallo de las operaciones de la base de datos
  • Carga inesperada en la aplicación que causa la degradación del rendimiento y, por lo tanto, la denegación de las solicitudes entrantes
  • Error inesperado que provoca la pérdida de datos
  • los procesos de larga duración que son administrados por la aplicación están atascados en su estado debido a la pérdida de datos

Tener una aplicación sin los mecanismos de auto-recuperación y respaldo relevantes no se mantendrá a largo plazo. Esas "fallas inesperadas" eventualmente sucederán, dañarán su SLA y la experiencia del cliente. Puede causar la pérdida de datos que será difícil de recuperar y requerirá que USTED intervenga manualmente y haga algo de magia para solucionar el problema. Ahora piense en las aplicaciones que manejan cientos de miles de solicitudes por minuto, una pequeña falla puede causar un gran impacto que puede ser difícil de solucionar manualmente y le costará mucho tiempo y dinero a su organización.
Por lo tanto, debes prepararte para lo peor.

Repasemos algunos escenarios comunes y brindemos soluciones que pueden aumentar el nivel de resistencia de su aplicación y las capacidades de respaldo para que pueda dormir en paz por la noche :)

Mecanismo de reintento

Especialmente cuando se trata de operaciones de E/S como enviar solicitudes http, leer/escribir desde una base de datos o un archivo, etc., la posibilidad de que encuentre un error es común. Por ejemplo: servicio no disponible, error interno del servidor, tiempo de espera de la operación y más.
Muchos de estos errores pueden ser transitorios y detenerse en cuestión de segundos, lo que significa que existe una alta probabilidad de que el próximo intento después de unos segundos funcione.
Entonces… ¿por qué falla el flujo de su negocio solo por un pequeño problema temporal? ¡Intentar otra vez! Utilice un mecanismo de reintento sobre la operación que garantizará que, si el problema es temporal, su aplicación lo superará automáticamente.
Preste atención a lo siguiente:

  • Ser capaz de configurar y limitar la cantidad de reintentos que su aplicación intenta ejecutar. Un número infinito/alto de reintentos puede hacer que su aplicación se atasque y niegue otras solicitudes, ya que estará ocupada en reintentos interminables
  • implementar el horneado exponencial. Aumente exponencialmente el intervalo entre los reintentos.
    Aumentará la posibilidad de que el problema temporal (en el servidor/base de datos/terceros) se haya resuelto y su próximo intento sea exitoso. Además, el uso del intervalo exponencial también otorga gracia a terceros que pueden estar bajo carga y, por lo tanto, no pueden atender a sus clientes. Ejecutar reintentos inmediatos sin demora puede empeorar el problema.
  • Asegúrese de comprender qué errores debe haber reintentos y cuáles no. No todos los errores se pueden resolver con otro reintento. Por ejemplo, supongamos que su solicitud http falló con el código de estado 'Solicitud incorrecta' (400). Lo que significa que algo con los datos de la solicitud está mal y fue rechazado por el servidor. No importa cuántos reintentos haga su aplicación, terminará con el mismo resultado: falla.
    Hacer reintentos en ese tipo de errores es redundante y afectará el rendimiento de su aplicación. Debe distinguir entre los diferentes errores y decidir qué reintento se puede realizar.

Hay varias bibliotecas de código abierto que ya le brindan todas las características relevantes del mecanismo de reintento para que no tenga que escribirlo desde cero. Por ejemplo, en .NET tienes una biblioteca llamada Polly.

No olvide hacer la pregunta, ¿qué debo hacer si todos los reintentos fallan? Esta es una pregunta comercial y la respuesta se puede cambiar entre escenarios.

Defina el tiempo de espera para sus procesos

Algunas aplicaciones gestionan procesos de larga duración. El proceso tiene un estado que indica el progreso. El progreso del proceso a veces depende de operaciones largas o de una respuesta asíncrona de un tercero. Dado que las cosas pueden salir mal (y siempre salen mal), su proceso puede quedarse atascado para siempre en un estado no final y permanecer "abierto". Por ejemplo, el tercero no pudo devolver la respuesta.

Ok, entonces permanecerán abiertos. ¿Cuál es el problema?
Lo más probable es que otros sistemas dependan de tus procesos y estén pendientes de su resultado. Su aplicación bloqueará todo el flujo comercial, lo que afectará el SLA de otros servicios y la experiencia del usuario.
Además, esos procesos abiertos dificultarán las investigaciones y el análisis de datos, ya que se comportan de manera diferente y pueden dañar sus estadísticas.

¡No quiero esto! ¿Cómo lo soluciono?
Primero, define cuál es el tiempo adecuado y aceptable que tu proceso puede permanecer en un estado no definitivo. Esta es una pregunta comercial y debe ser determinada por el SLA al que está obligado y por la evaluación de cuál es el tiempo razonable total que debe tomar para las operaciones dentro del proceso. Por ejemplo, si mi proceso depende de un tercero cuyo tiempo de respuesta promedio es de 10 minutos, puede definir un tiempo de espera de 20 a 30 minutos.
Ahora puede saltar para la implementación. La implementación básica puede ser crear una operación programada que se ejecute cada X minutos y busque procesos abiertos durante más de Y minutos (valor de tiempo de espera). Si lo encuentra, simplemente mueva el proceso a un estado final, puede ser un estado de falla/cancelación.
De esta manera, tiene la capacidad de fallar/cancelar el proceso con gracia. Se está haciendo de una manera manejable y brinda a su aplicación la capacidad de notificar el resultado relevante a sus clientes pendientes.

¡Aviso! el patrón no resolvió el problema en sí, PERO le brindó la capacidad de administrarlo automáticamente y ejecutar el flujo de reserva. Ser capaz de comunicar que hay un problema y cumplir con el SLA prometido.
Veamos un escenario de la vida real que usa este patrón:
Mi equipo en Payoneer es responsable del proceso de aprobación automática de los documentos de los clientes. El proceso de aprobación de un documento es un proceso fuera de línea que se construye a partir de varios pasos e implica la integración con proveedores externos que brindan resultados de análisis a través de la comunicación asíncrona.
Nuestro sistema implementa el patrón de tiempo de espera y, cuando se agota el tiempo, el sistema mueve los documentos del cliente para que sean revisados ​​manualmente por un representante en lugar de esperar el resultado automático.
De esta manera no estamos impactando el SLA que tenemos frente al cliente en cuanto al tiempo que toma revisar sus documentos.

No retransmitir solo en devolución de llamada / webhook: implemente un mecanismo de sondeo

Cuando se trata de comunicación asincrónica, la respuesta se devuelve al cliente como una devolución de llamada. Puede ser que el servidor active un webhook con los datos o que el servidor publique un evento a través de un bus de eventos/intermediario de mensajes.
Hay escenarios en los que el cliente no recibió el mensaje o se recibió pero no se manejó correctamente y, por lo tanto, se eliminó, lo que provocó la pérdida de datos. En esos escenarios, el cliente aún está pendiente de la respuesta pero el servidor ya la envió y no la volverá a enviar.

En la sección anterior, mencionamos el patrón de tiempo de espera que no resuelve el problema, pero le permite fallar con gracia y habilitar un flujo de respaldo manejable. Al implementar el mecanismo de sondeo, podrá resolver automáticamente algunos de los problemas y mantenerse en el "camino feliz" del flujo comercial.

En lugar de simplemente esperar a que el servidor le proporcione la respuesta de forma asíncrona mediante inserción, implemente un proceso programado que se ejecutará cada X minutos y buscará solicitudes que estén pendientes durante más de Y minutos. Para esas solicitudes, su aplicación enviará una solicitud síncrona al servidor para obtener los datos de respuesta. Si la solicitud original terminó de ser procesada por el servidor, podrá enviarle el resultado. En este caso, su aplicación continuará con el flujo de negocios de la misma manera que lo hacía si el webhook recibiera los datos. De lo contrario, el servidor todavía está procesando la solicitud original y no hay que devolverla. Solo tienes que seguir esperando.

Al implementar este mecanismo, pudo resolver automáticamente problemas inesperados relacionados con la comunicación asincrónica y, por lo tanto, evitar fallas en el flujo o intervenir manualmente para restaurar los datos del servidor.

¡Aviso! No todos los servicios con los que se integrará tendrán la capacidad de proporcionar puntos finales para sondeo además de la comunicación asíncrona que ofrecen. Si no lo tienen, vale la pena preguntar si se puede hacer en el futuro.

Es cierto que hay una sobrecarga de rendimiento con esta solución, PERO con la elección de los intervalos correctos será perfecto y el beneficio que proporciona en momentos de fallas no tiene precio.

Evitar el bloqueo del proveedor

Es común que la aplicación utilice proveedores externos para lograr sus objetivos comerciales. No es posible que todas las organizaciones desarrollen cada parte de la lógica internamente, especialmente si la lógica es compleja y está alejada del negocio principal de la organización. Por lo tanto, probablemente prefiera integrarse con un proveedor que pueda proporcionarle parte de la lógica que necesita para completar el flujo de su negocio.

Entonces, después de una investigación y POC, encontró un proveedor adecuado para sus necesidades.
La integración se realizó sin problemas y... ¡VOILA! puso en marcha el flujo de su negocio y atendió a cientos de miles de clientes al día.

Ahora permítame hacerle una pregunta. ¿Qué pasará si un día este proveedor externo deja de funcionar? ¿Qué pasará si entra en quiebra? ¿Si fue pirateado y el servicio no estará disponible durante un largo período de tiempo? ¿O tal vez simplemente no estará satisfecho con el rendimiento del proveedor o tal vez con la forma en que se administra, ya que cada mes hay nuevos cambios importantes que hacen que su equipo de desarrollo trabaje las 24 horas para ajustar la integración?

Especialmente cuando se trata de flujos comerciales principales y sensibles, este escenario es crítico y puede paralizar parte de su organización y tener un gran impacto en sus clientes.
Volviendo al proceso de aprobación automática de los documentos del cliente que gestiona mi equipo en Payoneer, como parte del proceso utilizamos proveedores externos para extraer el texto relevante del documento y procesarlo. Elegimos integrarnos con 2 proveedores que nos brindan este resultado y tienen prioridad entre ellos, por lo que si uno falla o tiene muchos falsos positivos, podemos cambiar al otro sin tiempo de inactividad del flujo comercial. Por supuesto, esto también nos da la confianza de que si algo le sucede a uno de ellos, siempre tendremos otro en quien apoyarnos.

Por supuesto, no siempre tiene que crear esta copia de seguridad. Es una cuestión de valor por dinero y cuán crítico es el flujo de negocios con el que está tratando.

Patrón de disyuntor

A diferencia del patrón de reintento que supone resolver fallas transitorias, el patrón de ruptura de circuito está aquí para evitar que la aplicación invoque una operación que tiene una alta probabilidad de fallar.
Piense que está utilizando un tercero a través de la API REST que actualmente tiene un error que hace que todas las solicitudes fallen. Digamos que la ETA para la corrección es de 4 horas.
Con el patrón de reintento, la aplicación intentará varias veces invocar la solicitud que finalmente fallará. Esos reintentos inútiles afectarán el rendimiento de la aplicación y del tercero.
En lugar de eso, el patrón de interrupción del circuito permitirá que la aplicación falle rápidamente e intente recuperarse gradualmente cuando se resuelva el problema. Por eso evita que se produzcan errores y la degradación del rendimiento.

¿Entonces, cómo funciona?
El mecanismo actúa como un proxy para la operación que desea ejecutar.
Tiene 3 estados:

  • Cerrado : lo que significa que la operación puede ejecutarse regularmente.
    El número de fallas se cuenta en este estado. Si el número de fallas cruza el umbral definido en un intervalo de tiempo definido, el mecanismo pasará al estado abierto.
  • Abierto : lo que significa que el mecanismo evitará que la operación se ejecute ya que experimentó muchas fallas y, por lo tanto, preferirá fallar rápidamente sin intentar ejecutarla. En este estado tenemos un tiempo de espera. Cuando se alcanza, el mecanismo se mueve al estado Medio abierto.
  • Medio abierto : lo que significa que se permiten y se pasan solicitudes limitadas para ejecutar la operación. Este estado es para verificar que el problema se resolvió. Si esa cantidad limitada de operaciones se ejecuta con éxito, el mecanismo asume que el problema se resolvió y cambia el estado del mecanismo a cerrado. De lo contrario, volverá a Abrir ya que el problema aún existe.

Puntos finales dedicados para una intervención manual fácil y eficiente

Dicho todo esto, a veces no estamos preparados con una solución automática para cualquier caso que pueda surgir. A veces, nuestra solución automatizada vendrá solo después de que nos encontramos por primera vez con el escenario y comprendimos cómo resolverlo mediante la automatización. Por lo tanto, en esos casos, la intervención manual es inevitable. La mayoría de las veces, estas intervenciones manuales requieren que usted pase por las "tripas" del servicio como: cambiar valores directamente a través de la base de datos o reestructurar manualmente un mensaje y enviarlo directamente a través del intermediario de mensajes.

También aquí podemos estar preparados para que esta intervención manual sea manejable, segura y eficiente. Simplemente cree una API interna en su aplicación que habilitará esas operaciones manuales comunes.
Por ejemplo, cree un punto final que le permita cancelar un proceso que está administrando su aplicación. O cree un punto final que permita volver a publicar un resultado de un proceso anterior para que otro sistema pueda consumirlo nuevamente si se perdió o nunca se disparó.

Al crear esta API interna, aunque requiere que alguien la active, tiene muchos beneficios:

  • La operación es manejable por la aplicación. La aplicación es propietaria del cambio y es consciente de ello.
  • La operación fue probada por QA ya que era parte del desarrollo. Por lo tanto, reduce las posibilidades de errores.
  • a veces, la operación no es tan simple como actualizar el valor en la base de datos. A veces también hay acciones secundarias que deben realizarse. Todas esas acciones secundarias se pueden administrar bajo la API interna para un uso fácil.
  • Ahorrar tiempo

Para concluir

¡Su solicitud siempre DEBE estar preparada para lo peor!
Cuanto más sepa su aplicación para recuperarse automáticamente y tenga soluciones alternativas, más aumentará su rendimiento, el impacto en la experiencia del cliente será bajo, más rápido podrá superar las fallas y ahorrar tiempo y dinero valiosos para su organización.

Durante el diseño de su aplicación/característica, debe prestar atención a esto. Debe hacer las preguntas correctas e identificar los puntos débiles para preparar el tratamiento pertinente y evitar sorpresas desagradables en la producción.