91

For a task like this:

from celery.decorators import task

@task()
def add(x, y):
    if not x or not y:
        raise Exception("test error")
    return self.wait_until_server_responds(

if it throws an exception and I want to retry it from the daemon side, how can apply an exponential back off algorithm, i.e. after 2^2, 2^3,2^4 etc seconds?

Also is the retry maintained from the server side, such that if the worker happens to get killed then next worker that spawns will take the retry task?

3 Answers 3

164

The task.request.retries attribute contains the number of tries so far, so you can use this to implement exponential back-off:

from celery.task import task

@task(bind=True, max_retries=3)
def update_status(self, auth, status):
    try:
        Twitter(auth).update_status(status)
    except Twitter.WhaleFail as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)

To prevent a Thundering Herd Problem, you may consider adding a random jitter to your exponential backoff:

import random
self.retry(exc=exc, countdown=int(random.uniform(2, 4) ** self.request.retries))
Sign up to request clarification or add additional context in comments.

7 Comments

Do you know if this is a server side retry or the client is kept to wait? If the client is kept to wait then it’s bad.
As far as I know the countdown attribute sets an eta for the task at the MQ backend (e.g. RabbitMQ). So it is not set on the client side.
client is not kept to wait unless you do result.get() which is an explicit request to wait for the result to be ready, but there's also a timeout argument and there's a RETRY state so you can check if the task is being retried (and what the reason for the retry was)
For celery 3.1, you should use @task(bind=True) and celery will pass self into the function as the first argument, so you would change the args to be def update_status(self, auth, status): which then gives you access to self.retries
thanks @robbyt ! just a small correction - retries is an attribute of request, so self.request.retries is the proper call.
|
61

As of Celery 4.2 you can configure your tasks to use an exponential backoff automatically: https://docs.celeryq.dev/en/stable/userguide/tasks.html#automatic-retry-for-known-exceptions

@app.task(autoretry_for=(Exception,), retry_backoff=2)
def add(x, y):
    ...

(This was already in the docs for Celery 4.1 but actually wasn't released then, see merge request)

5 Comments

Nice catch, scratching my heads in 4.1.0, why my parameter of "retry_backoff" not respected.
@kororo it doesn't seem to work with self.retry, only other exception types
With this approach you also benefit from the built in retry_jitter (defaulted to True) which avoids the Thundering Herd Problem mentioned in asksol's answer
This is the correct answer given that it is built-in, and does not require manually handling countdown
Does this also work when retry() is called? It doesn't seem to work for non-automatic retries (on Celery 4.2.2 at least). Anyone has any idea?
3

FYI, celery has a util function to calculate exponential backoff time with jitter here, so you don't need to write your own.

def get_exponential_backoff_interval(
    factor,
    retries,
    maximum,
    full_jitter=False
):
    """Calculate the exponential backoff wait time."""
    # Will be zero if factor equals 0
    countdown = min(maximum, factor * (2 ** retries))
    # Full jitter according to
    # https://www.awsarchitectureblog.com/2015/03/backoff.html
    if full_jitter:
        countdown = random.randrange(countdown + 1)
    # Adjust according to maximum wait time and account for negative values.
    return max(0, countdown)

1 Comment

In the future, avoid link-only answers, as links tend to go stale over time. Best to also include a code snippit and explanation in your answer for maximum upvotes and value-add. Edit: case in point, this answer's link is already broken stackoverflow.com/a/46467851/366529

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.