REL05-BP01 Implementar uma degradação normal para transformar dependências rígidas aplicáveis em dependências flexíveis
Os componentes da aplicação devem continuar desempenhando sua função principal mesmo que as dependências se tornem indisponíveis. Eles podem estar fornecendo dados um pouco obsoletos, dados alternativos ou até mesmo nenhum dado. Isso garante que o funcionamento geral do sistema seja minimamente impedido por falhas localizadas e, ao mesmo tempo, ofereça o valor empresarial central.
Resultado desejado: quando as dependências de um componente não estão íntegras, o próprio componente ainda pode funcionar, embora de maneira prejudicada. Os modos de falha dos componentes devem ser vistos como operação normal. Os fluxos de trabalho devem ser projetados de forma que essas falhas não ocasionem à falha total ou, pelo menos, a estados previsíveis e recuperáveis.
Práticas comuns que devem ser evitadas:
-
Não identificar a principal funcionalidade empresarial necessária. Não testar se os componentes estão funcionando mesmo durante falhas de dependência.
-
Não fornecer dados sobre erros ou quando apenas uma das várias dependências não está disponível e resultados parciais ainda podem ser retornados.
-
Criar um estado inconsistente quando uma transação falha parcialmente.
-
Não ter uma forma alternativa de acessar um armazenamento de parâmetros central.
-
Invalidar ou esvaziar o estado local como resultado de uma falha na atualização sem levar em conta as consequências de fazer isso.
Benefícios de implementar esta prática recomendada: a degradação gradual melhora a disponibilidade do sistema como um todo e mantém as funções mais importantes em execução mesmo durante falhas.
Nível de risco exposto se esta prática recomendada não for estabelecida: Alto
Orientação para implementação
A implementação de uma degradação gradual ajuda a minimizar o impacto das falhas de dependência na função do componente. Preferencialmente, um componente detecta falhas de dependência e as contorna de uma maneira que afeta minimamente outros componentes ou clientes.
Arquitetar para uma degradação gradual significa considerar possíveis modos de falha durante o projeto de dependência. Para cada modo de falha, tenha uma maneira de fornecer a maior parte ou pelo menos a funcionalidade mais crítica do componente para chamadores ou clientes. Essas considerações podem se tornar requisitos adicionais que podem ser testados e verificados. Preferencialmente, um componente é capaz de realizar sua função principal de maneira aceitável, mesmo quando uma ou várias dependências falham.
Trata-se tanto de uma discussão empresarial quanto técnica. Todos os requisitos comerciais são importantes e devem ser atendidos, se possível. No entanto, ainda faz sentido perguntar o que deve acontecer quando nem todos eles podem ser cumpridos. Um sistema pode ser projetado para estar disponível e ser consistente, mas em circunstâncias em que um requisito deve ser descartado, qual deles é mais importante? Para o processamento de pagamentos, pode ser a consistência. Para uma aplicação em tempo real, pode ser a disponibilidade. Para um site voltado para o cliente, a resposta pode depender das expectativas do cliente.
O que isso significa depende dos requisitos do componente e do que deve ser considerado sua função principal. Por exemplo:
-
Um site de comércio eletrônico pode exibir dados de vários sistemas diferentes, como recomendações personalizadas, produtos mais bem classificados e status dos pedidos dos clientes na página de pouso. Quando um sistema upstream falha, ainda faz sentido exibir todo o resto em vez de mostrar uma página de erro para um cliente.
-
Um componente que executa gravações em lote ainda poderá continuar processando um lote se ocorrer uma falha em uma das operações individuais. Deve ser simples implementar um mecanismo de novas tentativas. Isso pode ser feito retornando informações sobre quais operações foram bem-sucedidas, quais falharam e por que falharam para o chamador, ou colocando solicitações com falha em uma fila de mensagens não entregues para implementar novas tentativas assíncronas. As informações sobre operações com falha também devem ser registradas em log.
-
Um sistema que processa transações deve verificar se todas ou nenhuma atualização individual foi executada. Para transações distribuídas, o padrão saga pode ser usado para reverter operações anteriores caso ocorra uma falha em uma operação posterior da mesma transação. Aqui, a função principal é manter a consistência.
-
Sistemas essenciais devem ser capazes de lidar com dependências não correspondentes em tempo hábil. Nesses casos, o padrão de disjuntor pode ser usado. Quando as respostas de uma dependência começam a atingir o tempo limite, o sistema pode mudar para um estado fechado em que nenhuma chamada adicional é realizada.
-
Uma aplicação pode ler parâmetros de um armazenamento de parâmetros. Pode ser útil criar imagens de contêiner com um conjunto padrão de parâmetros e usá-las caso o armazenamento de parâmetros não esteja disponível.
Observe que as vias percorridas em caso de falha do componente precisam ser testadas e devem ser significativamente mais simples do que a via principal. Em geral, estratégias de fallback devem ser evitadas
Etapas de implementação
Identifique dependências externas e internas. Leve em conta quais tipos de falhas podem ocorrer nelas. Pense em maneiras de minimizar o impacto negativo nos sistemas upstream e downstream e nos clientes durante essas falhas.
Veja a seguir uma lista de dependências e como degradar normalmente quando elas falham:
-
Falha parcial das dependências: um componente pode fazer várias solicitações para sistemas downstream, como várias solicitações para um sistema ou uma solicitação para vários sistemas cada. Dependendo do contexto empresarial, diferentes maneiras de lidar com isso podem ser apropriadas (para obter mais detalhes, consulte exemplos anteriores em Orientações de implementação).
-
Um sistema downstream não consegue processar solicitações devido à alta carga: se as solicitações para um sistema downstream falharem constantemente, não fará sentido continuar tentando novamente. Isso pode criar carga adicional em um sistema já sobrecarregado e dificultar a recuperação. O padrão de disjuntor pode ser utilizado aqui, o qual monitora as chamadas com falha para um sistema downstream. Se ocorrer uma falha em um grande número de chamadas, ele deixará de enviar mais solicitações para o sistema downstream e só ocasionalmente permitirá que as chamadas passem para testar se o sistema downstream está disponível novamente.
-
Uma loja de parâmetros não está disponível: para transformar um armazenamento de parâmetros, é possível usar o armazenamento em cache flexível de dependências ou padrões razoáveis incluídos nas imagens do contêiner ou da máquina. Observe que esses padrões precisam ser mantidos atualizados e incluídos nos pacotes de testes.
-
Um serviço de monitoramento ou outra dependência não funcional não está disponível: se um componente não conseguir enviar logs, métricas ou rastreamentos de forma intermitente para um serviço de monitoramento central, geralmente é melhor continuar executando as funções empresariais normalmente. Não registrar em log nem enviar métricas silenciosamente por um longo período geralmente não é aceitável. Além disso, alguns casos de uso podem exigir entradas de auditoria completas para atender aos requisitos de conformidade.
-
Uma instância primária de um banco de dados relacional pode estar indisponível: o Amazon Relational Database Service, como quase todos os bancos de dados relacionais, só pode ter uma instância de gravador principal. Isso cria um único ponto de falha para workloads de gravação e dificulta o ajuste de escala. Isso pode ser parcialmente reduzido com o uso de uma configuração Multi-AZ para alta disponibilidade ou do Amazon Aurora Sem Servidor para melhor ajuste de escala. Para requisitos de disponibilidade muito altos, pode fazer sentido não confiar no gravador principal. Para consultas que são somente leitura, é possível usar réplicas de leitura que fornecem redundância e a capacidade de aumentar a escala horizontalmente, e não apenas verticalmente. As gravações podem ser armazenadas em buffer, por exemplo, em uma fila do Amazon Simple Queue Service, para que as solicitações de gravação dos clientes ainda possam ser aceitas mesmo que a principal esteja temporariamente indisponível.
Recursos
Documentos relacionados:
-
Amazon API Gateway: controlar as solicitações de API para um melhor throughput
-
CircuitBreaker
(resume "Disjuntor" do livro "Release It!") -
Michael Nygard "Release It! Design and Deploy Production-Ready Software
" -
Amazon Builders' Library: evitar fallback em sistemas distribuídos
-
Amazon Builders' Library: evitar backlogs de fila insuperáveis
-
Amazon Builders' Library: desafios e estratégias de armazenamento em cache
-
Amazon Builders' Library: tempos limite, novas tentativas e recuo com jitter
Vídeos relacionados:
Exemplos relacionados: