Fallback e degradação graciosa

TL;DR

Timeout, retry e breaker decidem quando desistir. Nenhum decide o que responder — e essa pergunta não é técnica: é de produto. O fallback é a resposta pior servida de propósito: valor em cache, valor padrão, funcionalidade reduzida ou uma mensagem honesta. Ele é o que transforma “erro 500” em “a página carregou sem o bloco de recomendações”, que é a diferença entre um incidente e uma imperfeição. E carrega a armadilha mais cruel da família: o plano B nunca exercitado, que falha justamente no dia em que é acionado — transformando um incidente em dois.

O recorte desta nota

Aqui o fallback como decisão e o que ele sacrifica. Como testá-lo e observá-lo em produção em Operação 3-06 (“Fallback: a armadilha de ter um plano B nunca testado”).

O breaker abriu. E agora?

Você implementou o circuit breaker corretamente. Ele detecta a falha do serviço de recomendação, abre, e as chamadas passam a falhar em microssegundos em vez de dois segundos.

Aí você olha a tela do usuário: erro 500.

O breaker fez exatamente o que devia — economizou recursos, parou de martelar a dependência. Mas do ponto de vista de quem está comprando, nada melhorou: a página não carrega, só falha mais rápido. Você trocou um erro lento por um erro rápido, o que é melhor para o servidor e indiferente para o cliente.

A defesa estava completa pela metade. Detectar a falha é a parte técnica, fica pronta primeiro, e é onde a maioria dos times para. Decidir o que responder exige alguém dizer “é aceitável mostrar a página sem recomendações” — uma decisão de produto, que não cabe ao desenvolvedor tomar sozinho e por isso tende a ficar para depois. Depois costuma ser nunca.

Os níveis de degradação

Não existe “o fallback”: existe uma escada de respostas, da melhor à pior. Vale conhecê-la porque a escolha muda por funcionalidade.

NívelO que respondeQuando cabe
Cacheo último valor conhecidodado que tolera estar desatualizado
Valor padrãoum resultado genérico e segurorecomendações → “mais vendidos”; frete → tabela fixa
Funcionalidade reduzidaa página sem aquele blococomponentes opcionais da tela
Fila para depoisaceita agora, processa quando voltarescrita que não precisa de confirmação imediata
Mensagem honesta”indisponível, tente em instantes”quando não há resposta parcial possível
Errofalha explícitaquando responder errado é pior que não responder

A última linha é a mais importante e a mais esquecida: nem toda funcionalidade deve ter fallback. Se o serviço antifraude está fora, o fallback “aprova a transação” é catastrófico e o fallback “recusa tudo” pode ser aceitável — mas alguém precisa decidir isso explicitamente, e a decisão pertence ao negócio. Para saldo bancário, mostrar um valor em cache pode ser pior que mostrar erro.


graph TD
    C["Chamada falhou<br/>(timeout · breaker aberto)"] --> Q{"existe resposta<br/>aceitável e pior?"}
    Q -->|"sim"| F["Fallback<br/>cache · padrão · sem o bloco"]
    Q -->|"não — responder errado<br/>é pior que não responder"| E["Erro honesto"]
    F --> M["<b>marcar como degradado</b><br/>métrica + sinal ao usuário"]
    E --> M

    style F fill:#4A90D9,color:#fff
    style E fill:#F5A623,color:#000
    style M fill:#4A90D9,color:#fff

O quadrado final é o que mais falta na prática: degradar em silêncio é uma armadilha. Se o fallback não emite métrica, o sistema pode operar degradado por semanas com todos os painéis verdes — porque, do ponto de vista das métricas de erro, ele está respondendo 200.

O que se sacrifica

Correção. É o único padrão da família que sacrifica isso, e por isso é o mais delicado: você está entregando de propósito uma resposta pior — desatualizada, genérica ou incompleta. Todos os outros padrões sacrificam disponibilidade de algumas requisições para preservar o todo; este preserva a disponibilidade degradando a verdade.

Quem paga é o usuário, muitas vezes sem saber — e daí decorre uma obrigação: sinalizar. Um preço vindo de cache exibido como se fosse atual é um problema de confiança, não de engenharia. “Dados de alguns minutos atrás” é uma frase barata que resolve.

Sacrifica também clareza operacional, se malfeito. Um sistema que degrada silenciosamente esconde a falha das suas próprias métricas — e você perde a informação de que a dependência está mal justamente quando precisaria dela.

Armadilhas comuns

O plano B nunca exercitado

O que acontece: o fallback é acionado pela primeira vez durante um incidente real — e falha, porque tem um bug, ou porque o cache está vazio, ou porque a dependência que ele usa também caiu. O incidente vira dois, e o segundo é mais difícil de diagnosticar. Por quê: esse código só roda quando algo já deu errado, então nunca executa em teste nem em produção normal. Cobertura alta convive perfeitamente com fallback quebrado. Como evitar: exercite deliberadamente — injeção de falha na esteira, e um interruptor que force o caminho degradado em ambiente de teste. Alguns times mantêm uma pequena fração do tráfego real de produção passando pelo fallback, justamente para que ele nunca esteja frio.

Fallback silencioso

O que acontece: a chamada falha, o valor padrão é servido, e nada é registrado. O sistema opera degradado por semanas com painéis verdes, e a descoberta vem por reclamação de que “os dados estão estranhos”. Por quê: do ponto de vista de HTTP, a requisição teve sucesso — o try/catch transformou o erro em resposta 200 e a métrica de erro não vê nada. Como evitar: toda ativação de fallback emite métrica própria e entra no rastreamento. Alerta quando a taxa passar de um patamar. E, para o usuário, um sinal honesto quando o dado for degradado.

Fallback que chama outra dependência

O que acontece: o plano B para o serviço de preços é consultar outro serviço — que está sobrecarregado justamente porque todo mundo migrou o tráfego para ele. A cascata continua, agora por um caminho que ninguém desenhou. Por quê: o plano B foi pensado como funcionalidade equivalente, não como caminho de contingência sob estresse. Como evitar: o fallback ideal é local e barato — cache em memória, constante, resposta parcial. Se ele precisa de rede, herda todos os problemas da chamada original e precisa das mesmas defesas: timeout, breaker e limite próprios.

Como explicar em inglês

“Timeouts, retries and breakers decide when to give up; none of them decides what to answer, and that question is a product decision, not a technical one. A fallback is a deliberately worse answer — a cached value, a sensible default, the page without that section, or an honest message. Without one, a circuit breaker just converts a slow error into a fast error, which is better for your servers and identical for the user. The part people underestimate is that not everything should have a fallback: if the fraud service is down, ‘approve anyway’ is catastrophic, so failing is correct. And the classic trap is that the fallback path only ever runs during an incident, so it’s the least tested code you own — which is why it’s worth forcing it deliberately in testing, and why every fallback activation should emit a metric. Degrading silently means running degraded for weeks with green dashboards.”

PTEN
degradação graciosagraceful degradation
valor padrãosensible default
caminho degradadodegraded path
injeção de falhafault injection
dado obsoletostale data
funcionalidade reduzidareduced functionality

O que vem a seguir

Os padrões vistos até aqui reagem a uma dependência que falha fora. Os próximos olham para dentro: como impedir que a carga que chega — legítima ou não — sature o próprio serviço antes que qualquer dependência tenha chance de falhar.

Veja também

Fontes

  • Michael NygardRelease It! (2ª ed., 2018) — degradação graciosa entre os stability patterns.
  • Google SRE BookAddressing Cascading Failures — degradação e a importância de exercitar o caminho de falha.
  • MicrosoftCloud Design Patterns — o catálogo de referência da família.