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
- A task is executed and fails (timeout, 5xx error, network error)
- The system waits for a configured delay
- The task is executed again with the same payload
- This repeats until the task succeeds or the maximum retry count is reached
- If all retries fail, the task moves to the dead-letter queue
Retry Strategies
| Strategy | Delay Pattern | Best For |
|---|---|---|
| Fixed interval | Same delay every time (e.g., 5s, 5s, 5s) | Simple cases, internal services |
| Exponential backoff | Increasing delay (e.g., 1s, 2s, 4s, 8s) | External APIs, rate-limited services |
| Exponential + jitter | Backoff with randomness added | High-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' });
});