Retentativas elegantes em Python com backoff
Índice
Quando usar isto
Use este helper quando uma dependência é geralmente confiável, mas ocasionalmente instável:
- APIs HTTP sob carga moderada
- serviços internos durante deploys
- integrações de terceiros com rate limits
Chamadas HTTP que falham são normais; falhas silenciosas, não. Este padrão adiciona retentativas com jitter, registra cada tentativa em log e mantém o código compacto.
Helper principal
import random
import time
import logging
from typing import Callable, TypeVar, Iterable
import requests
T = TypeVar("T")
logger = logging.getLogger(__name__)
def with_backoff(
fn: Callable[[], T],
attempts: int = 4,
base: float = 0.4,
factor: float = 2.0,
jitter: float = 0.25,
retry_on: Iterable[int] = (500, 502, 503, 504),
) -> T:
for i in range(1, attempts + 1):
try:
return fn()
except requests.HTTPError as exc:
status = exc.response.status_code
if status not in retry_on or i == attempts:
logger.error("giving up", extra={"status": status, "attempt": i})
raise
delay = base * (factor ** (i - 1))
delay = delay * (1 + random.uniform(-jitter, jitter))
logger.warning("retrying", extra={"status": status, "attempt": i, "sleep": round(delay, 3)})
time.sleep(delay)
raise RuntimeError("exhausted retries")
Como usar
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
API = "https://api.example.com/health"
def fetch_health() -> dict:
resp = requests.get(API, timeout=3)
resp.raise_for_status()
return resp.json()
result = with_backoff(fetch_health)
print("service status:", result["status"])
Por que esse formato funciona
- Mantenha pequeno: função pura, sem decorators ou globais.
- Controle o backoff: jitter reduz o efeito manada;
factorcontrola o crescimento entre tentativas. - Registre com estrutura: logging é amigável a JSON via
extra, pronto para pipelines de logs. - Agnóstico de cliente: troque
requestspor qualquer cliente ajustando a lógica deretry_on.
Ideias de extensão
- Adicione circuit-breaking após falhas repetidas.
- Exponha métricas de tentativas e durações.
- Mova a política de retentativa para configuração, permitindo que o CI rode com menos retentativas.