ResiliênciaIniciante6 min de leitura7 seções1981, Data aproximada

Timeout

Todo pedido que sai da sua máquina precisa de um prazo máximo. Sem ele, a falha mais comum em produção não é o erro — é a espera infinita, que consome recursos até derrubar quem ainda estava saudável.

Em 10 segundos

Todo pedido que sai do seu processo precisa de um prazo. Sem prazo, uma dependência lenta segura suas conexões até esgotar o pool — e o seu serviço cai por causa de um problema que era do vizinho. Timeout transforma espera infinita em erro tratável.

Antes de ter nome

Desistir após um prazo é anterior à computação: qualquer protocolo de comunicação com espera acumulada precisa de um limite para não travar para sempre.

  • #resiliencia
  • #prazo
  • #latencia
  • #producao
  • #recursos
Analogia

A ligação em espera

Você liga para o suporte e a musiquinha começa. Se você se recusar a desligar, a linha fica ocupada — e ninguém mais da sua casa consegue usar o telefone. Não é o suporte que travou a sua casa: foi a sua decisão de esperar para sempre. Desligar depois de dez minutos não resolve o problema deles, mas devolve o telefone para você.

01Conceito

Lento é pior que morto

Quando uma dependência cai, você recebe connection refused em milissegundos e segue a vida. Quando ela apenas trava, você espera — e a espera custa uma thread, uma conexão e uma vaga no pool, cada uma delas indisponível para os pedidos que estavam saudáveis.

É assim que uma falha em um serviço vira uma falha em todos: o esgotamento de recursos se propaga para cima, e o serviço que cai não é o que quebrou.

O nome disso é exaustão de pool. Suponha um pool de 100 conexões e uma dependência que passou a responder em 30 segundos em vez de 50 milissegundos. Com 10 requisições por segundo, em dez segundos as 100 vagas estão ocupadas esperando — e a partir daí toda requisição nova, inclusive as que nem tocam nessa dependência, fica na fila do pool. O painel mostra o seu serviço com p99 explodindo, e a causa está a duas camadas de distância.

Por isso o timeout é o primeiro padrão da família de resiliência, e não um detalhe de configuração: ele é a fronteira que impede o problema do vizinho de virar o seu. Retry, Circuit Breaker e Bulkhead pressupõem que existe um prazo — sem ele, não há evento de falha para reagir.

Há um detalhe que quase todo mundo erra na primeira vez: a maioria das bibliotecas chama de *timeout* o tempo ocioso, não o tempo total. Um servidor que envia um byte a cada 1,9 segundo nunca estoura um read timeout de 2 segundos, e mantém a conexão presa por horas. É o ataque *slowloris*, e ele acontece por acidente com mais frequência do que por malícia.

Sem o padrão × com o padrão

Com prazo

  • A chamada é abortada ao estourar o limite
  • A conexão volta para o pool imediatamente
  • A falha vira um erro nomeado, tratável no código
  • O resto do sistema continua atendendo

O prazo não conserta a dependência lenta — ele impede que a lentidão dela vire indisponibilidade sua.

O timeout não melhora a disponibilidade da dependência. Ele converte uma falha que se propaga numa falha que fica contida.
02Conceito

O prazo é do pedido, não da chamada

Configurar timeout por chamada parece suficiente e não é. Se o seu handler faz três chamadas de 1 segundo cada, o cliente que tinha 2 segundos de paciência já foi embora antes da terceira começar.

A forma correta é orçamento: o pedido entra com um tempo total, cada etapa consome uma parte, e o que sobra é o prazo da etapa seguinte.

Sem orçamento, os prazos se somam de um jeito que ninguém previu. Três chamadas de 1s parecem 1s de latência no desenho e são 3s no pior caso — e se cada uma tiver retry, viram 9s. O número que importa é o prazo do cliente lá na ponta, e ele precisa ser decrescente conforme desce a pilha.

A regra prática: o timeout de quem chama tem que ser menor que o de quem chamou. Se o gateway espera 5s e o serviço interno espera 10s, o serviço interno vai continuar trabalhando por mais 5 segundos numa resposta que ninguém vai ler — gastando banco, CPU e conexão em trabalho garantidamente inútil.

É por isso que sistemas maduros propagam o prazo restante no cabeçalho da requisição: gRPC tem grpc-timeout, e o context.Context do Go carrega o deadline explicitamente. Quando a etapa recebe o prazo em vez de configurá-lo, o orçamento fica correto por construção.

03No mundo real

Como isso aparece na prática

O checkout que caiu por causa do serviço de recomendação

Cenário

A página de checkout busca recomendações de produto para exibir num carrossel lateral. O serviço de recomendação teve um problema de índice e passou a responder em 40 segundos em vez de 80 milissegundos.

Como o padrão entra

A chamada de recomendação recebe um prazo de 200ms, e o carrossel vira um estado vazio quando o prazo estoura. O checkout não depende dela para funcionar.

Trade-off: Alguns usuários deixam de ver recomendações durante o incidente — em troca de todos continuarem conseguindo comprar. Escolher o que é essencial exige decisão de produto, não só de engenharia.

A consulta que travou a tabela na Black Friday

Cenário

Um relatório administrativo roda uma consulta sem índice adequado. Em volume normal ela leva 2 segundos; sob o volume de pico, passa a levar 4 minutos e segura locks que a escrita precisa.

Como o padrão entra

statement_timeout no papel de banco usado pelo relatório mata a consulta em 10 segundos. O relatório falha; as vendas continuam.

Trade-off: O relatório passa a falhar em horário de pico e precisa de outra solução (réplica de leitura, pré-agregação). O timeout não resolve o problema — ele escolhe quem paga por ele.

04Cuidado

Onde isto dá errado

  1. Confundir tempo ocioso com tempo total

    A maioria das bibliotecas chama de timeout o intervalo máximo entre bytes, não a duração total da chamada. Um servidor que envia um byte a cada 1,9 segundo nunca estoura um read timeout de 2 segundos e prende a conexão por horas. Sempre imponha também um prazo total.

  2. Timeout de quem chama maior que o de quem chamou

    Se o gateway desiste em 5 segundos e o serviço interno só desiste em 10, o serviço interno passa 5 segundos produzindo uma resposta que ninguém vai ler — gastando banco e CPU em trabalho garantidamente descartado. Os prazos precisam decrescer conforme se desce a pilha.

  3. Timeout sem cancelamento de verdade

    Abortar a espera não é o mesmo que abortar o trabalho. Se você para de esperar mas a query continua rodando no banco, o recurso que você queria liberar continua ocupado — você só deixou de saber disso. O cancelamento precisa chegar até a ponta.

  4. Prazo escolhido pela média

    Timeout dimensionado pela latência média corta metade das requisições legítimas assim que a carga sobe. O prazo se define pelo percentil alto (p99) mais uma folga, e sempre abaixo do que o cliente da ponta está disposto a esperar.

  5. Estourar o prazo e não saber se o trabalho aconteceu

    Um timeout num pedido de escrita é ambíguo: pode ser que ele não tenha chegado, ou que tenha sido executado e só a resposta se perdeu. Quem estoura prazo em operação que muda estado precisa de idempotência para poder tentar de novo sem duplicar.

05Interativo

A régua que justifica o prazo

Mexa nos controles: a teoria acima vira comportamento observável.

Quantas vezes cabe?

São Paulo ↔ Virgínia, ida e volta é 1,2 milhões × mais lento que leitura da memória principal.

Se o ciclo de CPU durasse um segundo, a ida e volta até a Virgínia levaria mais de doze anos. É essa a distância que uma chamada de rede atravessa, e a razão de todo pedido precisar de prazo.

Na régua

Clique num ponto para comparar. Só o par escolhido leva rótulo.

Cada linha de grade é 10× a anterior. É o único jeito de tudo caber.

usado pelo simulador do Construtorreferência de hardware e rede

Todos os pontos

Por ordem de grandeza. Clique para colocar no comparador.

Nanossegundos

Microssegundos

Milissegundos

Segundos

05Interativo

E se a dependência travar?

Mexa nos controles: a teoria acima vira comportamento observável.

Simulador de falha

O banco já está fora. Vai colocando proteção e olha o custo cair.

Proteger a borda

Sem prazo. A dependência trava e você segura a thread uns 30 segundos.

Latência sentida

30s

erro
  1. 1Usuário · confirma a ação: POST /pedidos0ns
  2. 2API / Gateway · Nginx termina TLS e balanceia → encaminha2ms
  3. 3Aplicação (casos de uso) · caso de uso orquestra a operação2ms
  4. 4Domínio · invariantes de negócio validam o comando1ms
  5. 5Escrita falhou · PostgreSQL não responde: sem prazo, a chamada pendura até o cliente desistir e a conexão fica presa até lá30s
  6. 6Erro · a requisição falhou: o usuário recebe erro0ns
  • Sem timeout, esta falha custa 30 segundos de conexão presa. Com prazo configurado seriam 2 segundos; com um disjuntor aberto, microssegundos. A dependência quebrada é a mesma; o que muda é o que protege a borda.
  • Sem fila para absorver a escrita, a queda do banco vira erro imediato. Uma fila (Kafka/RabbitMQ) transformaria isso em 'aceito, processo depois'.
06Implementação

Prazo por chamada e orçamento por pedido

// Sem timeout: esta chamada pode ficar pendurada para sempre.
// O servidor nao precisa cair — basta ele aceitar a conexao e nunca responder.
const resposta = await fetch(url);

// Com timeout: um limite explicito, e o recurso e liberado.
async function buscarComPrazo(url: string, ms: number) {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), ms);
  try {
    return await fetch(url, { signal: ctrl.signal });
  } finally {
    clearTimeout(timer); // sem isto, o timer segura o processo vivo
  }
}

// O orcamento e do PEDIDO, nao da chamada: se ja gastei 800ms,
// a proxima chamada nao pode ter 1000ms de prazo.
async function comOrcamento<T>(restanteMs: number, fn: (ms: number) => Promise<T>) {
  if (restanteMs <= 0) throw new Error("orcamento esgotado");
  return fn(restanteMs);
}
07Decisão

Quando usar × quando evitar

Use quando

3
  • Toda chamada que atravessa um limite de processo — rede, banco, disco, fila.
  • Ao adquirir recurso escasso: conexão de pool, lock distribuído, semáforo.
  • Como orçamento decrescente ao longo da cadeia de chamadas.

Evite quando

3
  • Como remendo para lentidão crônica — o prazo esconde o sintoma, não corrige a causa.
  • Em trabalho de fundo legitimamente longo, onde o certo é acompanhar progresso.
  • Com valor escolhido pela latência média em vez do percentil alto.

Testar o que ficou

5 armadilhas de Timeout viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • AbortController + fetchO jeito padrão do browser de cancelar uma requisição que passou do prazo, liberando a conexão.
  • asyncio.wait_forImpõe prazo total a qualquer corrotina do Python, e não só ao tempo ocioso entre bytes.
  • statement_timeout do PostgresO banco mata a consulta que passar do limite, protegendo-se de query que trava a tabela.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Todo pedido que sai da maquina tem prazo.
await fetch(url, { signal: AbortSignal.timeout(2000) });

O que ele cobra

Nenhum salto a mais entre a chamada e o efeito.

  • Um número a calibrar por chamada, que envelhece quando o tráfego muda
  • Requisições legítimas e lentas passam a falhar

Não vale se: nunca. Não existe caso legítimo de chamada de rede sem prazo — o que varia é o valor.

Onde isto já custou caro

Incidentes reais que este conceito explica.

Onde isto aparece

Continue por aqui