Erro 502 em arquiteturas distribuídas

Circuit Breaker: Mitigando problemas de escalabilidade em uma API

Neste artigo, discutiremos sobre um problema comum em arquiteturas distribuídas: a falta de escalabilidade em um serviço quando combinado com uma API Gateway desprovida de limitação de taxa (rate limit). Explicaremos como esse cenário pode resultar em erros 502 para os clientes e como podemos mitigar esse problema com a implementação de práticas recomendadas, como rate limit no Gateway, controle de transações por segundo (TPS) no serviço de sessão, health check para validar as dependências, escalabilidade do serviço de sessão com base na carga de conexões, tratamento de erros e mensagens adequadas para o cliente.

O problema

Imaginemos a seguinte situação: uma aplicação cliente precisa acessar um serviço de sessão para realizar gerar registro de identificação de usuários. Para intermediar as chamadas entre o cliente e o serviço de sessão oferecendo recursos de autorização, uma API Gateway é utilizada. Entretanto, tanto a API Gateway quanto o cliente e o serviço de sessão apresentam limitações de implementação que resultam em uma experiência negativa para o usuário.

Sem a configuração de rate limit na API Gateway, os clientes são capazes de fazer chamadas recursivas em alta frequência para o serviço de sessão. Isso pode ocorrer, por exemplo, quando um cliente precisa obter informações de sessão em uma sequência de requisições rápidas e consecutivas. No entanto, o serviço de sessão não possui a capacidade de escalonamento adequada para lidar com essa demanda. Além disso, quando o serviço de sessão está sobrecarregado, ele retorna um erro 502 (Bad Gateway) para a API Gateway, indicando que não pode processar a solicitação devido à sua própria incapacidade de lidar com a carga. E como já dito, outros problemas como a própria implementação no cliente, pode prejudicar ainda mais esse cenário.

A sequência de eventos é a seguinte: o cliente realiza uma chamada para a API Gateway, que, por sua vez, redireciona a requisição para o serviço de sessão. Quando o serviço de sessão está sobrecarregado, ele retorna um erro 502 para a API Gateway. Essa resposta é então repassada ao cliente, indicando que o serviço não está disponível temporariamente devido a uma sobrecarga (algo que deveria ser evitado a todo custo em uma arquitetura distribuída).

Ilustrando o problema

Abaixo vou propor um cenário com vários problemas de implementação, desde o cliente, até as camadas mais baixas do servidor e na sequência exploramos as oportunidades.
Perceba que quando o cliente gera um volume de carga muito grande, nesse caso um push para 25 mil usuários em intervalos de 10 minutos, essa carga vai diretamente para o serviço de sessão, que mesmo se considerarmos estar preparado para processar um máximo de 9k por minuto, quebraria pois não teria tempo de escalar e atender os 25k que chegou. Esse cenário faria com que as instâncias do serviço de sessão fossem sobrecarregadas (mesmo cenário que ocorre em um DDOS) e ficassem indisponíveis, retornando para o cliente erro 502.

Mitigando o problema

Para evitar esse problema, existem várias práticas que podem ser adotadas:

  1. Configurar rate limit na API Gateway: A limitação de taxa permite controlar o número de solicitações por unidade de tempo que um cliente pode fazer à API. Ao definir um limite razoável para o número de solicitações (essa capacidade do serviço e sessão pode ser descoberto com testes de carga, usando Jmeter, por exemplo), a API Gateway evita sobrecarregar o serviço de sessão com um grande número de conexões simultâneas. Aqui é importante se preocupar em usar algoritmos inteligentes para analisar o comportamento de escala do serviço de sessão, pois imagine que uma instância processa 30 requisições por minuto, 2 processariam 60, ou seja, combinar uma estratégia de balanceamento com um algoritmo de limitação de taxa inteligente, já traria bons resultados. O que nos leva ao passo 2.
  2. Controlar TPS no serviço de sessão: Implementar um controle de transações por segundo no serviço de sessão permite regular o fluxo de solicitações recebidas. Isso garante que o serviço possa processar as requisições em um ritmo adequado, evitando sobrecargas e quedas de desempenho. Considere que o serviço de sessão possui dependências, como um banco de dados. Se esse banco de dados atingir seu limite de conexões, isso gerará problemas para o serviço de sessão. Então é importante entender essas limitações das dependências do serviço de sessão e informar aos consumidores. O que nos leva ao passo 3.
  3. Realizar health checks no serviço de sessão: A implementação de verificações regulares de integridade (health checks) no serviço de sessão permite identificar falhas ou gargalos em suas dependências, como o banco de dados. Isso permite uma resposta rápida a problemas, como redirecionar as solicitações para uma réplica em funcionamento ou acionar uma ação corretiva. No caso específico mencionado, o health check pode ser configurado para retornar um código de erro 429 (Too Many Requests) quando o serviço de sessão estiver sobrecarregado. Permitindo assim que o serviço de sessão tenha tempo de se recuperar ou escalar, não tendo de lidar com novas requisições. O que nos leva ao passo 4.
  4. Escalonar o serviço de sessão com base na carga: Monitorar o volume de conexões ativas no serviço de sessão e definir regras para o escalonamento automático das instâncias é fundamental para lidar com demandas crescentes. Quando um determinado limite de conexões é atingido, novas instâncias podem ser adicionadas para distribuir a carga e evitar a sobrecarga do sistema. Porém, as requisições que falharam não podem simplesmente falhar e comprometerem o cliente. O que nos leva ao passo 5.
  5. Implementar tratamento de erros e retries no cliente (e/ou no Gateway): Ao encontrar um erro 429, indicando que o serviço de sessão está sobrecarregado, o gateway pode fazer novas tentativas em espaços de tempo fixos e curtos (até 3 tentativas em intervalos de 1 segundo, por exemplo) e o cliente pode implementar um mecanismo de retry com um tempo de espera adequado entre as tentativas (e incremental), digamos que até 3 tentativas onde a primeira depois de 3 segundos (lembra que o Gateway colocamos 3*1?, então é uma boa forma de começar no cliente), a segunda depois de 6 e a terceira depois de 9 segundos. Isso permite uma experiência de usuário mais suave e a possibilidade de a operação ser bem-sucedida em tentativas subsequentes. Para o usuário, será como se apenas tivesse demorado mais para receber a resposta ou uma percepção de "internet lenta". Mas podemos melhorar ainda mais essa experiência. O que nos leva ao passo 6.
  6. Fornecer mensagens adequadas ao cliente durante retries: Durante o processo de retry, é importante fornecer mensagens claras ao cliente, informando-o sobre a tentativa em andamento e a possibilidade de sucesso nas próximas tentativas. Isso ajuda a evitar frustrações e melhora a experiência do usuário. O gmail tem essa implementação quando percebe que está offline.

Abaixo uma ilustração destacando os problemas mencionados na primeira imagem do artigo:



Em conclusão, a falta de escalabilidade em um serviço quando combinado com uma API Gateway desprovida de rate limit pode resultar em erros 502 para os clientes. No entanto, adotando práticas como a configuração de rate limit no Gateway, controle de TPS no serviço, health checks para validar as dependências, escalabilidade com base na carga de conexões, tratamento de erros e mensagens adequadas para o cliente, é possível mitigar esses problemas. Garantir a escalabilidade e a estabilidade do sistema é essencial para fornecer uma experiência consistente e confiável aos usuários finais.