Tasks talk to flaky networks and APIs, and well-configured retries turn transient failures into eventual success.
A task that raises an exception ends in FAILURE by default. For transient errors (timeouts, 503s, deadlocks) you want retries.
The explicit way is a bound task: @app.task(bind=True, max_retries=5) passes the task instance as self, and inside an except block you call raise self.retry(exc=exc, countdown=30). retry() re-publishes the task with the same id and arguments and raises a Retry exception to end the current run; the state becomes RETRY. self.request.retries tells you which attempt this is, so you can compute your own delay.
The declarative way is autoretry_for=(ConnectionError, TimeoutError) on the decorator, combined with:
retry_backoff=True: exponential delays (1s, 2s, 4s, ...), or a number for the base factor.retry_backoff_max=600: cap the delay.retry_jitter=True(default): randomize delays so a thousand failing tasks don't retry in lockstep.retry_kwargs={"max_retries": 5}.
max_retries defaults to 3; when exceeded, the original exception is raised and the task fails. max_retries=None retries forever, which is rarely wise.
Only retry errors that might succeed next time. Validation errors and missing records should fail fast. And since a retried task runs its body again, any side effects before the failure point run again too: make tasks idempotent.
Example
import random
from celery import Celery
app = Celery("tasks", broker="memory://")
app.conf.task_always_eager = True # run inline for this demo
class Flaky(Exception):
pass
@app.task(bind=True, autoretry_for=(Flaky,), retry_backoff=True,
retry_backoff_max=60, retry_kwargs={"max_retries": 5})
def fetch_rate(self, currency):
print("attempt", self.request.retries)
if self.request.retries < 2:
raise Flaky("upstream timeout")
return {"currency": currency, "rate": 1.08}
print(fetch_rate.delay("EUR").get())
# attempt 0
# attempt 1
# attempt 2
# {'currency': 'EUR', 'rate': 1.08}Remember
- bind=True gives self; raise self.retry(exc=..., countdown=...)
- autoretry_for + retry_backoff + jitter for declarative retries
- max_retries defaults to 3; then the task fails with the original error
- Retry only transient errors and keep tasks idempotent
Watch out for
- Calling self.retry() without raise, or outside an except block, and losing the original error.