Projetar para escala e alta disponibilidade

Last reviewed 2023-08-05 UTC

Neste documento no Framework da arquitetura do Google Cloud, você encontra os princípios de design para arquitetar seus serviços, permitindo tolerar falhas e escalonar em resposta à demanda dos clientes. Um serviço confiável continua a responder às solicitações do cliente quando há uma alta demanda no serviço ou quando há um evento de manutenção. Os seguintes princípios de design de confiabilidade e práticas recomendadas precisam fazer parte da arquitetura do sistema e do plano de implantação.

Criar redundância para maior disponibilidade

Sistemas com necessidades de alta confiabilidade não precisam ter pontos únicos de falha, e os recursos precisam ser replicados em vários domínios com falha. Domínio de falha é um pool de recursos que podem falhar de forma independente, como uma região ou zona de instância de VM. Ao replicar entre domínios de falha, o nível agregado de disponibilidade é maior do que o que as instâncias individuais podem alcançar. Para mais informações, consulte Regiões e zonas.

Como um exemplo específico de redundância que pode fazer parte da arquitetura do sistema, para isolar falhas no registro DNS de zonas individuais, use nomes DNS zonais nas instâncias na mesma rede para acessar umas às outras.

Projetar uma arquitetura de várias zonas com failover para alta disponibilidade

Para tornar seu aplicativo resiliente a falhas zonais, arquitete-o para usar pools de recursos distribuídos em várias zonas com replicação de dados, balanceamento de carga e failover automatizado entre zonas. Execute réplicas zonais de cada camada da pilha de aplicativos e elimine todas as dependências entre zonas na arquitetura.

Replicar dados entre regiões para recuperação de desastres

Replicar ou arquivar dados em uma região remota para permitir a recuperação de desastres em caso de interrupção regional ou perda de dados. Quando a replicação é usada, a recuperação é mais rápida porque os sistemas de armazenamento na região remota já têm dados quase atualizados, além da possível perda de uma pequena quantidade de dados devido ao atraso de replicação. Quando você usa o arquivamento periódico em vez da replicação contínua, a recuperação de desastres envolve a restauração de dados de backups ou arquivos em uma nova região. Este procedimento geralmente resulta em um tempo de inatividade maior do que a ativação de uma réplica de banco de dados atualizada continuamente, podendo envolver mais perda de dados devido ao intervalo de tempo entre operações de backup consecutivas. Seja qual for a abordagem usada, toda a pilha de aplicativos precisa ser reimplantada e iniciada na nova região, e o serviço ficará indisponível enquanto isso está acontecendo.

Para uma discussão detalhada sobre técnicas e conceitos de recuperação de desastres, consulte Como arquitetar a recuperação de desastres para interrupções na infraestrutura em nuvem.

Projetar uma arquitetura multirregional para resiliência a interrupções regionais

Se o serviço precisa ser executado continuamente, mesmo no caso raro em que uma região inteira falha, crie-o para usar pools de recursos de computação distribuídos em diferentes regiões. Execute réplicas regionais de cada camada da pilha de aplicativos.

Use a replicação de dados em regiões e o failover automático quando uma região ficar inativa. Alguns serviços do Google Cloud têm variantes multirregionais, como o Spanner. Para ser resistente a falhas regionais, use esses serviços multirregionais no seu design sempre que possível. Para mais informações sobre regiões e disponibilidade de serviços, consulte Locais do Google Cloud.

Evite a dependências entre regiões para que a amplitude do impacto de uma falha no nível da região seja limitada a essa região.

Elimine pontos únicos de falha regionais, como um banco de dados primário de região única que possa causar uma interrupção global quando estiver inacessível. Arquiteturas multirregionais costumam custar mais. Portanto, considere a necessidade de negócios em relação ao custo antes de adotar essa abordagem.

Para mais orientações sobre como implementar redundância em domínios de falha, consulte o artigo de pesquisa arquétipos de implantação para aplicativos em nuvem (PDF em inglês).

Eliminar gargalos de escalonabilidade

Identifique os componentes do sistema que não podem ultrapassar os limites de recursos de uma única VM ou zona. Alguns aplicativos escalonam verticalmente. Nesse caso, você adiciona mais núcleos de CPU, memória ou largura de banda de rede a uma única instância de VM para processar o aumento na carga. Esses aplicativos têm limites absolutos de dimensionamento, e geralmente exigem configuração manual para lidar com o crescimento.

Se possível, recrie esses componentes para escalonamento horizontal, como com fragmentação ou particionamento, em VMs ou zonas. Para lidar com o crescimento do tráfego ou do uso, adicione mais fragmentos. Use tipos de VM padrão que possam ser adicionados automaticamente para processar aumentos na carga por fragmento. Para mais informações, consulte Padrões para aplicativos escalonáveis e resilientes.

Se não for possível redesenhar o aplicativo, será possível substituir componentes gerenciados por você por serviços de nuvem totalmente gerenciados que foram projetados para escalonar horizontalmente sem ação do usuário.

Reduzir os níveis de serviço de maneira suave se estiverem sobrecarregados

Projete seus serviços para tolerarem a sobrecarga. Os serviços devem detectar a sobrecarga e retornar respostas de baixa qualidade para o usuário ou eliminar parcialmente o tráfego, não falhando completamente diante da sobrecarga.

Por exemplo, o serviço pode responder a solicitações de usuários com páginas da Web estáticas e desativar temporariamente o comportamento dinâmico, que é mais caro de processar. Esse comportamento é detalhado no padrão de failover quente do Compute Engine para o Cloud Storage. O serviço também pode permitir operações somente leitura e desativar temporariamente as atualizações de dados.

Os operadores devem ser notificados para corrigir a condição de erro quando um serviço degrada.

Evitar e reduzir picos de tráfego

Não sincronize solicitações entre clientes. Muitos clientes que enviam tráfego ao mesmo tempo causam picos de tráfego que podem causar falhas em cascata.

Implementar estratégias de mitigação de picos do lado do servidor, como limitação, enfileiramento, redução de carga ou disjuntor, degradação suave e priorização de solicitações críticas.

As estratégias de mitigação no cliente incluem limitação do lado do cliente e espera exponencial com instabilidade.

Remover e validar entradas

Para evitar entradas incorretas, aleatórias ou mal-intencionadas que causem falhas temporárias de serviço ou violações de segurança, remova e valide parâmetros de entrada para APIs e ferramentas operacionais. Por exemplo, a Apigee e o Google Cloud Armor podem ajudar a proteger contra ataques de injeção.

Use regularmente o teste de imprecisão, em que um arcabouço de testes chama intencionalmente APIs com entradas aleatórias, vazias ou muito grandes. Realize esses testes em um ambiente de teste isolado.

As ferramentas operacionais precisam validar automaticamente as alterações de configuração antes que as mudanças sejam implementadas, e rejeitar as alterações se a validação falhar.

Falhar com segurança para preservar a função

Se houver uma falha devido a um problema, os componentes do sistema precisarão falhar de forma que o sistema geral continue funcionando. Esses problemas podem ser um bug de software, entrada ou configuração incorreta, uma interrupção de instância não planejada ou erro humano. O que o processo de serviços ajuda a determinar se você precisa ser excessivamente permissivo ou excessivamente simplista, em vez de excessivamente restritivo.

Considere os cenários de exemplo a seguir e como responder a falhas:

  • Geralmente, é melhor que um componente de firewall com configuração ruim ou vazia falhe ao abrir e permita que tráfego de rede não autorizado passe por um curto período enquanto o operador corrige o erro. Esse comportamento mantém o serviço disponível, em vez de falhar e fechar 100% do tráfego. O serviço precisa depender de verificações de autenticação e autorização mais profundas na pilha do aplicativo para proteger áreas confidenciais enquanto todo o tráfego passa.
  • No entanto, é melhor que um componente do servidor de permissões controle o acesso aos dados do usuário para que ele não seja fechado e bloqueie todo o acesso. Esse comportamento causa uma interrupção do serviço quando a configuração está corrompida, mas evita o risco de vazamento de dados confidenciais do usuário caso o erro seja ignorado.

Em ambos os casos, a falha precisa gerar um alerta de alta prioridade para que um operador possa corrigir a condição de erro. Os componentes de serviço devem errar no lado em que o erro foi ignorado, a menos que apresentem riscos extremos para a empresa.

Projetar chamadas de API e comandos operacionais para serem repetidos

As APIs e ferramentas operacionais precisam tornar as invocações as mais seguras possíveis. Uma abordagem natural para muitas condições de erro é tentar novamente a ação anterior, mas nem sempre é possível saber se a primeira tentativa foi bem-sucedida.

A arquitetura do sistema precisa tornar as ações idempotentes. Se você executar a ação idêntica em um objeto duas ou mais vezes em sequência, ela vai produzir os mesmos resultados que uma única invocação. As ações não idempotentes exigem código mais complexo para evitar uma corrupção do estado do sistema.

Identificar e gerenciar dependências de serviço

Os designers e proprietários de serviços precisam manter uma lista completa de dependências em outros componentes do sistema. O projeto de serviço também precisa incluir recuperação de falhas de dependência ou degradação suave se a recuperação completa não for viável. Considere as dependências nos serviços de nuvem usados pelo sistema e pelas dependências externas, como APIs de serviço de terceiros, reconhecendo que todas as dependências do sistema têm uma taxa de falha diferente de zero.

Ao definir metas de confiabilidade, reconheça que o SLO de um serviço é matematicamente restrito pelos SLOs de todas as dependências críticas. Não é possível ser mais confiável do que o SLO mais baixo de uma das dependências. Para mais informações, consulte o cálculo de disponibilidade do serviço.

Dependências de inicialização

Os serviços se comportam de maneira muito diferente quando são iniciados em comparação com o comportamento de estado estável. As dependências de inicialização podem ser significativamente diferentes das dependências de ambiente de execução de estado estável.

Por exemplo, na inicialização, um serviço pode precisar carregar informações do usuário ou da conta de um serviço de metadados do usuário que raramente invoca novamente. Quando muitas réplicas de serviço são reiniciadas após uma falha ou manutenção de rotina, elas podem aumentar consideravelmente a carga nas dependências de inicialização, principalmente quando os caches estão vazios e precisam ser preenchidos novamente.

Teste a inicialização do serviço sob carga e provisione as dependências de inicialização conforme necessário. Pense em um design para degradação suave salvando uma cópia dos dados que ele recupera de dependências de inicialização críticas. Esse comportamento permite que o serviço seja reiniciado com dados potencialmente desatualizados em vez de impedir a inicialização quando uma dependência crítica tiver uma interrupção. O serviço poderá carregar dados recentes, quando possível, para voltar à operação normal.

As dependências de inicialização também são importantes quando você inicializa um serviço em um novo ambiente. Projete a pilha de aplicativos com uma arquitetura em camadas, sem dependências cíclicas entre elas. As dependências cíclicas podem parecer toleráveis porque não bloqueiam alterações incrementais em um aplicativo. No entanto, as dependências cíclicas podem dificultar ou impossibilitar a reinicialização depois que um desastre remove toda a pilha de serviço.

Minimize as dependências críticas

Minimize o número de dependências críticas para o serviço, ou seja, outros componentes com falha que inevitavelmente causará interrupções no serviço. Para tornar seu serviço mais resiliente a falhas ou lentidão em outros componentes de que depende, considere as seguintes técnicas e princípios de design de exemplo para converter dependências críticas em dependências não críticas:

  • Aumente o nível de redundância nas dependências críticas. A adição de mais réplicas diminui a probabilidade de um componente inteiro ficar indisponível.
  • Use solicitações assíncronas para outros serviços em vez de bloquear em uma resposta ou use mensagens de publicação/assinatura para separar solicitações de respostas.
  • Armazene em cache as respostas de outros serviços para recuperar a indisponibilidade de curto prazo das dependências.

Para tornar as falhas ou lentidão no serviço menos prejudiciais para outros componentes que dependem dele, considere os seguintes exemplos de técnicas e princípios de design:

  • Use filas de solicitações priorizadas e dê prioridade maior às solicitações em que um usuário aguarda uma resposta.
  • Exiba as respostas de um cache para reduzir a latência e o carregamento.
  • Falhar com segurança para preservar a função.
  • Faça uma degradação suave quando houver sobrecarga no tráfego.

Garantir que todas as alterações possam ser revertidas

Se não houver uma forma bem definida de desfazer certos tipos de mudanças em um serviço, mude o design do serviço para aceitar a reversão. Teste os processos de reversão periodicamente. As APIs de cada componente ou microsserviço precisam ter versão e compatibilidade com versões anteriores, para que a geração anterior de clientes continue funcionando corretamente à medida que a API evolui. Esse princípio de design é essencial para permitir o lançamento progressivo de alterações da API, com reversão rápida quando necessário.

A reversão pode ser cara para implementar aplicativos em dispositivos móveis. A Configuração remota do Firebase é um serviço do Google Cloud para facilitar a reversão de recursos.

Não é possível reverter as alterações de esquema do banco de dados. Então execute-as em várias fases. Projete cada fase para permitir solicitações seguras de leitura e atualização de esquema pela versão mais recente do aplicativo e pela versão anterior. Essa abordagem de design permite reverter com segurança se houver um problema com a versão mais recente.

Recomendações

Para aplicar a orientação no framework de arquitetura ao seu próprio ambiente, siga estas recomendações:

  • Implemente a espera exponencial com ordem aleatória na lógica de repetição de erro dos aplicativos clientes.
  • Implemente uma arquitetura multirregional com failover automático para alta disponibilidade.
  • Use o balanceamento de carga para distribuir solicitações de usuários entre fragmentos e regiões.
  • Projete o aplicativo para degradar suavemente em caso de sobrecarga. Permita respostas parciais ou forneça funcionalidade limitada em vez de falhar completamente.
  • Estabeleça um processo baseado em dados para o planejamento de capacidade e use testes de carga e previsões de tráfego para determinar quando provisionar recursos.
  • Estabeleça procedimentos de recuperação de desastres e teste-os periodicamente.

A seguir

Explore outras categorias no Framework de arquitetura, como design do sistema, excelência operacional e segurança, privacidade e conformidade.