REL05-BP01 Implementación de una degradación estable para transformar las dependencias estrictas en flexibles
Los componentes de la aplicación deben seguir desempeñando su función principal incluso si las dependencias dejan de estar disponibles. Es posible que proporcionen datos ligeramente obsoletos, datos alternativos o incluso ningún dato. Esto garantiza que los errores localizados solo impidan lo mínimo del funcionamiento general del sistema y, al mismo tiempo, se obtenga el valor empresarial central.
Resultado deseado: cuando las dependencias de un componente no están en buen estado, el propio componente puede seguir funcionando, aunque con capacidad mermada. Los modos de errores de los componentes deben considerarse parte del funcionamiento normal. Los flujos de trabajo deben diseñarse de tal manera que dichos errores no produzcan un fallo total o, al menos, lleven a estados predecibles y recuperables.
Patrones comunes de uso no recomendados:
-
No identificar la funcionalidad empresarial principal necesaria. No probar que los componentes funcionen, incluso durante los errores de dependencia.
-
No proporcionar datos en caso de error o cuando solo una de las múltiples dependencias no está disponible y aún se pueden devolver resultados parciales.
-
Crear un estado incoherente cuando una transacción falla parcialmente.
-
No tener una forma alternativa de acceder a un almacén de parámetros central.
-
Invalidar o vaciar un estado local como resultado de un fallo de actualización sin tener en cuenta las consecuencias.
Beneficios de establecer esta práctica recomendada: la degradación gradual mejora la disponibilidad del sistema en su conjunto y mantiene la funcionalidad de las funciones más importantes incluso cuando hay errores.
Nivel de riesgo expuesto si no se establece esta práctica recomendada: alto
Guía para la implementación
La implementación de una degradación gradual ayuda a minimizar el impacto de los errores de dependencia en la función de los componentes. Lo ideal sería que un componente detectara los errores de dependencia y siguiese funcionando de una forma que afectara lo menos posible a otros componentes o clientes.
Diseñar una arquitectura que permita una degradación gradual implica considerar los posibles modos de errores durante el diseño de las dependencias. Para cada modo de error, disponga de una forma de ofrecer la mayoría o, al menos la funcionalidad más crítica del componente, a las personas que llaman o a los clientes. Estos factores pueden convertirse en requisitos adicionales que se pueden probar y verificar. Lo ideal es que un componente pueda desempeñar su función principal de manera aceptable incluso cuando falla una o varias dependencias.
Se trata tanto de una cuestión empresarial como técnica. Todos los requisitos empresariales son importantes y deben cumplirse si es posible. Sin embargo, es lógico preguntarse qué debe suceder cuando no se puedan cumplir todos. Se puede diseñar un sistema para que esté disponible y sea coherente, pero en circunstancias en las que haya que eliminar un requisito, ¿cuál es más importante? En el caso del procesamiento de pagos, puede ser la coherencia. En una aplicación en tiempo real, puede ser la disponibilidad. En el caso de un sitio web orientado al cliente, la respuesta dependería de las expectativas del cliente.
Lo que esto significa depende de los requisitos del componente y de lo que deba considerarse su función principal. Por ejemplo:
-
Un sitio web de comercio electrónico podría mostrar en su página de inicio los datos de varios sistemas diferentes, como las recomendaciones personalizadas, los productos mejor clasificados y el estado de los pedidos de los clientes. Cuando un sistema anterior falla, sigue siendo lógico mostrar todo lo demás en lugar de mostrar una página de error al cliente.
-
Un componente que lleva a cabo escrituras por lotes puede seguir procesando un lote si se produce un error en una de las operaciones individuales. Implementar un mecanismo de reintento debería ser sencillo. Para hacerlo, se puede devolver a la persona que llama información sobre qué operaciones se han hecho correctamente, cuáles han fallado y por qué han fallado, o colocar las solicitudes que han fallado en una cola de mensajes fallidos para implementar reintentos asíncronos. También se debe registrar la información sobre las operaciones que han fallado.
-
Un sistema que procese las transacciones debe verificar que se ejecuten todas o ninguna de las actualizaciones individuales. En el caso de las transacciones distribuidas, se puede usar el patrón Saga para revertir operaciones anteriores en caso de que falle una operación posterior de la misma transacción. En este caso, la función principal es mantener la coherencia.
-
Los sistemas en los que el tiempo es crítico deberían contar con la capacidad de gestionar de manera oportuna las dependencias que no respondan. En estos casos, se puede utilizar el patrón del disyuntor. Cuando se agota el tiempo de espera de las respuestas de una dependencia, el sistema puede cambiar a un estado cerrado en el que no se hacen llamadas adicionales.
-
Una aplicación puede leer parámetros de un almacén de parámetros. Puede resultar útil crear imágenes de contenedores con un conjunto predeterminado de parámetros y utilizarlos en caso de que ese almacén de parámetros no esté disponible.
Tenga en cuenta que las soluciones que se adopten en caso de fallo de un componente deben probarse y ser significativamente más sencillas que la solución principal. En general, se debe evitar el uso de estrategias alternativas
Pasos para la implementación
Identifique las dependencias externas e internas. Considere qué tipos de errores pueden producirse en ellas. Piense en formas de minimizar el impacto negativo en los sistemas anteriores y posteriores y en los clientes durante esos errores.
A continuación, tenemos una lista de dependencias y la descripción de cómo degradar correctamente cuando fallan:
-
Errores parciales de dependencias: un componente puede hacer varias solicitudes a los sistemas posteriores, ya sean varias solicitudes a un sistema o una sola solicitud destinada a varios sistemas. En función del contexto empresarial, es posible que haya diferentes formas apropiadas de gestionar este problema (para obtener más información, consulte los ejemplos anteriores en la Guía de implementación).
-
Un sistema descendente no puede procesar las solicitudes debido a la alta carga: si las solicitudes a un sistema descendente fallan constantemente, no tiene sentido volver a intentarlo. Esto puede suponer una carga adicional para un sistema ya sobrecargado y dificultar la recuperación. Aquí se puede utilizar el patrón de disyuntor, que supervisa las llamadas que fallaron al enviarlas a un sistema posterior. Si falla un gran número de llamadas, dejará de enviar más solicitudes al sistema posterior y solo permitirá ocasionalmente el paso de las llamadas para comprobar si el sistema posterior vuelve a estar disponible.
-
Un almacén de parámetros no está disponible: para transformar un almacén de parámetros, se puede utilizar el almacenamiento en caché de dependencia flexible o los valores predeterminados en buen estado que se incluyen en las imágenes de contenedores o máquinas. Tenga en cuenta que estos valores predeterminados deben mantenerse actualizados e incluirse en los conjuntos de pruebas.
-
Un servicio de supervisión u otra dependencia no funcional no está disponible: si un componente no puede enviar registros, métricas o rastros de forma intermitente a un servicio de monitorización central, suele ser mejor seguir ejecutando las funciones empresariales como de costumbre. No registrar ni subir métricas de forma silenciosa durante mucho tiempo no suele ser aceptable. Además, algunos casos de uso pueden requerir entradas de auditoría completas para satisfacer los requisitos de cumplimiento.
-
Es posible que una instancia principal de una base de datos relacional no esté disponible: Amazon Relational Database Service, como casi todas las bases de datos relacionales, solo puede tener una instancia de escritura principal. Esto crea un único punto de error para las cargas de trabajo de escritura y dificulta el escalamiento. Este problema se puede mitigar parcialmente mediante el uso de una configuración Multi-AZ para lograr alta disponibilidad o de Amazon Aurora sin servidor para mejorar el escalado. Cuando los requisitos de disponibilidad son muy altos, podría ser conveniente no utilizar en absoluto el escritor principal. Para consultas de solo lectura, se pueden utilizar réplicas de lectura, que proporcionan redundancia y capacidad de escalado horizontal, no solo vertical. Las escrituras se pueden almacenar en búfer, por ejemplo, en una cola de Amazon Simple Queue Service, de modo que las solicitudes de escritura de los clientes puedan seguir aceptándose incluso si la principal no está disponible temporalmente.
Recursos
Documentos relacionados:
-
Amazon API Gateway: Throttle API Requests for Better Throughput
-
CircuitBreaker (summarizes Circuit Breaker from “Release It!” book)
-
Michael Nygard “Release It! Design and Deploy Production-Ready Software”
-
Amazon Builders' Library: Evitar los planes alternativos en los sistemas distribuidos
-
Amazon Builders' Library: Cómo evitar demoras de colas insuperables
-
Amazon Builders' Library: Desafíos y estrategias del almacenamiento en caché
-
Amazon Builders' Library: Tiempos de espera, reintentos y retardo con fluctuación
Videos relacionados:
Ejemplos relacionados: