Reintento

Infrastructure & Governance Security Notes Jan 8, 2026 TYPESCRIPT
resilience fault-tolerance reliability distributed-systems error-recovery

Definition

Imagina esto: estás intentando cargar una página web y falla. Instintivamente presionas refrescar, y funciona. Eso es retry en su forma más simple - intentar una operación de nuevo cuando falla. En sistemas distribuidos, este instinto se formaliza en estrategias de reintento que automáticamente re-intentan operaciones fallidas, porque en el mundo real, los fallos frecuentemente son temporales: un problema de red, un servidor momentáneamente sobrecargado, o un breve problema de conexión a la base de datos.

Retry es un patrón de resiliencia fundamental que asume que muchos fallos son transitorios - tendrán éxito si simplemente lo intentas de nuevo. Pero reintentar de forma ingenua (martillear inmediatamente el servidor con peticiones repetidas) puede empeorar las cosas. Las estrategias de reintento efectivas incluyen retrasos entre intentos (backoff), límites en el número de reintentos, e inteligencia sobre qué errores vale la pena reintentar. Un error de validación (400) no tendrá éxito sin importar cuántas veces reintentes, pero un timeout (504) o servicio no disponible (503) podría resolverse en segundos.

La idea clave es que retry transforma fallos intermitentes en operaciones confiables. Un sistema con 99% de tasa de éxito se vuelve 99.99% confiable con lógica de reintento apropiada (asumiendo independencia). Pero este poder viene con responsabilidad: tormentas de reintentos pueden abrumar sistemas en recuperación, peticiones duplicadas pueden causar inconsistencia de datos, y cadenas largas de reintento pueden crear latencia impredecible. Las estrategias inteligentes de reintento balancean persistencia con paciencia, sabiendo cuándo intentar de nuevo y cuándo rendirse.

Example

Reintentos Automáticos del SDK de AWS: Cuando llamas a un servicio AWS y recibes un error de throttling (429) o error de servicio (500), el SDK de AWS automáticamente reintenta con exponential backoff. Una llamada que falla debido a problemas momentáneos de capacidad de DynamoDB tiene éxito en el segundo intento el 99% de las veces, completamente transparente para tu código.

Integración de Gateway de Pago: Las bibliotecas cliente de la API de Stripe automáticamente reintentan en errores de red y respuestas 500. Cuando una petición de pago expira, la biblioteca reintenta con claves de idempotencia, asegurando que el pago no se cobre dos veces incluso si la primera petición realmente tuvo éxito pero la respuesta se perdió.

Pools de Conexión a Base de Datos: Cuando una conexión a base de datos falla, los pools de conexión típicamente reintentan adquirir una nueva conexión con backoff. Esto maneja escenarios comunes como breves particiones de red o reinicios de base de datos sin crashear tu aplicación.

Servicios de Entrega de Email: SendGrid y servicios similares implementan colas de reintento sofisticadas. Si un email falla al entregarse (error SMTP temporal), se reintenta con retrasos crecientes - 1 minuto, 5 minutos, 30 minutos, 2 horas - durante varios días antes de marcarse como fallido permanentemente.

Llamadas API de Apps Móviles: Las apps móviles en redes poco confiables se benefician enormemente de la lógica de reintento. La app de Uber reintenta actualizaciones de ubicación y peticiones de viaje automáticamente, manejando túneles de metro y pérdidas de señal en elevadores sin que los usuarios lo noten.

Analogy

El Llamador Telefónico Persistente: Llamas a alguien y obtienes señal de ocupado. ¿Te rindes inmediatamente? No - esperas unos segundos e intentas de nuevo. Quizás esperas un poco más la siguiente vez. Después de varios intentos durante 10 minutos, podrías dejar un mensaje de voz o intentar más tarde. Las estrategias de reintento formalizan exactamente este comportamiento humano.

La Puerta Automática que No Abre: Te acercas a una puerta automática y no abre. Retrocedes, esperas un momento, y avanzas de nuevo. Usualmente funciona la segunda vez - el sensor solo necesitaba otra mirada. Pero no seguirías caminando contra una puerta claramente rota para siempre.

La Persistencia del Pescador: Un pescador lanza su línea, espera, y si nada muerde, lanza de nuevo. No lanzan 100 veces por segundo (eso asustaría a los peces y enredaría la línea). Tienen paciencia entre intentos y saben cuándo el lugar de pesca podría simplemente estar vacío hoy.

La Máquina de Café que Necesita un Momento: Algunas máquinas de café necesitan que presiones el botón dos veces si se están calentando. Aprendes a esperar unos segundos e intentar de nuevo en lugar de presionar rápidamente o rendirte inmediatamente. Así es exactamente como funciona el backoff.

Code Example

// Implementación completa de reintento con exponential backoff
interface RetryConfig {
  maxRetries: number;
  initialDelayMs: number;
  maxDelayMs: number;
  backoffMultiplier: number;
  jitterFactor: number;  // 0-1, añade aleatoriedad para prevenir thundering herd
  retryableErrors: (error: Error) => boolean;
}

const defaultConfig: RetryConfig = {
  maxRetries: 3,
  initialDelayMs: 100,
  maxDelayMs: 10000,
  backoffMultiplier: 2,
  jitterFactor: 0.2,
  retryableErrors: (error) => {
    // Reintentar en errores de red y códigos de estado 5xx
    if (error.name === 'NetworkError') return true;
    if (error instanceof HttpError) {
      return error.status >= 500 || error.status === 429;
    }
    return false;
  }
};

async function withRetry<T>(
  operation: () => Promise<T>,
  config: Partial<RetryConfig> = {}
): Promise<T> {
  const opts = { ...defaultConfig, ...config };
  let lastError: Error;

  for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;

      // No reintentar si este tipo de error no es reintentable
      if (!opts.retryableErrors(lastError)) {
        throw lastError;
      }

      // No reintentar si hemos agotado intentos
      if (attempt === opts.maxRetries) {
        throw lastError;
      }

      // Calcular retraso con exponential backoff y jitter
      const baseDelay = Math.min(
        opts.initialDelayMs * Math.pow(opts.backoffMultiplier, attempt),
        opts.maxDelayMs
      );
      const jitter = baseDelay * opts.jitterFactor * Math.random();
      const delay = baseDelay + jitter;

      console.log(`Intento ${attempt + 1} fallido, reintentando en ${delay}ms...`);
      await sleep(delay);
    }
  }

  throw lastError!;
}

// Ejemplos de uso
async function fetchUserWithRetry(userId: string) {
  return withRetry(
    () => fetch(`/api/users/${userId}`).then(r => {
      if (!r.ok) throw new HttpError(r.status, r.statusText);
      return r.json();
    }),
    {
      maxRetries: 5,
      initialDelayMs: 200,
      retryableErrors: (error) => {
        // También reintentar en errores de negocio específicos
        return error instanceof HttpError &&
          (error.status >= 500 || error.status === 429);
      }
    }
  );
}

// Con clave de idempotencia para reintentos seguros
async function createPaymentWithRetry(payment: PaymentRequest) {
  const idempotencyKey = crypto.randomUUID();

  return withRetry(
    () => fetch('/api/payments', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey
      },
      body: JSON.stringify(payment)
    }),
    { maxRetries: 3 }
  );
}

function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Diagram

sequenceDiagram
    participant C as Cliente
    participant R as Lógica de Reintento
    participant S as Servidor

    C->>R: Ejecutar Operación
    R->>S: Intento 1
    S-->>R: 503 Servicio No Disponible

    Note over R: Esperar 100ms (retraso inicial)

    R->>S: Intento 2
    S-->>R: 503 Servicio No Disponible

    Note over R: Esperar 200ms (backoff x2)

    R->>S: Intento 3
    S-->>R: 200 OK

    R-->>C: Respuesta Exitosa

Security Notes

SECURITY NOTES

CRÍTICO - …

Configuración y Validación:

  • Siempre usa exponential backoff con jitter para prevenir tormentas de reintento.
  • Sin jitter, miles de clientes que fallaron al mismo tiempo todos reintentarán al mismo tiempo, abrumando repetidamente al servidor.
  • Implementa circuit breakers junto con lógica de reintento.
  • Cuando un servicio está genuinamente caído, los reintentos solo añaden carga a un sistema luchando.
  • Los circuit breakers detienen los reintentos completamente cuando las tasas de fallo son demasiado altas.
  • Ten cuidado reintentando operaciones que no son idempotentes.
  • Reintentar un pago sin claves de idempotencia puede cobrar al cliente dos veces.

Monitoreo y Protección:

  • Reintentar un envío de mensaje puede entregar duplicados.
  • Las operaciones no idempotentes necesitan manejo especial.
  • Establece conteos máximos de reintento y presupuestos de timeout total.
  • Reintentos ilimitados pueden hacer que las peticiones tarden horas y consuman recursos indefinidamente.
  • Los usuarios esperan una respuesta en segundos, no espera infinita.
  • Registra intentos de reintento con correlation IDs para observabilidad.
  • Reintentos excesivos indican problemas de salud del sistema que necesitan investigación, no solo recuperación automática..

Best Practices

  1. Usa exponential backoff - Cada reintento debe esperar más que el anterior (100ms, 200ms, 400ms, 800ms…)
  2. Añade jitter - Aleatoriza ligeramente los retrasos para prevenir thundering herd cuando muchos clientes reintentan simultáneamente
  3. Establece intentos máximos - Típicamente 3-5 reintentos es suficiente; más indica un problema real
  4. Limita el retraso máximo - No dejes que el backoff crezca indefinidamente; 30-60 segundos es usualmente el retraso máximo útil
  5. Solo reintenta errores transitorios - 500, 502, 503, 504, 429 vale la pena reintentar; 400, 401, 403, 404 no
  6. Usa claves de idempotencia - Para operaciones no idempotentes, asegura que los reintentos no causen efectos duplicados
  7. Respeta headers Retry-After - Cuando el servidor te dice cuándo reintentar, escucha
  8. Implementa circuit breakers - Deja de reintentar cuando las tasas de fallo indican que el servicio está caído
  9. Registra intentos de reintento - Rastrea reintentos como métricas para identificar dependencias problemáticas
  10. Establece presupuestos de timeout - El tiempo total incluyendo reintentos debe tener un límite superior

Common Mistakes

Reintentos inmediatos sin retraso: Martillear un servidor luchando con reintentos inmediatos empeora las cosas para todos, incluyéndote.

Reintentar errores no reintentables: Errores de validación (400), fallos de autenticación (401), y errores not-found (404) no tendrán éxito al reintentar - solo estás desperdiciando tiempo y recursos.

Sin límite máximo de reintento: Reintentos infinitos pueden hacer que las peticiones cuelguen para siempre, consumiendo conexiones y empeorando el problema.

Mismo retraso para todos los reintentos: Sin exponential backoff, los reintentos se agrupan y abruman sistemas en recuperación.

Ignorar headers Retry-After: Cuando un servidor te dice que esperes 60 segundos vía Retry-After, reintentar en 1 segundo es irrespetuoso y puede hacer que te bloqueen.

Reintentar sin idempotencia: Si reintentas una petición POST que crea un recurso, podrías crearlo dos veces a menos que uses claves de idempotencia.

No registrar reintentos: Reintentos silenciosos ocultan problemas sistémicos. Si una dependencia requiere reintentos frecuentes, eso es un problema que vale la pena investigar.

Standards & RFCs