Django et Celery : traitement asynchrone des tâches et questions d'entretien 2026

Guide Django Celery avec exemples pratiques, routage de tâches, planification Beat, configuration production et questions d'entretien technique 2026.

Django et Celery traitement asynchrone des tâches

Django et Celery constituent le duo incontournable du traitement asynchrone dans l'écosystème Python. Avec la sortie de Celery 5.6 et Django 6.0 en 2026, la maîtrise des files de tâches distribuées reste une compétence décisive pour les entretiens backend et les systèmes de production. Cet article explore en profondeur l'intégration Django-Celery, depuis la configuration initiale jusqu'au déploiement en production, en passant par les questions techniques posées en entretien.

Concept fondamental

Celery découple les opérations coûteuses en temps du cycle requête-réponse. Une vue Django envoie une tâche vers un broker de messages (Redis ou RabbitMQ), et un processus worker séparé l'exécute de manière asynchrone. Ce pattern gère l'envoi d'emails, la génération de rapports, le traitement d'images et toute charge de travail qui bloquerait autrement l'utilisateur.

Intégration de Celery avec Django

Celery fonctionne comme un processus indépendant partageant le code source du projet Django. L'architecture repose sur trois composants : l'application Django (producteur), un broker de messages (Redis ou RabbitMQ) et un ou plusieurs workers Celery (consommateurs). Lorsqu'une vue Django appelle .delay() ou .apply_async(), la tâche est sérialisée et envoyée dans la file du broker. Un worker la récupère et exécute la fonction dans son propre processus.

La configuration commence par un module celery.py à la racine du projet Django :

python
# myproject/celery.py
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

L'appel autodiscover_tasks() parcourt chaque application Django installée à la recherche d'un module tasks.py, enregistrant automatiquement toutes les fonctions décorées comme tâches Celery.

Dans settings.py, le broker et le backend de résultats sont configurés :

python
# myproject/settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'

Redis assure un double rôle de broker et de backend de résultats dans la majorité des déploiements Django. RabbitMQ reste le choix privilégié pour les applications nécessitant des files d'attente à quorum ou un routage avancé, fonctionnalité pleinement supportée depuis Celery 5.5.

Écriture et dispatch des tâches Celery

Une tâche Celery est une fonction Python classique décorée avec @shared_task. Le décorateur shared_task évite de coder en dur l'instance de l'application Celery, rendant la tâche réutilisable entre différentes applications Django.

python
# orders/tasks.py
from celery import shared_task
from django.core.mail import send_mail
from orders.models import Order

@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_order_confirmation(self, order_id: int) -> str:
    """Send confirmation email for a completed order."""
    try:
        order = Order.objects.select_related('user').get(id=order_id)
        send_mail(
            subject=f'Order #{order.id} Confirmed',
            message=f'Your order totaling {order.total} has been confirmed.',
            from_email='noreply@example.com',
            recipient_list=[order.user.email],
        )
        return f'Email sent for order {order_id}'
    except Order.DoesNotExist:
        return f'Order {order_id} not found'
    except Exception as exc:
        # Retry on transient failures (SMTP timeout, etc.)
        raise self.retry(exc=exc)

Trois patterns essentiels apparaissent dans cet exemple. Premièrement, bind=True donne à la tâche accès à self, ce qui permet les tentatives de relance. Deuxièmement, la fonction reçoit un entier order_id plutôt qu'un objet ORM : les instances de modèles ne peuvent pas être sérialisées de manière fiable entre processus. Troisièmement, self.retry(exc=exc) remet la tâche en file d'attente avec un backoff exponentiel lors de défaillances transitoires.

Le dispatch depuis une vue Django :

python
# orders/views.py
from orders.tasks import send_order_confirmation

def complete_order(request, order_id):
    # ... process payment, update order status ...
    send_order_confirmation.delay(order_id)
    return redirect('order_success', order_id=order_id)

L'appel .delay() retourne immédiatement. L'utilisateur voit la page de confirmation pendant que la tâche d'envoi d'email s'exécute en arrière-plan.

Routage des tâches et gestion des files d'attente

Les systèmes en production ne devraient jamais envoyer toutes les tâches dans une seule file par défaut. Une tâche lente de génération de rapports peut affamer les tâches rapides de notification si elles partagent le même pool de workers. Celery résout ce problème grâce au routage de tâches.

python
# myproject/settings.py
CELERY_TASK_ROUTES = {
    'orders.tasks.send_order_confirmation': {'queue': 'notifications'},
    'reports.tasks.generate_monthly_report': {'queue': 'reports'},
    'images.tasks.resize_upload': {'queue': 'media'},
}

Les workers sont ensuite démarrés par file d'attente :

bash
# Start notification worker (fast tasks, high concurrency)
celery -A myproject worker -Q notifications -c 8 --loglevel=info

# Start report worker (slow tasks, limited concurrency)
celery -A myproject worker -Q reports -c 2 --loglevel=info

# Start media worker (CPU-bound, prefork pool)
celery -A myproject worker -Q media -c 4 -P prefork --loglevel=info

Cette séparation empêche la contention de ressources. Les workers de notifications gèrent un débit élevé avec 8 threads concurrents, tandis que les workers de rapports n'exécutent que 2 tâches simultanées pour éviter l'épuisement mémoire causé par les requêtes volumineuses.

Prêt à réussir tes entretiens Django ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Tâches périodiques avec Celery Beat

Celery Beat est le planificateur intégré pour les tâches récurrentes. Il fonctionne comme un processus séparé qui envoie les tâches vers le broker à des intervalles configurés.

python
# myproject/settings.py
from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
    'cleanup-expired-sessions': {
        'task': 'accounts.tasks.cleanup_expired_sessions',
        'schedule': crontab(hour=3, minute=0),  # Daily at 3 AM UTC
    },
    'sync-inventory': {
        'task': 'inventory.tasks.sync_external_inventory',
        'schedule': 300.0,  # Every 5 minutes
    },
    'generate-weekly-digest': {
        'task': 'notifications.tasks.send_weekly_digest',
        'schedule': crontab(hour=9, minute=0, day_of_week='monday'),
    },
}

Beat est démarré en parallèle du worker :

bash
celery -A myproject beat --loglevel=info

Un piège classique en production : exécuter plusieurs instances de Beat provoque une duplication des exécutions de tâches. Un seul processus Beat doit tourner par déploiement. Des outils comme django-celery-beat stockent les planifications en base de données, permettant des modifications à chaud via l'interface d'administration Django.

Monitoring et observabilité avec Flower

Flower fournit un tableau de bord web en temps réel pour surveiller les workers, les tâches et les files d'attente Celery. Il expose les taux de succès des tâches, les temps d'exécution, la profondeur des files et l'état des workers.

bash
# Install and run Flower
pip install flower
celery -A myproject flower --port=5555

En production, les métriques Flower doivent alimenter un système d'alerte. Une profondeur de file croissante avec des tâches stagnantes indique un problème de capacité des workers. Un taux de relance élevé sur des tâches spécifiques signale une instabilité des services externes.

Celery 5.6 a également introduit des améliorations de journalisation structurée compatibles avec les agrégateurs de logs JSON comme Datadog ou les stacks ELK, offrant une efficacité de débogage supérieure de 30 % selon les notes de version.

Django 6.0 : tâches intégrées vs Celery

Django 6.0 intègre nativement un framework de tâches en arrière-plan, ce qui soulève la question de la pertinence de Celery. La réponse dépend du cas d'usage.

Le framework de tâches intégré de Django gère les opérations simples en arrière-plan — envoi d'emails, nettoyage de caches, traitements de données légers — sans nécessiter d'infrastructure de broker séparée. Le task runner est directement intégré à Django.

Celery reste indispensable pour les cas suivants :

  • Traitement distribué sur plusieurs machines
  • Files prioritaires et routage de tâches
  • Limitation de débit et politiques de relance avancées
  • Planification périodique (Celery Beat)
  • Suivi des résultats avec backends configurables
  • Workflows canvas (chains, groups, chords) pour l'orchestration de tâches complexes

Pour les applications qui n'ont besoin que de tâches fire-and-forget simples, la solution intégrée de Django 6.0 réduit la complexité opérationnelle. Pour tout ce qui implique des workers distribués, de la planification ou de la composition de tâches, Celery 5.6 reste le choix éprouvé.

Questions d'entretien : Django et Celery

Les entretiens techniques pour les postes backend testent fréquemment les connaissances Celery. Les questions suivantes sont tirées de scénarios réels d'entretien Django dans des entreprises allant des startups aux FAANG.

Q : Pourquoi les tâches doivent-elles recevoir des clés primaires plutôt que des instances de modèles ?

Les instances de modèles Django contiennent des connexions à la base de données, des querysets et des relations chargées paresseusement qui ne survivent pas à la sérialisation. Passer un order_id au lieu d'un objet Order garantit que la tâche récupère des données fraîches depuis la base, évitant ainsi les états obsolètes et les erreurs de sérialisation.

Q : En quoi self.retry() diffère-t-il d'un nouveau dispatch manuel de la tâche ?

self.retry() préserve le compteur de tentatives, applique la limite max_retries configurée et utilise un backoff exponentiel par défaut. Appeler manuellement .delay() une nouvelle fois crée une tâche entièrement nouvelle avec un compteur de tentatives réinitialisé, ce qui peut entraîner des boucles de relance infinies en cas de défaillances persistantes.

Q : Que se passe-t-il lorsqu'un worker Celery plante en cours de tâche ?

Le comportement dépend du paramètre acks_late. Avec acks_late=True, le broker redélivre le message à un autre worker puisque l'acquittement n'a jamais été envoyé. Avec le paramètre par défaut acks_late=False, le message est acquitté avant l'exécution, donc un plantage signifie que la tâche est perdue. Les systèmes en production gérant des charges critiques doivent utiliser acks_late=True combiné à une conception de tâches idempotentes.

Q : Quelle est la différence entre .delay(), .apply_async() et un appel direct de la tâche ?

.delay(*args) est du sucre syntaxique pour .apply_async(args=args). .apply_async() accepte des options supplémentaires comme countdown, eta, queue, priority et expires. Appeler la fonction de tâche directement (sans .delay()) l'exécute de manière synchrone dans le processus courant — utile pour les tests mais contraire à l'objectif du traitement asynchrone.

Q : Comment une couche de cache interagit-elle avec les résultats des tâches Celery ?

Les résultats de tâches stockés dans le backend Celery peuvent être mis en cache via le framework de cache Django. Une vue vérifie d'abord le cache ; si absent, elle dispatche la tâche et stocke l'identifiant AsyncResult. Les requêtes suivantes interrogent le backend de résultats via l'identifiant en cache jusqu'à la fin de la tâche, puis mettent en cache le résultat final.

Checklist de déploiement en production

Le déploiement de Celery aux côtés de Django requiert une attention particulière à plusieurs aspects opérationnels également abordés en entretien.

python
# myproject/settings.py — Production configuration
CELERY_TASK_ALWAYS_EAGER = False  # Never True in production
CELERY_TASK_ACKS_LATE = True  # Redelivery on worker crash
CELERY_WORKER_PREFETCH_MULTIPLIER = 1  # Fair scheduling
CELERY_TASK_REJECT_ON_WORKER_LOST = True  # Reject on unexpected exit
CELERY_TASK_TIME_LIMIT = 300  # Hard kill after 5 minutes
CELERY_TASK_SOFT_TIME_LIMIT = 240  # SoftTimeLimitExceeded after 4 min
CELERY_WORKER_MAX_TASKS_PER_CHILD = 1000  # Prevent memory leaks
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True

Le paramètre WORKER_MAX_TASKS_PER_CHILD redémarre les processus workers après 1000 tâches, ce qui prévient les fuites mémoire — un problème que Celery 5.6 a corrigé de manière plus globale avec ses correctifs de fuites mémoire.

La gestion des processus avec systemd ou supervisor garantit le redémarrage des workers en cas de défaillance. Un fichier unit systemd minimal :

ini
# /etc/systemd/system/celery-worker.service
[Unit]
Description=Celery Worker
After=network.target redis.service

[Service]
Type=forking
User=django
Group=django
WorkingDirectory=/opt/myproject
ExecStart=/opt/myproject/venv/bin/celery -A myproject worker \
    --loglevel=info --concurrency=4 --pidfile=/var/run/celery/worker.pid
ExecStop=/bin/kill -s TERM $MAINPID
Restart=always

[Install]
WantedBy=multi-user.target

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Conclusion

  • Celery 5.6.3 (mars 2026) apporte des correctifs de fuites mémoire, une journalisation structurée, le support des files à quorum et la compatibilité psycopg3 — la mise à jour depuis des versions antérieures est fortement recommandée.
  • Toujours passer des clés primaires aux tâches, jamais des objets ORM. Cela prévient les erreurs de sérialisation et les données obsolètes.
  • Router les tâches vers des files dédiées en fonction des caractéristiques de charge. Les notifications rapides et les rapports lents ne doivent jamais se disputer les mêmes workers.
  • Utiliser acks_late=True combiné à une conception idempotente des tâches pour les charges critiques devant survivre aux plantages de workers.
  • Définir time_limit et soft_time_limit sur chaque tâche pour empêcher les workers bloqués de consommer des ressources indéfiniment.
  • Django 6.0 gère les tâches simples en arrière-plan nativement ; Celery reste nécessaire pour le traitement distribué, la planification et les workflows canvas.
  • Surveiller la profondeur des files et les taux de relance avec Flower ou l'agrégation de logs structurés pour détecter les problèmes de capacité avant qu'ils n'affectent les utilisateurs.

Tags

#django
#celery
#python
#async
#interview

Partager

Articles similaires