Retentativas elegantes em Python com backoff

🇺🇸 Read in English
Í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; factor controla o crescimento entre tentativas.
  • Registre com estrutura: logging é amigável a JSON via extra, pronto para pipelines de logs.
  • Agnóstico de cliente: troque requests por qualquer cliente ajustando a lógica de retry_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.