Padrão de limitação

Limite os recursos que uma instância de aplicativo, um locatário individual ou um serviço inteiro podem consumir. Isso permite que o sistema funcione e atenda aos seus SLOs (objetivos de nível de serviço) sob carga súbita ou sustentada.

Contexto e problema

A carga em um aplicativo de nuvem varia ao longo do tempo com base em usuários ativos e suas atividades. Mais usuários se inscrevem durante o horário comercial e o sistema executa análises computacionalmente caras no final de cada mês. Explosões repentinas também ocorrem. Se a demanda de processamento exceder a capacidade disponível, o sistema diminuirá ou falhará. Quando o sistema tem um nível de serviço acordado, essa falha viola o SLO.

Várias estratégias lidam com cargas variadas, dependendo das metas de negócios do aplicativo. Uma estratégia é o dimensionamento automático, que corresponde aos recursos provisionados ao custo atual de demanda e controles. Mas o provisionamento de novos recursos leva tempo e adiciona custo. A demanda que excede o crescimento da capacidade ou o orçamento cria um déficit de recursos.

Solução

Uma alternativa ao escalonamento automático é limitar o uso de recursos e restringir as solicitações quando o uso ultrapassar esse limite. A carga de trabalho monitora seu próprio uso de recursos e limita as solicitações de um ou mais usuários quando o uso excede o limite. O sistema continua funcionando e cumprindo seus SLOs.

Limitação é um loop de controle, não uma única decisão de admissão. O sistema precisa de sinais de baixa latência em três camadas: utilização da infraestrutura, estado da aplicação e contadores por principal. Ele mede continuamente a saturação, impõe limites em limites bem definidos e adapta esses limites à medida que os padrões de tráfego mudam. A sobrecarga é um modo operacional normal do qual um sistema maduro detecta e se recupera. A limitação fornece recursos de autopreservação em sua carga de trabalho.

O sistema pode implementar várias estratégias de limitação de taxa ou relacionadas:

  • Limites de taxa por principal: Rejeite solicitações de um usuário que já excedeu a taxa configurada dentro de uma janela definida. Essa estratégia exige que o sistema atribua cada solicitação a uma entidade de segurança e contabilize o uso de recursos em relação a essa entidade de segurança. Para cargas de trabalho multilocatários, consulte Medir o consumo de cada locatário.

  • Degradação gradual de funcionalidades: Desative ou degrade funcionalidades não essenciais para que as funcionalidades essenciais disponham de recursos suficientes. Essa estratégia troca a integridade da resposta pela disponibilidade. Por exemplo, um aplicativo de streaming de vídeo pode cair para uma resolução mais baixa.

  • Nivelamento de carga: Suavize o volume de atividade usando uma fila. Em um ambiente multilocatário, o nivelamento reduz o desempenho de cada locatário. Quando os inquilinos tiverem SLAs (acordos de nível de serviço) diferentes, processe imediatamente o trabalho dos inquilinos de maior valor e retenha o trabalho de menor prioridade até que o backlog diminua. Implemente essa abordagem usando o padrão fila de prioridade ou expondo pontos de extremidade separados para cada camada de prioridade.

  • Adiamento baseado em prioridade: Adiar operações em nome de aplicativos ou locatários de menor prioridade. Suspender ou limitar as operações e retornar uma exceção informando ao locatário para tentar novamente mais tarde.

  • Limites de taxa de saída: Restrinja suas próprias chamadas de saída quando uma dependência externa falhar ou retornar erros. Reduza o número de solicitações em andamento para evitar sobrecarregar os logs e os custos de novas tentativas em uma dependência instável. Restaure o fluxo de solicitação normal após a recuperação da dependência. Por exemplo, o NServiceBus implementa essa funcionalidade.

O gráfico a seguir mostra o uso de recursos (uma combinação de memória, CPU, largura de banda e outros fatores) ao longo do tempo para um aplicativo que usa três recursos, rotulados como A, B e C. Um recurso é uma área específica da funcionalidade, como um componente que executa um conjunto específico de tarefas, um pedaço de código que executa um cálculo complexo ou um elemento que fornece um serviço como um cache na memória.

Gráfico que mostra o uso de recursos em relação ao tempo para aplicativos executados em nome de três usuários.

Um grafo de linha plota a utilização de recursos no eixo y em relação ao tempo no eixo x. Três linhas coloridas representam o Recurso A, o Recurso B e o Recurso C, com a linha mais baixa do Recurso A, a linha do Recurso B no meio e a linha do Recurso C mais alta. Uma linha horizontal sólida próxima à parte superior do gráfico marca a capacidade máxima e uma linha horizontal tracejada abaixo dela marca o limite suave de utilização de recursos. Duas linhas tracejadas verticais marcam os instantes T1 e T2. Antes de T1, as linhas das três funcionalidades oscilam, e a linha da Funcionalidade C sobe e cruza o limite flexível. Em T1, a linha da Funcionalidade B cai para zero e permanece em zero até T2 porque a Funcionalidade B é suspensa para liberar recursos para a Funcionalidade A e a Funcionalidade C. A linha da Funcionalidade C volta a ficar abaixo do limite flexível entre T1 e T2, enquanto a Funcionalidade A continua normalmente. No T2, o recurso B é retomado e todas as três linhas continuam a flutuar abaixo do limite suave.

O gráfico é um gráfico de áreas empilhadas. A área abaixo da linha do Recurso A mostra os recursos que o Recurso A consome, a área entre as linhas do Recurso A e do Recurso B mostra os recursos que o Recurso B consome e a área entre as linhas do Recurso B e do Recurso C mostra os recursos que o Recurso C consome. A linha do recurso C fica na parte superior da pilha, portanto, ela também mostra o uso total de recursos do sistema ao longo do tempo.

O gráfico mostra a degradação gradual de funcionalidades. Pouco antes do tempo T1, o uso total de recursos se aproxima do limite e corre o risco de esgotar a capacidade disponível. O recurso B é menos crítico que o Recurso A ou o Recurso C, portanto, o sistema desativa o Recurso B e libera seus recursos. Entre os horários T1 e T2, o Recurso A e o Recurso C continuam normalmente. Até o momento T2, o uso total de recursos cai o suficiente para reativar a Funcionalidade B.

Você pode combinar dimensionamento automático, degradação gradual e limitação de taxa para manter os aplicativos responsivos e em conformidade com os SLAs. Quando você espera que a demanda permaneça alta, a limitação de taxa mantém a estabilidade enquanto o sistema aumenta a capacidade. Após a conclusão desse aumento de capacidade, o sistema restabelece a funcionalidade completa.

O gráfico a seguir mostra o uso total de recursos ao longo do tempo e como a limitação de taxa se combina com o dimensionamento automático e outros mecanismos compensatórios.

Gráfico que mostra os efeitos da combinação entre limitação e dimensionamento automático.

Um grafo de linha plota a utilização de recursos para todos os aplicativos no eixo y em relação ao tempo no eixo x. Duas linhas de referência horizontais marcam o limite flexível de utilização de recursos e a capacidade máxima antes do dimensionamento automático. Uma linha horizontal mais alta, que começa no momento T2, marca a capacidade máxima após o dimensionamento automático. A linha de utilização sobe e flutua ao longo do tempo. Ele ultrapassa o limite suave no momento T1, que é o ponto em que o dimensionamento automático começa. Entre T1 e T2, o sistema é restringido enquanto ocorre o escalonamento automático, e a utilização permanece abaixo da capacidade máxima anterior ao escalonamento automático. No instante T2, o escalonamento automático é concluído, a limitação de taxa é reduzida, e a linha de utilização dá um salto e continua flutuando abaixo da nova capacidade máxima, agora mais alta.

No momento T1, o sistema atinge o limite flexível e começa a escalar horizontalmente. Se novos recursos não chegarem a tempo, a demanda poderá esgotar os recursos existentes e o sistema poderá falhar. A limitação rejeita solicitações em excesso durante a expansão para manter o uso de recursos abaixo do limite rígido e, em seguida, eleva essas restrições depois que a nova capacidade fica online.

Dica

Os controles de Edge e o padrão de limitação de taxa lidam com problemas diferentes. Controles de borda, como o Azure DDoS Protection e as regras de limitação de taxa do WAF (firewall de aplicativo Web), operam na borda da rede e descartam o tráfego volumétrico ou malicioso antes que ele chegue ao seu aplicativo. O padrão Throttling é executado na sua aplicação e controla o tráfego legítimo de acordo com os limites definidos pela aplicação. Use ambas as camadas juntas. A proteção contra DDoS não impede que um usuário legítimo sobrecarregue seu serviço e a limitação do aplicativo não absorve um ataque volumetrico.

Problemas e considerações

Considere os seguintes pontos ao decidir como implementar esse padrão:

  • Tome decisões de limitação mais cedo. A limitação é uma decisão arquitetônica que afeta todo o sistema. Reajustá-lo mais tarde é caro.

  • Ajuste os limites de limitação de taxa ao componente que satura primeiro.

    A taxa de requisições é a métrica mais comum de limitar, mas o verdadeiro gargalo geralmente está nas requisições simultâneas em andamento, na profundidade da fila, na utilização de CPU ou memória, ou nos limites da própria dependência subsequente. Um limite de solicitações por segundo não protege um sistema cujo gargalo está na simultaneidade em um ponto de fan-out.

    Em cada ponto de aplicação da limitação de taxa, como o gateway, o serviço, uma partição ou uma dependência a jusante, identifique o que satura primeiro e defina o limite com base nessa dimensão. Para obter proteção associada à simultaneidade em pontos de saída, consulte o padrão Bulkhead, que complementa a limitação.

  • Escolha um algoritmo de limitação intencionalmente. Ajuste-a à tolerância do componente que você está protegendo.

    Algoritmo Comportamento e melhor ajuste
    balde de tokens Dá suporte a intermitências até um tamanho configurado e impõe uma taxa de recarga constante. Use-o para gateways que precisam absorver picos de curta duração.
    Bucket com vazamento Emite a uma taxa constante. Use em back-ends que precisam de uma taxa de entrada constante.
    Janela fixa Simples de implementar, mas permite rajadas consecutivas nos limites da janela.
    Janela Deslizante Suaviza o problema nas bordas da janela em janelas fixas, ao custo de manter mais estado.
  • Decida quem o limite afeta. A limitação em um limite grosseiro, como um gateway regional, pode afetar muitos usuários não relacionados quando apenas alguns deles conduzem a carga.

  • Decida onde o contador reside quando um limite abrange vários nós. Os contadores locais são rápidos, mas subestimam a contagem quando o mesmo chamador acessa várias réplicas. Um contador centralizado em um repositório compartilhado como o Redis vê todas as solicitações, mas adiciona latência a cada decisão. Para aproximar uma taxa global, divida o limite entre réplicas e reconcilie periodicamente.

  • Tome decisões de limitação rapidamente. O sistema deve detectar o aumento da carga, reagir e voltar ao normal após a redução da carga. Esse processo requer instrumentação contínua de desempenho.

  • Reduza a carga de forma proativa, não à beira do colapso. Um limitador que somente rejeita depois de um componente saturar faz a latência disparar antes que os clientes percebam qualquer retrossinalização.

    À medida que a utilização se aproxima do limite rígido, comece a rejeitar uma fração crescente de solicitações. A rejeição antecipada sinaliza aos clientes para reduzirem o ritmo e impede o colapso na latência que limites abruptos geralmente provocam. Use a latência p99 em relação ao seu SLO como gatilho principal. A utilização média pode parecer saudável mesmo quando o p99 já ultrapassou o limite.

    Quando for possível identificar o valor da solicitação, descarte primeiro as tarefas de menor valor ou mais passíveis de nova tentativa. Para obter mais informações, consulte o padrão de Fila de Prioridade.

  • Retornar um código de status que informa ao cliente quando uma rejeição temporária é o resultado da limitação:

    • HTTP 429 (muitas solicitações): O chamador excede uma taxa de solicitação configurada em uma janela definida.
    • HTTP 503 (Serviço Indisponível): O serviço não pode lidar com a solicitação no momento, muitas vezes devido a um pico de carga inesperado.

    Inclua um cabeçalho HTTP Retry-After para que o cliente possa escolher uma estratégia de nova tentativa. Retorne contexto suficiente para o chamador tentar novamente de forma deliberada em vez de adivinhar. Por exemplo, nomeie o limite que o chamador excede, esclareça o escopo afetado ou sugira uma taxa que tenha êxito. Rejeições inexplicáveis não ajudam os chamadores a se adaptarem.

  • Propagar os sinais de sobrecarga das suas dependências em vez de absorvê-los. Um serviço que limita seus chamadores também deve honrar as respostas de limitação que recebe de suas próprias dependências downstream. Se o seu serviço ocultar uma resposta 429 ou 503 de um serviço posterior, tentando novamente em segundo plano ou retornando uma resposta HTTP 500 genérica (Erro Interno do Servidor), os clientes não conseguem reduzir o ritmo, as tentativas de repetição se multiplicam e a sobrecarga se propaga de volta para os serviços anteriores. O antipadrão Retry Storm descreve esse modo de falha. Propague a contrapressão aos chamadores anteriores para que toda a cadeia de chamadas reduza a carga em conjunto.

  • Torne a rejeição mais barata do que o trabalho que ela impede. Se recusar uma solicitação exigir autenticação pesada, análise profunda ou avaliação de política complexa, uma enxurrada de solicitações rejeitadas ainda poderá saturar o sistema. Rejeite a solicitação o mais cedo possível no pipeline de processamento da solicitação e faça testes de carga no próprio caminho de rejeição.

  • Planeje-se para os casos em que a limitação de taxa não consegue dar tempo suficiente para o dimensionamento automático. Se a demanda crescer mais rápido do que a entrada em operação de nova capacidade, mesmo um sistema com capacidade limitada pode falhar. Quando esse resultado for inaceitável, mantenha as reservas de capacidade maiores e configure o dimensionamento automático mais agressivo.

  • Não use o cache como um substituto para limitação. Um cache reduz a carga média na origem, mas não vincula a carga de pico. Cada erro de cache passa até a origem e, quando uma chave popular expira sob tráfego pesado, muitos chamadores podem correr para recarregá-la. Use cache para reduzir a carga normal e a limitação de taxa para limitar o impacto no pior caso. Para obter mais informações, consulte o padrão Cache-Aside.

  • Normalize os custos de recursos para operações diferentes porque geralmente não têm custos de execução iguais. Por exemplo, os limites de limitação podem ser maiores para operações de leitura e inferiores para operações de gravação. Ignorar o custo por operação pode esgotar a capacidade e criar um vetor de ataque.

  • Permita que a configuração de limitação de taxa seja alterada em tempo de execução. Quando ocorre uma carga incomum, você precisa ajustar os limites sem precisar fazer uma implantação. As implantações são lentas e arriscadas durante um incidente. O padrão do Repositório de Configuração Externo externaliza a configuração para que você possa alterá-la em runtime.

  • Considere limites adaptáveis em vez de limites estáticos. Alguns SDKs de limitação reagem a sinais de latência ou profundidade de fila para que o limite acompanhe as condições reais do componente. Sempre emparelhe um limitador adaptável com um máximo definido.

  • Revisite seus limites à medida que a carga de trabalho evolui. Os limitadores adaptativos não conseguem acompanhar todos os tipos de desvio, como mudanças nos SLOs, mudanças na capacidade das dependências ou alterações no custo por operação. Agende a revisão periódica do operador em relação a essas entradas.

Quando usar esse padrão

Use este padrão:

  • Para manter um sistema em conformidade com seus SLOs.

  • Para impedir que um único locatário monopolize os recursos do aplicativo.

  • Para lidar com picos de atividade.

  • Para limitar o nível máximo de recurso de que um sistema precisa.

  • Para reduzir cargas de trabalho computacionais de baixo valor em períodos de alta intensidade de carbono na rede elétrica.

Design de carga de trabalho

Avalie como usar o padrão de limitação no projeto de uma carga de trabalho para atender aos objetivos e princípios descritos nos pilares do Azure Well-Architected Framework. A tabela a seguir fornece diretrizes sobre como esse padrão dá suporte às metas de cada pilar.

Pilar Como esse padrão apoia os objetivos do pilar
As decisões de design de confiabilidade ajudam sua carga de trabalho a se tornar resiliente ao mau funcionamento e garantir que ela se recupere para um estado totalmente funcional após a ocorrência de uma falha. Você projeta os limites para ajudar a evitar o esgotamento dos recursos que pode levar ao mau funcionamento. Você também pode usar esse padrão como um mecanismo de controle em uma estratégia de degradação graciosa.

- RE:07 Autopreservação
As decisões de design de segurança ajudam a garantir a confidencialidade, integridade e disponibilidade dos dados e sistemas da sua carga de trabalho. Você pode projetar os limites para ajudar a evitar o esgotamento dos recursos que pode resultar do abuso na automação do sistema.

- SE:06 Controles de rede
- SE:08 Endurecimento de recursos
A Otimização de Custos concentra-se na manutenção e na melhoria do retorno sobre o investimento da carga de trabalho. Os limites impostos podem informar a modelagem de custos e podem ser diretamente vinculados ao modelo de negócios do seu aplicativo. Eles também colocam limites superiores claros na utilização, que podem ser levados em consideração no dimensionamento dos recursos.

- CO:02 Modelo de custos
- CO:12 Custos do dimensionamento
A Eficiência de Desempenho ajuda sua carga de trabalho a atender com eficiência às demandas por meio de otimizações no dimensionamento, nos dados e no código. Quando o sistema está sob alta demanda, esse padrão ajuda a mitigar o congestionamento que pode causar gargalos de desempenho. Você também pode usá-lo para evitar proativamente cenários de vizinhos barulhentos.

- PE:02 Planejamento de capacidade
- PE:05 Dimensionamento e particionamento

Se esse padrão introduzir compensações dentro de um pilar, considere-as em relação aos objetivos dos outros pilares.

Example

O diagrama a seguir mostra a limitação em um sistema multilocatário.

Diagrama que mostra a limitação em um aplicativo multilocatário.

Três usuários identificados à esquerda representam locatários de um aplicativo de pesquisas multilocatário: Adatum, Fabrikam e Contoso. Cada usuário envia solicitações por meio de um domínio personalizado específico do locatário, que o aplicativo usa para identificar o locatário. O Adatum envia 5 solicitações por segundo até surveys.adatum.com, a Fabrikam envia 10 solicitações por segundo até surveys.fabrikam.com e a Contoso envia 150 solicitações por segundo até surveys.contoso.com. À direita, a função web do aplicativo de pesquisas mede a taxa de solicitações por segundo de cada locatário. Os fluxos de requisição da Adatum e da Fabrikam são encaminhados para o aplicativo. O fluxo de solicitação da Contoso é bloqueado por um erro: resposta limitada porque a taxa excede o limite por locatário.

Usuários de várias organizações de locatário acessam um aplicativo hospedado na nuvem para preencher e enviar pesquisas. O aplicativo contém instrumentação que monitora a taxa na qual os usuários de cada locatário enviam solicitações.

Para impedir que os usuários de um locatário degradem a capacidade de resposta e a disponibilidade dos usuários em outros locatários, o aplicativo limita a taxa de solicitações por segundo que qualquer locatário pode enviar. O aplicativo bloqueia solicitações que excedem esse limite.

Próxima etapa