REL05-BP01 Implementar 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 la 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 mínimo 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 realizar su función principal de manera aceptable incluso cuando falla una o varias dependencias.
Se trata tanto de un tema empresarial como técnico. 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 realiza 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. Se puede hacer devolviendo a la persona que llama información sobre qué operaciones se han realizado correctamente, cuáles han fallado y por qué han fallado, o colocando 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 poder gestionar de la 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 realizan 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 deben ser significativamente más sencillas que la solución principal. En general, deben evitarse 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 cómo degradar correctamente cuando fallan:
-
Fallo parcial de las dependencias: un componente puede realizar varias solicitudes a los sistemas posteriores, ya sean varias solicitudes a un sistema o una sola solicitud destinada a varios sistemas. Dependiendo 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 posterior no puede procesar las solicitudes debido a la alta carga: si las solicitudes a un sistema posterior fallan constantemente, no tiene sentido seguir intentándolo. 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 monitoriza las llamadas que han fallado 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.
-
El 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.
-
No hay disponible un servicio de monitorización u otra dependencia no funcional: 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 escritor 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 alta disponibilidad o Amazon Aurora sin servidor para mejorar el escalamiento. Cuando los requisitos de disponibilidad son muy altos, podría ser conveniente no utiliza en absoluto el escritor principal. Para consultas que solo leen, se pueden utilizar réplicas de lectura, que proporcionan redundancia y capacidad de escalamiento 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: Limitar las solicitudes de la API para mejorar el rendimiento
-
CircuitBreaker (resumen del patrón de interruptor del libro «Release It!»)
-
Michael Nygard «Release It! Design and Deploy Production-Ready Software»
-
La Amazon Builders' Library: Evitar el retroceso en sistemas distribuidos
-
La Amazon Builders' Library: Evitar trabajos pendientes en colas insalvables
-
La Amazon Builders' Library: Desafíos y estrategias del almacenamiento en caché
-
La Amazon Builders' Library: Tiempos de espera, reintentos y retroceso con alteración
Vídeos relacionados:
Ejemplos relacionados: