logo

Retry

A retry is the automatic re-execution of a task or request that previously failed. In distributed systems, transient failures — network blips, temporary service outages, rate limits — are inevitable. Retries turn those temporary failures into eventual successes.

How Retries Work

  1. A task is executed and fails (timeout, 5xx error, network error)
  2. The system waits for a configured delay
  3. The task is executed again with the same payload
  4. This repeats until the task succeeds or the maximum retry count is reached
  5. If all retries fail, the task moves to the dead-letter queue

Retry Strategies

StrategyDelay PatternBest For
Fixed intervalSame delay every time (e.g., 5s, 5s, 5s)Simple cases, internal services
Exponential backoffIncreasing delay (e.g., 1s, 2s, 4s, 8s)External APIs, rate-limited services
Exponential + jitterBackoff with randomness addedHigh-concurrency systems to avoid thundering herd

Retries in AsyncQueue

await aq.tasks.create({
  callbackUrl: 'https://your-app.com/api/charge-payment',
  payload: { orderId: 'ord_123', amount: 4999 },
  retries: 3,          // retry up to 3 times
  webhookUrl: 'https://your-app.com/api/on-payment-result',
});

AsyncQueue handles retry logic, delay calculation, and attempt tracking automatically.

Idempotency and Retries

Retries mean your endpoint may receive the same payload more than once. Your handler must be idempotent — producing the same result whether it runs once or ten times:

app.post('/api/charge-payment', async (req, res) => {
  const { orderId, amount } = req.body;

  // Check if already processed
  const existing = await db.payments.findByOrderId(orderId);
  if (existing) {
    return res.json({ paymentId: existing.id, status: 'already_processed' });
  }

  const payment = await stripe.charges.create({ amount });
  await db.payments.create({ orderId, paymentId: payment.id });
  res.json({ paymentId: payment.id, status: 'charged' });
});