Um serviço de checkout consulta o catálogo, calcula o frete e chama o pagamento. Quando o serviço de frete começa a responder lentamente, cada requisição fica esperando até o timeout. Novas requisições continuam chegando, ocupam conexões e threads e aumentam a fila. Em pouco tempo, uma dependência degradada compromete também o checkout.
O Circuit Breaker interrompe temporariamente chamadas que têm grande chance de falhar. Em vez de insistir até esgotar recursos, a aplicação falha rápido, preserva sua própria capacidade e dá tempo para a dependência se recuperar.
O que é Circuit Breaker?
Circuit Breaker é um padrão de resiliência aplicado a chamadas que atravessam uma fronteira sujeita a falhas, como uma API remota, um banco de dados ou um provedor externo. Ele envolve a operação, registra resultados recentes e decide se uma nova chamada deve chegar à dependência.
O nome vem do disjuntor elétrico: quando existe uma condição perigosa, o circuito abre para impedir que o dano se espalhe. No software, abrir o circuito significa rejeitar novas tentativas imediatamente, sem executar a chamada remota.
O padrão não torna a dependência saudável e não garante que a operação será concluída. Ele limita o impacto enquanto a falha existe.
sem proteção
requisições → dependência lenta → timeouts → recursos presos → falha em cascata
com Circuit Breaker
requisições → circuito aberto → falha rápida ou fallback
→ dependência recebe tempo para se recuperar
Como funcionam os três estados?
O Circuit Breaker normalmente é representado por uma máquina de estados.
falhas acima do limite
┌────────────────────────────────────┐
│ ↓
FECHADO ABERTO
chamadas passam chamadas bloqueadas
↑ │
│ testes bem-sucedidos │ tempo de espera
│ ↓
└────────────────────────────── SEMIABERTO
poucas chamadas de teste
│
└── falhou → ABERTO
Fechado
No estado fechado, as chamadas passam normalmente. O Circuit Breaker registra resultados em uma janela recente: sucessos, falhas, timeouts e, em algumas implementações, chamadas lentas.
Quando há amostras suficientes e a taxa configurada ultrapassa o limite, o circuito abre. Usar uma quantidade mínima de chamadas evita que uma única falha em um serviço com pouco tráfego abra o circuito cedo demais.
Aberto
No estado aberto, o Circuit Breaker não chama a dependência. Ele devolve um erro controlado ou aciona um fallback imediatamente. Isso reduz espera, evita trabalho sem chance razoável de sucesso e alivia o sistema que está tentando se recuperar.
Depois de um período configurado, o circuito não deveria simplesmente liberar todo o tráfego. Ele passa ao estado semiaberto.
Semiaberto
No estado semiaberto, apenas um número limitado de chamadas de teste chega à dependência. Se elas indicarem recuperação, o circuito fecha e o tráfego normal retorna. Se falharem ou continuarem lentas, o circuito abre novamente.
Limitar essas chamadas é essencial. Se todas as instâncias liberarem todo o tráfego ao mesmo tempo, a tentativa de recuperação pode criar outro pico e derrubar a dependência novamente.
O que deve contar como falha?
Nem todo resultado diferente de sucesso significa que a dependência está indisponível. Um 400 Bad Request causado por dados inválidos não tende a melhorar depois de alguns segundos. Já timeouts, falhas de conexão e respostas 5xx podem indicar degradação operacional.
Uma política razoável costuma separar:
- Falhas técnicas, como timeout, conexão recusada e
5xx, que podem alimentar o Circuit Breaker; - Limites temporários, como
429 Too Many Requests, que exigem respeitarRetry-Aftere podem justificar proteção; - Erros de negócio ou do cliente, como saldo insuficiente, recurso inexistente ou entrada inválida, que normalmente não devem abrir o circuito;
- Cancelamentos do chamador, que precisam ser classificados conforme a causa e não atribuídos automaticamente à dependência.
Também pode ser útil medir chamadas lentas antes que elas virem timeout. Uma dependência que ainda responde, mas prende recursos por vários segundos, já pode ameaçar o serviço chamador.
Como definir os limites?
Uma configuração baseada apenas em “cinco erros seguidos” é simples, mas reage mal a volumes diferentes. Cinco falhas em dez chamadas representam um cenário diferente de cinco falhas em cem mil chamadas.
Circuit Breakers costumam combinar:
| Parâmetro | Pergunta que ele responde |
|---|---|
| Janela de observação | Quais chamadas recentes entram no cálculo? |
| Mínimo de chamadas | Já existe amostra suficiente para decidir? |
| Taxa de falhas | Qual proporção de erros abre o circuito? |
| Limite de lentidão | Quando uma chamada é considerada lenta? |
| Taxa de chamadas lentas | Quanto tráfego degradado é aceitável? |
| Tempo no estado aberto | Quando começar a testar a recuperação? |
| Chamadas no estado semiaberto | Quantas sondagens simultâneas serão permitidas? |
A janela pode considerar as últimas N chamadas ou um intervalo, como os últimos 30 segundos. A primeira mantém uma amostra de tamanho previsível; a segunda acompanha melhor mudanças ao longo do tempo, mas reage de forma diferente conforme o volume.
Não existe um conjunto universal de valores. Os limites devem partir do SLO, da latência normal, do volume, da capacidade da dependência e do custo de falhar rápido. Depois, precisam ser ajustados com telemetria e testes de falha.
Exemplo prático: consulta de frete
Considere um checkout que consulta um provedor de frete. A página pode continuar sem estimativa precisa e informar que o valor será calculado mais tarde. Portanto, existe um fallback seguro.
Uma política inicial, ainda sujeita a validação por métricas, poderia ser:
timeout por tentativa: 800 ms
janela: últimas 50 chamadas
amostra mínima: 20 chamadas
abre com: 50% de falhas
considera lenta acima de: 500 ms
abre também com: 70% de chamadas lentas
tempo aberto: 30 s
testes no estado semiaberto: 5 chamadas
O fluxo simplificado fica assim:
consultarFrete(cep, itens):
se circuito não permite chamada:
retornar estimativa indisponível
tentar:
resposta = provedor.calcular(cep, itens, timeout = 800 ms)
registrar sucesso e duração
retornar resposta
capturar erro técnico:
registrar falha
retornar estimativa indisponível
Se o circuito estiver aberto, o usuário recebe uma resposta rápida e explícita. O checkout não deve apresentar um valor inventado como se fosse definitivo. Dependendo da regra de negócio, pode oferecer retirada, ocultar temporariamente a opção ou permitir continuar e recalcular antes da confirmação.
Se esse mesmo padrão proteger uma autorização de pagamento, um fallback fictício seria perigoso. A resposta correta pode ser interromper a compra, informar indisponibilidade e preservar a chave de idempotência para uma tentativa posterior.
Circuit Breaker, timeout e retry
Esses mecanismos resolvem problemas diferentes e funcionam melhor quando formam uma política coerente.
Timeout limita cada espera
Sem timeout, uma chamada pode prender recursos indefinidamente e o Circuit Breaker demora a receber um resultado para contabilizar. O timeout deve refletir o orçamento de latência da operação inteira, não apenas um valor padrão da biblioteca.
Retry tenta superar falhas transitórias
Retry é adequado quando uma nova tentativa tem chance real de funcionar. Porém, cada retry multiplica carga. Três camadas com três tentativas cada podem provocar até 27 chamadas à dependência para uma única operação original.
Use poucas tentativas, backoff exponencial e jitter, respeite o orçamento total e faça retry apenas de operações seguras ou idempotentes. Quando o Circuit Breaker rejeitar a chamada, continuar tentando imediatamente apenas contorna a proteção.
Circuit Breaker interrompe insistências persistentes
O Circuit Breaker observa um conjunto de chamadas e impede novas tentativas quando a falha deixa de parecer isolada. Uma composição comum é:
requisição
→ timeout total da operação
→ Circuit Breaker
→ retry curto e limitado
→ chamada remota com timeout por tentativa
A ordem exata depende da biblioteca e do comportamento desejado. O ponto essencial é saber qual resultado cada camada observa. Se cada retry for contado como uma falha separada, o circuito pode abrir mais rápido; se apenas o resultado final for contado, ele reage à experiência percebida pelo chamador.
Fallback não é resposta falsa
Um fallback mantém uma função reduzida quando a dependência falha. Exemplos aceitáveis incluem:
- Mostrar dados de cache com aviso de que podem estar desatualizados;
- Ocultar recomendações sem impedir a compra;
- Enfileirar uma tarefa para processamento posterior;
- Trocar para um provedor independente, com limites próprios;
- Retornar indisponibilidade de forma rápida e compreensível.
Fallbacks também falham e podem aumentar o risco. Um cache antigo pode violar regras de preço; um provedor secundário pode depender da mesma infraestrutura; enfileirar sem limite apenas move a sobrecarga; devolver uma autorização fictícia corrompe o negócio.
O fallback precisa preservar as invariantes da operação. Às vezes, falhar claramente é a degradação mais segura.
Circuit Breaker não é Bulkhead nem Rate Limiter
Os padrões se complementam, mas não são intercambiáveis.
| Padrão | Protege contra | Decisão principal |
|---|---|---|
| Circuit Breaker | Dependência degradada ou indisponível | A chamada deve ser tentada agora? |
| Bulkhead | Esgotamento compartilhado de recursos | Quanto recurso esta dependência pode ocupar? |
| Rate Limiter | Excesso de tráfego em uma janela | Esta requisição cabe no limite permitido? |
| Timeout | Espera longa demais | Por quanto tempo esta tentativa pode durar? |
| Retry | Falha transitória | Vale fazer outra tentativa? |
Mesmo com o circuito fechado, uma explosão de chamadas concorrentes pode esgotar conexões antes que a taxa de falhas ultrapasse o limite. Um Bulkhead ou limitador de concorrência contém essa pressão. O Circuit Breaker reage ao resultado das chamadas; ele não controla sozinho quantas estão em andamento.
Escopo local ou estado compartilhado?
Em geral, cada instância da aplicação mantém seu próprio Circuit Breaker em memória. Isso evita uma coordenação remota no caminho crítico e permite que cada instância reaja ao que observa.
O custo é que os estados podem divergir. Uma instância nova começa sem histórico; com pouco tráfego por instância, a amostra pode demorar a atingir o mínimo; várias instâncias podem testar a recuperação ao mesmo tempo.
Compartilhar o estado parece resolver essas diferenças, mas adiciona latência, disponibilidade e consistência a um mecanismo criado justamente para proteger chamadas remotas. Antes de centralizar, avalie se métricas locais, limites de concorrência, balanceamento e uma quantidade pequena de sondagens já resolvem o problema.
Também defina o escopo com cuidado. Circuitos separados por dependência e operação costumam evitar que a falha de POST /payments bloqueie um GET /payment-methods saudável. Em sistemas multi-tenant, um cliente que excede sua cota não deveria abrir o circuito para todos os demais.
Observabilidade e operação
Um Circuit Breaker sem telemetria pode esconder uma indisponibilidade atrás de respostas rápidas. Observe pelo menos:
- Estado atual e cada transição, com motivo e horário;
- Taxas de sucesso, falha, timeout e chamadas lentas;
- Requisições bloqueadas pelo circuito;
- Duração no estado aberto e resultado das sondagens;
- Uso, sucesso e idade dos dados de fallback;
- Latência e saturação da dependência protegida;
- Impacto percebido pelo usuário e nos SLOs.
Alertar em toda transição pode gerar ruído quando o circuito oscila. Alertas úteis consideram duração, frequência, volume afetado e importância da operação. Um circuito que abre e fecha repetidamente pode indicar limites ruins, capacidade insuficiente ou recuperação prematura.
Recursos administrativos como abrir, fechar ou desativar manualmente ajudam em incidentes, mas precisam de controle de acesso, auditoria e expiração. Um override esquecido pode mascarar o estado real por horas.
Vantagens
- Contém falhas em cascata ao evitar trabalho com baixa chance de sucesso;
- Reduz a latência durante uma indisponibilidade porque falha rapidamente;
- Preserva conexões, threads, memória e capacidade do serviço chamador;
- Dá espaço para a dependência se recuperar sem uma tempestade contínua de tentativas;
- Torna a degradação e a recuperação estados explícitos e observáveis.
Limitações e trade-offs
| Decisão | Benefício | Custo ou risco |
|---|---|---|
| Abrir cedo | Protege rapidamente os recursos | Pode bloquear uma dependência ainda saudável |
| Abrir tarde | Tolera falhas pontuais | Permite mais timeouts e pressão antes de reagir |
| Tempo aberto longo | Dá mais espaço para recuperação | Prolonga a indisponibilidade percebida |
| Muitas sondagens | Gera evidência de recuperação mais rápido | Pode causar novo pico na dependência |
| Circuito muito amplo | Configuração e operação simples | Uma falha parcial bloqueia operações saudáveis |
| Fallback com cache | Mantém leitura disponível | Pode servir dados desatualizados |
Erros comuns
Contar todo erro como falha da dependência
Erros de validação e regras de negócio não indicam necessariamente degradação. Classifique os resultados para que tráfego inválido não abra o circuito.
Usar Circuit Breaker sem timeout
O circuito só aprende depois que as chamadas terminam. Sem limites de tempo, recursos podem ficar presos antes que o mecanismo reaja.
Fazer retries em várias camadas
Retries no cliente, gateway, serviço e SDK multiplicam chamadas e podem impedir a recuperação. Escolha a camada responsável e defina um orçamento único.
Usar o mesmo circuito para tudo
Dependências, endpoints, regiões e clientes podem falhar de forma independente. Um escopo amplo aumenta o raio de impacto de uma decisão incorreta.
Liberar todo o tráfego após o tempo aberto
Tempo decorrido não prova recuperação. Use o estado semiaberto com poucas sondagens e retome gradualmente.
Tratar o padrão como correção da causa
O Circuit Breaker limita danos, mas não corrige falta de capacidade, consultas lentas, vazamentos de conexão ou contratos instáveis. As transições devem orientar investigação e melhoria da dependência.
Perguntas de entrevista
- Quais problemas os estados fechado, aberto e semiaberto resolvem?
- Como distinguir uma falha técnica de um erro de negócio no cálculo do circuito?
- Por que timeout, retry e Circuit Breaker não são equivalentes?
- Como uma janela por quantidade difere de uma janela por tempo?
- O que pode acontecer quando todas as instâncias entram no estado semiaberto juntas?
- Quando um fallback com cache é seguro e quando ele viola regras de negócio?
- Por que o Circuit Breaker não substitui um Bulkhead ou limitador de concorrência?
- Quais métricas indicam que os limites estão muito sensíveis ou permissivos?
Próximos conteúdos
Continue por idempotência para tornar retries seguros e por Redis para entender cache, expiração e degradação quando uma dependência rápida fica indisponível. Depois, aprofunde retry com exponential backoff e jitter, Bulkhead, Rate Limiting e observabilidade para formar uma estratégia completa de resiliência.