Sèkun blog


Une sorte d'alternative au rate-limit propre à celery pour huey

2025-01-31 dev | tags : django

TLDR

Avec l'implémentation du décorateur pause_secondsdisponible à la fin de l'article, les tâches send_hello_world s'exécuteront séquentiellement, avec 10 secondes de pause après chacune.

@task(retries=5, retry_delay=60)
@pause_seconds("send-email", seconds=10)
def send_hello_world():
    send_mail("Hello", "World !", "from@example.com", ["to@example.com"])

Description

J'ai longtemps utilisé celery pour lancer des tâches en arrière-plan, je configurais alors redis, celery-worker, celery-beat et celery-flower pour le monitoring. J'utilise maintenance huey, car il vient sans dépendance, est intégrable avec django, et peut utiliser sqlite3 plutôt que redis pour gérer les tâches qui doivent s'exécuter.

J'ai donc une base de donnée supplémentaire, et dois juste (avec django) lancer la commande ./manage.py run_huey.

Une fonctionnalité que j'utilisais beaucoup avec celery est l'option rate_limit de Task, notamment pour l'envoi d'e-mail, car certains serveurs limitent le nombre d'e-mails envoyés par heure, je pouvais donc simplement écrire par exemple: @task(rate_limit="100/m").

Huey ne propose pas cette fonctionnalité, mais permet nativement d'empêcher que plusieurs tâches s'exécutent en même temps. Si une deuxième tâche vient à se lancer alors que la première est toujours en cours, une TaskLockedException sera renvoyée. Une astuce est d'intercepter cette exception, et de relancer la tâche via RetryTask.

Les tâches pourront être définies comme suit:

# filename: tasks.py
from huey.contrib.djhuey import task
from django.core.mail import send_mail
from .utils import pause_seconds # implémentation plus loin

@task(retries=5, retry_delay=60)
@pause_seconds("send-email", seconds=10)
def send_hello_world():
    send_mail("Hello", "World !", "from@example.com", ["to@example.com"])

@task(retries=1)
@pause_seconds("send-email", seconds=10)
def send_another():
    send_mail("Another", "World", "from@example.com", ["to@example.com"])

En interne, un lock nommé send-email est spécifié; si plusieurs tâches sont lancées en même temps, seule une pourra s'exécuter et les autres seront relancées dans 10 secondes.

L'implémentation du décorateur pause_seconds:

# filename: utils.py
import time
from functools import wraps

from huey.contrib.djhuey import lock_task
from huey.exceptions import RetryTask, TaskLockedException


def pause_seconds(lock_name, seconds=300):
    def decorator(fun):
        @wraps(fun)
        def wrapper(*args, **kwargs):
            try:
                with lock_task(lock_name):
                    fun(*args, **kwargs)
                    time.sleep(seconds)
            except TaskLockedException:
                raise RetryTask(delay=seconds)

        return wrapper

    return decorator
Article publié le 31 janvier 2025.