Marcio Cunha

Sistema fora do ar? Como desenhar circuit breakers resilientes

Descubra como os circuit breakers evitam quedas em cascata na nuvem. Proteja seus serviços dependentes com padrões de tolerância a falhas robustos.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • Circuit breakers interrompem chamadas repetidas a serviços instáveis para poupar recursos vitais
  • O estado aberto evita novas tentativas de conexão até que o sistema dependente recupere a estabilidade
  • Testes periódicos no estado semi-aberto garantem a retomada segura do tráfego sem sobrecarga imediata
  • Fallbacks inteligentes mantêm a aplicação funcional mesmo quando serviços secundários falham completamente
  • Ajustes finos de timeouts e limiares impedem falsos positivos em redes de alta volatilidade na nuvem

O perigo invisível das quedas em cascata na nuvem

Quando um banco de dados ou uma API externa de pagamento começa a responder lentamente, o comportamento padrão de grande parte das aplicações modernas é continuar insistindo. Na prática, isso significa que centenas de servidores continuam abrindo novas conexões simultâneas, esgotando rapidamente as filas de espera e a memória RAM dos servidores. Esse fenômeno destrutivo é conhecido como queda em cascata, onde uma única falha localizada derruba progressivamente todos os demais microsserviços interligados de uma arquitetura corporativa.

Para evitar que um pequeno soluço na rede se transforme em uma interrupção total do negócio, a engenharia de software moderna adotou o padrão de tolerância a falhas chamado circuit breaker ou disjuntor de circuito. Inspirado diretamente nos disjuntores elétricos das nossas residências, esse mecanismo monitora ativamente as chamadas de rede e desarma o fluxo assim que o número de erros ultrapassa um limite tolerável, cortando o tráfego antes que o estrago seja irreversível. Assim como detalhamos em nossa análise sobre arquiteturas tolerantes a falhas, isolar problemas evita que o sistema inteiro colapse por falta de contenção.

Como funcionam os três estados de um disjuntor de software

Para operar com precisão cirúrgica, um circuit breaker transita constantemente entre três estados distintos: Fechado, Aberto e Semi-Aberto. No estado fechado, que é a operação normal do dia a dia, todas as requisições passam livremente para o serviço de destino enquanto o componente contabiliza silenciosamente a proporção de falhas e sucessos em uma janela deslizante de tempo. Caso a taxa de erros ultrapasse, por exemplo, cinquenta por cento em dez segundos, o disjuntor muda imediatamente para o estado aberto, bloqueando de forma preventiva qualquer tentativa de chamada futura.

Quando o disjuntor está aberto, o código não perde tempo esperando a rede expirar ou esgotar timeouts; ele retorna instantaneamente um erro amigável ou um dado alternativo conhecido como fallback. Após um intervalo de tempo pré-determinado, o componente transita para o estado semi-aberto, permitindo que apenas uma requisição de teste passe para verificar se o serviço dependente já se recuperou. Se essa única chamada obtiver sucesso, o circuito se fecha novamente e a operação normal é restabelecida, caso contrário, ele retorna ao estado aberto por mais tempo.

Implementando um circuit breaker na prática com código

Abaixo apresentamos uma implementação funcional e direta de um circuit breaker em código para ilustrar a lógica por trás das bibliotecas comerciais que você utiliza no dia a dia. O algoritmo controla o histórico recente de falhas e decide se deve executar a função remota ou disparar uma resposta de proteção imediata:

class SimpleCircuitBreaker { constructor(threshold, timeout) { this.threshold = threshold; this.timeout = timeout; this.state = 'CLOSED'; this.failureCount = 0; this.nextAttempt = Date.now(); } async execute(action) { if (this.state === 'OPEN') { if (Date.now() > this.nextAttempt) { this.state = 'HALF-OPEN'; } else { throw new Error('Circuit is OPEN. Fast failing.'); } } try { const result = await action(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failureCount = 0; this.state = 'CLOSED'; } onFailure() { this.failureCount++; if (this.failureCount >= this.threshold) { this.state = 'OPEN'; this.nextAttempt = Date.now() + this.timeout; } } }

Esse modelo mental simples demonstra como a aplicação deixa de gastar recursos preciosos tentando falar com um servidor que claramente está fora do ar. Além disso, essa mesma preocupação com o uso eficiente de recursos aparece quando precisamos garantir a integridade dos dados em cenários complexos, tal como discutimos no guia sobre estratégias de resiliência e redundância em nuvem.

Estratégias de fallback e a experiência do usuário

Bloquear requisições para evitar a sobrecarga do sistema é apenas metade do trabalho, pois a aplicação ainda precisa decidir o que entregar para o usuário final. Se um microsserviço que retorna o catálogo de produtos de uma loja virtual falhar, exibir uma tela branca em branco ou um erro genérico de servidor destrói completamente a conversão de vendas e frustra o cliente. É exatamente nesse momento que entram as estratégias de fallback inteligentes, que fornecem respostas alternativas baseadas em dados em cache, catálogos estáticos ou mensagens informativas amigáveis.

Um bom fallback nunca deve mascarar totalmente um problema sistêmico a ponto de esconder alertas críticos da equipe de engenharia, mas deve preservar a dignidade da navegação sempre que possível. Em sistemas de recomendação de conteúdo, por exemplo, se o motor de inteligência artificial falhar, o sistema pode exibir os itens mais populares da semana armazenados localmente na memória. Dessa forma, a interface continua responsiva e o usuário consegue concluir suas tarefas principais sem perceber que nos bastidores uma dependência importante está temporariamente inoperante. Para aprofundar esses conceitos na prática em produção, confira também nossa análise técnica sobre Por que o modelo alucina e como criar barreiras em RAG.

Ajustes finos, falsos positivos e o futuro da resiliência

Configurar os parâmetros de um circuit breaker exige calibração cuidadosa baseada no tráfego real e nas características operacionais do seu ambiente de produção. Se o limite de falhas for rigoroso demais, oscilações momentâneas de rede podem desarmar o disjuntor sem necessidade real, gerando falsos positivos incômodos. Por outro lado, um limiar muito permissivo deixará a aplicação exposta por tempo demais, permitindo que quedas em cascata comprometam a estabilidade dos demais serviços da nuvem.

Em suma, desenhar disjuntores de circuito resilientes é um exercício contínuo de observabilidade, testes de carga e compreensão profunda dos limites de cada dependência do sistema. Combinar essas práticas de proteção com um monitoramento rigoroso garante que sua arquitetura permaneça elástica, estável e preparada para absorver falhas inevitáveis sem impactar a experiência de quem mais importa: o usuário final.