Timeout é a resiliência mais barata que você vai comprar
Sem timeout significa infinito. Como eu defino connect, read e orçamento total para uma dependência lenta não derrubar tudo.
De tudo que eu verifico quando audito um sistema, timeout é o item que mais provavelmente está faltando e o mais barato de corrigir. Uma linha de configuração, às vezes um argumento numa chamada de função, e a diferença entre "uma dependência está lenta" e "a plataforma inteira caiu."
O padrão da maioria dos clients HTTP, drivers de banco e consumers de fila é nenhum timeout. Nenhum timeout não quer dizer "razoável." Quer dizer infinito. Seu código vai esperar para sempre por uma resposta que nunca vem, segurando uma conexão, uma thread e uma vaga no pool enquanto isso.
O que quebra de verdade
Uma dependência lenta não falha fazendo barulho. Ela só responde cada vez mais tarde. Cada requisição que espera segura recursos. Seu pool de workers tem talvez 50 threads, ou seu pool de conexões tem 20 vagas. Quando todas elas estão esperando pela coisa lenta, requisições que não têm nada a ver com ela começam a enfileirar atrás. O health check falha. O load balancer te marca como indisponível. Agora você caiu porque um widget de recomendação estava lento.
Isso é esgotamento do pool de threads, e o timeout é o que evita. Uma chamada travada que desiste depois de dois segundos devolve sua thread ao pool. Uma chamada travada sem timeout fica com ela até o processo reiniciar.
Três números, não um
Timeout não é um número só. No mínimo, você precisa pensar em três.
O connect timeout cobre o estabelecimento da conexão. Se o host está inacessível ou sobrecarregado, é aqui que você descobre. Deve ser curto, muitas vezes algumas centenas de milissegundos dentro de um data center, um ou dois segundos pela internet.
O read timeout cobre a espera por bytes depois de conectado. É onde a maioria dos travamentos mora. Depende do que você está chamando, mas se seus próprios usuários estão esperando por isso, deve ficar bem abaixo do que eles tolerariam.
O orçamento total cobre a operação inteira, incluindo retries. É o número que importa para o usuário. Um read timeout de um segundo com três retries é uma operação de quatro segundos. Projete isso de propósito, não por acidente.
Valores ilustrativos, não regras: uma busca em cache pode ter 50 milissegundos, uma chamada a serviço interno de 500 milissegundos a um segundo, uma API de pagamento de terceiro alguns segundos porque você não tem escolha, e um job em lote minutos. O ponto é que cada um é uma decisão.
O prazo viaja com a requisição
Uma requisição de usuário chega com, digamos, três segundos de paciência. Sua API chama o serviço A, que chama o serviço B, que consulta um banco. Se cada um deles decide sozinho por um timeout de três segundos, o usuário pode esperar nove segundos por uma falha. O padrão certo é propagação de deadline: o ponto de entrada escolhe um prazo, passa o tempo restante a cada salto, e cada chamado usa um timeout menor do que recebeu.
A regra que segue disso: seu timeout precisa ser menor do que o timeout de quem está te chamando. Se quem chama desiste em dois segundos e você continua trabalhando por cinco, está queimando recursos numa resposta que ninguém vai ler.
Combine com retry, com cuidado
Timeout sem retry é uma falha rápida. Timeout com retry sem limite é um ataque de negação de serviço contra você mesmo. Combine os dois de forma deliberada: poucas tentativas, backoff exponencial e jitter para que mil clients não tentem de novo no mesmo milissegundo. Só faça retry de operações idempotentes, ou que você tornou idempotentes com uma chave.
E quando o timeout disparar, faça algo útil. Devolva dado do cache, degrade a funcionalidade, mostre uma página parcial. O timeout é o que te dá a chance de fazer essa escolha. Sem ele, a escolha é feita por você pela dependência mais lenta que existe.
Nunca vi um sistema maduro com timeouts demais. Já vi muitos caírem por falta de um.