2 min read

Handling Errors and Retries in Celery

Robust error handling is crucial for distributed systems. Celery provides mechanisms to automatically retry tasks when they fail, which is essential for handling transient issues like network timeouts or temporary service unavailability.

1. Manual Retries using self.retry()

You can manually trigger a retry inside your task using the self.retry() method. To use this, you must bind the task to the instance using bind=True.

from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379/0')

@app.task(bind=True)
def send_email(self, email_address):
    try:
        # Simulate sending email
        print(f"Sending email to {email_address}...")
        raise ConnectionError("SMTP Server down")
    except ConnectionError as exc:
        # Retry in 60 seconds. Max retries defaults to 3.
        raise self.retry(exc=exc, countdown=60, max_retries=3)
  • bind=True: Gives access to self (the task instance).
  • exc: Passes the exception context to the retry (useful for logging).
  • countdown: Time in seconds to wait before retrying.

2. Automatic Retries (Decorator)

For a cleaner approach, you can configure retries directly in the @app.task decorator. This avoids boilerplate try...except blocks.

@app.task(autoretry_for=(ConnectionError,), retry_kwargs={'max_retries': 5}, retry_backoff=True)
def fetch_url(url):
    # If ConnectionError is raised, Celery automatically retries
    pass
  • autoretry_for: A tuple of exceptions that trigger a retry.
  • retry_kwargs: Dictionary of arguments passed to retry().
  • retry_backoff: If True, enables exponential backoff (wait time increases with each failure).

3. Exponential Backoff

Exponential backoff prevents your workers from hammering a failing service.

  • retry_backoff=True: Default behavior.
  • retry_backoff=60: Starts at 60s, then 120s, 240s, etc.

4. Handling Max Retries

When a task fails after the maximum number of retries, it raises a MaxRetriesExceededError. You can handle this by overriding the on_failure method or using a custom base class.

from celery import Task

class MyTask(Task):
    def on_failure(self, exc, task_id, args, kwargs, einfo):
        print(f"Task {task_id} failed: {exc}")
        # Send alert to admin, log to DB, etc.

@app.task(base=MyTask)
def add(x, y):
    raise KeyError()

programming/python/celery