Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

03 - Circuit Breaker (na nuvem)

#circuit-breaker#degradacao#fallback#resiliencia#service-mesh
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Circuit Breaker (na nuvem)

O Circuit Breaker é o padrão que impede que uma dependência falha transforme a falha dela em falha de todo o sistema, ao interromper temporariamente as chamadas para o destino degradado e responder com um caminho alternativo. O nome vem do disjuntor elétrico, que corta a corrente quando detecta sobrecarga para proteger a instalação; no software, o corte protege o chamador de ficar preso em timeouts e protege o destino de receber ainda mais tráfego em momento de fragilidade. Em contexto de nuvem, o padrão ganha duas dimensões: pode viver no código do cliente, por meio de bibliotecas de resiliência, e pode viver na infraestrutura, em service mesh e API gateway que aplicam a mesma lógica sem que o código saiba. A diferença entre as duas camadas define latência de reação, granularidade de controle e custo de operação. Em sistemas distribuídos com dezenas de dependências, o disjuntor é o que separa uma falha pontual de uma cascata de indisponibilidade.

Conceito e estados

O disjuntor opera em três estados bem definidos. No estado fechado, as chamadas fluem normalmente e o componente acumula estatísticas de erro e latência em uma janela deslizante. Quando a taxa de falha cruza o limiar configurado, o estado abre, e as chamadas deixam de ser enviadas, retornando imediatamente erro ou resposta de fallback. Após um intervalo de espera, o estado passa a meio aberto, em que um volume pequeno de requisições de prova atravessa para avaliar se o destino se recuperou; em caso de sucesso, fecha-se novamente, e em caso de falha, volta a abrir.

Essa máquina de estados resolve um problema de física do tráfego: sem corte, cada requisição falha após o timeout completo, ocupando threads, conexões e memória do chamador. Com o corte, a falha é reconhecida em milissegundos, e o chamador libera recursos para atender o resto do tráfego. O efeito colateral positivo é aliviar a dependência doente, dando a ela chance real de se recuperar em vez de afogar sob uma fila crescente.

  • Fechado (closed): tráfego normal, janela de estatísticas ativa, contagem de falhas em curso.
  • Aberto (open): chamadas rejeitadas na hora, fallback executado, alarme emitido para o time de plantão.
  • Meio aberto (half-open): sondagem limitada, transição automática conforme o resultado das provas.
  • Fallback: resposta alternativa definida por negócio, de cache a valor padrão, executada quando o circuito está aberto.

O problema que resolve

O cenário clássico de morte por dependência começa pequeno: o serviço de recomendação passa a responder em 8 segundos em vez de 80 milissegundos. Sem disjuntor, cada chamada do checkout espera o timeout completo, o pool de conexões enche, as threads bloqueiam, e o próprio checkout, que estava saudável, para de responder. O incidente então se espalha para quem chama o checkout, e a cascata atinge todo o front. Esse padrão de propagação é uma das causas mais comuns de indisponibilidade total a partir de um componente secundário.

O disjuntor confina esse dano em duas direções. Para o chamador, converte falha lenta em falha rápida e previsível, permitindo responder com degraded mode em vez de travar. Para o destino, reduz a pressão exatamente quando ele menos aguenta, porque o circuito aberto corta o fluxo de requisições que jamais seriam processadas a tempo. Em nuvem, onde dependências cruzam zonas, contas e empresas, essa proteção local é essencial, pois ninguém controla o tempo de recuperação do vizinho.

Há também o custo silencioso da falha lenta: enquanto o destino responde devagar, o chamador paga por recursos bloqueados, e em ambientes com autoscaling o sistema cresce para absorver capacidade gasta em espera, não em trabalho útil. O disjuntor devolve essa capacidade ao sistema em segundos, e o efeito aparece na conta no fim do mês, não apenas nos gráficos de latência.

Funcionamento interno

A implementação precisa decidir quando abrir com base em sinais confiáveis. Os sinais mais usados são taxa de falha percentual na janela, número mínimo de requisições para calcular a taxa, e latência lenta tratada como falha, porque um sucesso em 10 segundos também derruba o chamador. Limiares mal escolhidos geram flapping, o circuito abre e fecha em ciclos rápidos, amplificando instabilidade; limiares bem calibrados combinam volume mínimo, taxa alvo e tempo de espera coerentes com o SLA da dependência.

# Configuração de circuit breaker por dependência (estilo resilience4j)
circuitbreakers:
  instances:
    pagamentos:
      slidingWindowType: COUNT_BASED      # janela por número de chamadas
      slidingWindowSize: 100              # amostra mínima para decidir
      minimumNumberOfCalls: 20            # não abre com estatística fraca
      failureRateThreshold: 50            # abre com 50% de falha na janela
      slowCallDurationThreshold: 2s       # chamada acima disso é lenta
      slowCallRateThreshold: 80           # 80% lentas também abrem o circuito
      waitDurationInOpenState: 30s        # espera antes de sondar de novo
      permittedNumberOfCallsInHalfOpenState: 5   # provas do meio aberto
    estoque:
      slidingWindowSize: 50
      failureRateThreshold: 60            # dependência mais tolerante
      waitDurationInOpenState: 15s

O comportamento do meio aberto merece atenção, porque sondar com tráfego real de usuário pode penalizar clientes reais. Implementações maduras limitam as chamadas de prova a um percentual pequeno e distribuem a sondagem no tempo, enquanto monitoram a transição como evento de primeira classe nos alertas.

Circuit breaker por dependência

A granularidade correta é uma instância de disjuntor por dependência, e não um global. O checkout depende de pagamento, de estoque, de frete e de recomendação, e cada uma tem contrato, criticidade e perfil de falha próprios. O disjuntor de pagamento deve ser conservador, porque vender sem cobrar gera prejuízo direto, e o de recomendação deve ser agressivo, porque perder uma sugestão não quebra a venda. Ignorar essa assimetria e usar um limiar único faz o sistema escolher errado dos dois lados.

A decisão por dependência também orienta o fallback, que precisa ser especificado por negócio e não por engenharia. Para estoque, um fallback plausível é seguir com pedido e validar assíncrona após o circuito fechar. Para pagamento, não existe fallback de valor, e a resposta honesta é recusar a etapa com mensagem clara e retentativa controlada. O disjuntor mecânico só decide quando cortar; o que acontece depois do corte é decisão de produto.

Disjuntor na infraestrutura

O mesmo padrão existe fora do código. No Envoy e nos service mesh, o circuit breaking aparece como limite de conexões e requisições pendentes por cluster, e o outlier detection ejeta endpoints que devolvem erro sequencial ou respondem lento, recolocando-os depois de um intervalo de espera. Na borda, API gateways como Amazon API Gateway e Apigee aplicam throttling e, em combinação com retries, comportamento equivalente a um disjuntor grosseiro na entrada do sistema.

# Envoy: outlier detection no cluster, o disjuntor da camada de rede
outlier_detection:
  consecutive_5xx: 5            # ejeta o host após 5 erros 5xx seguidos
  interval: 10s                 # frequência de análise dos hosts
  base_ejection_time: 30s       # host ejetado volta após no mínimo 30s
  max_ejection_percent: 50      # nunca ejeta mais da metade do pool

Aplicação prática em nuvem

Os serviços gerenciados oferecem blocos de construção para o caminho do fallback, e o desenho mais comum combina fila com armazenamento de leitura rápida. Quando o circuito do processador de pagamento abre, o pedido pode ser gravado em uma fila como SQS e processado quando o circuito fechar, preservando o trabalho do cliente sem prometer resposta síncrona. Para leituras, um cache como ElastiCache ou DynamoDB com TTL serve a última resposta conhecida marcada como stale, o que mantém a jornada navegável durante a degradação.

# Pseudocódigo: chamada protegida com disjuntor e fallback explícito
def criar_pedido(pedido):
    if circuito_pagamento.aberto:
        # fallback: registra intenção e responde aceitação diferida
        fila_pedidos.enviar(pedido)          # SQS garante durabilidade
        return Resposta(202, "pedido recebido, pagamento será confirmado")
    try:
        cobranca = gateway_pagamento.cobrar(pedido, timeout=2s)
        circuito_pagamento.sucesso()
        return Resposta(201, cobranca.id)
    except (TimeoutError, ErroGateway) as falha:
        circuito_pagamento.falha()           # alimenta a janela de decisão
        if circuito_pagamento.deve_abrir:
            alertar_plantao("circuito de pagamento aberto")
        return Resposta(503, "pagamento indisponível, tente novamente")

A nuvem adiciona ainda o disjuntor econômico, menos conhecido e igualmente útil: proteger a conta. Chamadas a serviços cobrados por requisição, como APIs de IA ou passos de step functions, podem ser cercadas por um disjuntor que corta o consumo quando o erro do provedor indica limite de cota esgotada, evitando pagar por tentativas fadadas a falhar e protegendo o orçamento do dia.

Cliente versus infraestrutura

As duas camadas se complementam, mas não são intercambiáveis, e saber onde cada uma atua evita a falsa sensação de proteção duplicada.

Critério Disjuntor no cliente Disjuntor na infraestrutura
Granularidade Por chamada e por operação Por host e por cluster
Conhecimento de negócio Alto, permite fallback específico Baixo, corta por sinal técnico
Latência de reação Imediata na próxima chamada Depende do intervalo de análise
Cobertura Apenas onde o código existe Todo tráfego que passa pelo proxy
Custo de implantação Biblioteca em cada serviço Plataforma, uma vez para todos
Fallback possível Qualquer lógica de negócio Resposta estática ou rota alternativa

A prática recomendada em frotas grandes é usar a infraestrutura como rede de segurança universal, proteção contra hosts doentes e limites por cluster, e manter disjuntores no código apenas nas dependências críticas de negócio, onde o fallback tem valor real. Deixar a proteção só no código é frágil, pois serviços esquecidos ficam descobertos; deixar só na infraestrutura é insuficiente, porque nenhum proxy sabe o que fazer com um pedido de pagamento abortado.

Observabilidade do estado

Um disjuntor invisível é um incidente à espera de diagnóstico. O estado atual deve ser exportado como métrica por dependência, com transições registradas como eventos e alarmes no momento da abertura, e não horas depois, quando alguém nota o fallback rodando. Dashboards de serviço devem mostrar, lado a lado, taxa de erro do destino e estado do circuito, pois a correlação entre os dois é o que permite decidir entre aguardar a recuperação e escalonar para o time dono. Traces com a marcação de chamada rejeitada por circuito aberto evitam o erro de diagnóstico mais comum, que é tratar o erro rápido de circuito aberto como falha do chamador.

O conjunto mínimo de métricas por circuito tem cinco itens: estado atual codificado como valor, taxa de falha na janela deslizante, número de chamadas rejeitadas por segundo, duração média das chamadas de prova no meio aberto e tempo total que o circuito permaneceu aberto na última hora. Com esses cinco sinais, o plantão distingue um corte protetor saudável de um circuito preso em flapping, e consegue quantificar quanto da experiência do usuário está rodando em modo degradado.

Erros comuns

  • Abrir com amostra pequena: três falhas em rede instável abrem o circuito e criam flapping; volume mínimo e janela de tempo resolvem.
  • Fallback silencioso: degradação executada sem métrica nem log, e o negócio opera semanas com dado incompleto sem que ninguém saiba.
  • Circuito global único: um disjuntor para todas as dependências faz a falha do estoque parar o pagamento.
  • Retentativa dentro do circuito aberto: o cliente testa o destino a cada requisição, transformando o corte em denial de serviço involuntário.
  • Timeout maior que o orçamento da requisição: o disjuntor abre corretamente, mas cada chamada já destruiu o SLA do usuário final antes disso.
  • Estado sem exportação: o circuito funciona, mas ninguém descobre a degradação até o cliente reclamar de dado desatualizado no fallback.

Boas práticas

Defina orçamento de latência e de tentativas por dependência antes de configurar limiares, pois os números do disjuntor derivam do SLA da jornada, e não do contrário. Trate cada abertura de circuito como alarme com runbook, incluindo o que o fallback entrega e como o negócio percebe a degradação. Teste o comportamento com caos controlado, injetando falha na dependência em ambiente de produção com tráfego real, porque apenas esse teste prova que o fallback funciona e que o time sabe reagir. Por fim, revisite limiares a cada mudança relevante de contrato ou de tráfego, já que calibração de disjuntor é decisão viva e não configuração estática.