Retries & Backoff
Falhas transitórias de rede e infraestrutura geralmente são bem-sucedidas na nova tentativa. Use tentativas limitadas, backoff exponencial com jitter e retente apenas operações idempotentes - bibliotecas como tenacity codificam o padrão de forma limpa.
Receita
import random
import time
def retry(callable_fn, attempts=3, base=0.5):
for attempt in range(1, attempts + 1):
try:
return callable_fn()
except TransientError:
if attempt == attempts:
raise
delay = base * (2 ** (attempt - 1)) + random.uniform(0, 0.1)
time.sleep(delay)Quando usar isso:
- HTTP 429/503, resets de conexão, falhas de DNS
- Deadlocks de banco de dados ou falhas de serialização (com cuidado)
- Timeouts de publicação em filas de mensagens
- Limites de taxa de API na nuvem com semântica Retry-After
- NÃO para erros de validação ou erros de cliente 4xx
Exemplo de Trabalho
Tentativa manual com jitter, depois estrutura equivalente ao estilo tenacity (demonstração apenas com stdlib).
import random
import time
from dataclasses import dataclass
class TransientError(Exception):
pass
@dataclass
class FakeClient:
calls: int = 0
fail_until: int = 2
def get(self) -> str:
self.calls += 1
if self.calls <= self.fail_until:
raise TransientError(f"attempt {self.calls}")
return "ok"
def with_retry(client: FakeClient, attempts: int = 5) -> str:
for attempt in range(1, attempts + 1):
try:
return client.get()
except TransientError as exc:
if attempt == attempts:
raise RuntimeError("exhausted retries") from exc
delay = min(2.0, 0.2 * (2 ** (attempt - 1))) + random.uniform(0, 0.05)
time.sleep(delay)
raise RuntimeError("unreachable")
client = FakeClient()
print(with_retry(client), "calls:", client.calls)O que isso demonstra:
- Limitar tentativas e levantar um erro encadeado quando esgotado
- Backoff exponencial limitado para evitar esperas de vários minutos em código síncrono
- Jitter reduz o "thundering herd" na recuperação
- Contar chamadas em testes para verificar o comportamento de retry
Mergulho Profundo
Como Funciona
- Classificar exceções em retriáveis vs permanentes
- Backoff:
delay = min(cap, base * 2**attempt) + jitter - Idempotência: retentativas seguras apenas se os efeitos colaterais estiverem ausentes ou forem deduplicados
- tenacity fornece decoradores:
stop_after_attempt,wait_exponential,retry_if_exception_type - Registrar cada retry com número da tentativa e duração do sono
Tabela de Política de Retry
| Sinal | Retry? |
|---|---|
| Timeout, 503 | Sim com backoff |
| Validação 400, 422 | Não |
| Conflito 409 | Apenas com merge idempotente |
| Sucesso | Parar |
Armadilhas
- Retentar POST não idempotente - duplica cobranças/pedidos. Correção: chaves de idempotência ou pular o retry.
- Loops de retry infinitos - nenhuma condição de
stopqueima CPU e mascara interrupções. Correção: tentativas máximas + circuit breaker. - Sem jitter - clientes sincronizados tentam novamente juntos. Correção: jitter uniforme aleatório ou completo.
- Retentar muito rápido - piora os limites de taxa. Correção: honrar o cabeçalho
Retry-Afterquando presente. - Engolir exceção final - retornar None após falhas. Correção: levantar com contexto na exaustão.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Circuit breaker | Interrupção sustentada | Blip único |
| Fila + dead letter | Workers assíncronos | Caminho de requisição síncrono |
| Retry embutido na biblioteca cliente | boto/urllib3 configurado | Necessita de logging/métricas personalizadas |
FAQs
Quando devo usar tenacity?
Quando decoradores e políticas de stop/wait compostas superam loops feitos à mão - a maioria dos serviços de produção.
Quantas retentativas são suficientes?
3-5 para HTTP com backoff exponencial é comum; ajuste com SLOs e orçamentos de latência p99.
Retries devem registrar avisos?
Sim - inclua tentativa, sono, tipo de exceção e ID de correlação para correlação de incidentes.
Retries de banco de dados são seguros?
Apenas para erros transitórios conhecidos (deadlock, conexão perdida) em instruções idempotentes.
Como retries assíncronos diferem?
Use asyncio.sleep em vez de time.sleep; tenacity suporta callables assíncronos.
E sobre retry em middleware?
Centralize a política para clientes HTTP de saída; não retente duas vezes em várias camadas.
Posso retentar em valores de retorno?
retry_if_result do tenacity funciona quando "falhas suaves" retornam códigos de erro em vez de levantar exceções.
Como testar backoff?
Injete mock de clock/sleep; afirme a contagem de tentativas sem atrasos reais (tenacity wait_fixed(0) em testes).
FastAPI retenta automaticamente?
Não - a aplicação ou o cliente HTTP (transporte httpx) é responsável pela política de retry.
O que é full jitter?
sleep = random.uniform(0, cap) - variante recomendada pela AWS para espalhar a carga na recuperação.
Relacionados
- Raising & Chaining - cadeia final de falha
- The logging Module - avisos de retry
- Async HTTP with httpx/aiohttp - timeouts do cliente
- Errors & Logging Best Practices - política operacional
Versões do Stack: Esta página foi escrita para Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.