1. Vue d'ensemble du problème
Dans une structure utilisant transaction.on_commit()
pour appeler des tâches Celery dans un environnement Django,
le phénomène de données vides dans le champ ManyToMany à l'intérieur de la tâche Celery se produit.
2. Phénomène
- Après avoir complété à la fois
post.categories.add(...)
etpost.tags.add(...)
- Appel de
translate_post.delay()
via le callbacktransaction.on_commit()
- En consultant
post.categories.all()
etpost.tags.all()
à l'intérieur de la tâche Celery, une liste vide ([]
) est renvoyée
3. Configuration de l'environnement
- WRITE: PostgreSQL maître sur GCP VM
- READ: PostgreSQL réplique (réplication en streaming) sur Raspberry Pi
- Configuration du routeur de base de données Django pour rediriger les requêtes de lecture vers la réplique
- Modèle Post utilisant le champ de relation ManyToMany
4. Analyse des journaux
✅ Résumé des étapes (en fonction de l'heure d'exemple)
13:13:56.728
— Création du Post13:14:00.922
— Dernière requêteTaggedItem
(ManyToMany)13:14:01.688
— Exécution deon_commit()
→ appel de Celery13:14:01.772
— Résultats de la consultation danstranslate_post()
:- catégories:
[]
- tags:
[]
- catégories:
En d'autres termes, l'ordre est correct mais le contenu n'est pas reflété
5. Analyse des causes
✅ Retard de la réplique PostgreSQL
- La réplication par défaut de PostgreSQL fonctionne de manière asynchrone
- Les changements dans la base de données maîtresse prennent de quelques ms à quelques centaines de ms pour être reflétés dans la Réplique
- Les enregistrements de la table intermédiaire de ManyToMany connectés via
add()
n'avaient pas encore été reflétés dans la réplique
✅ Fonctionnement de on_commit() de Django
on_commit()
s'exécute immédiatement après que la transaction de Django ait été validée- Cependant, Celery fonctionne dans un processus séparé, et les lectures utilisent la base de données de réplique
- En conséquence, au moment de on_commit, la réplique n'est pas encore à jour
6. Points de vérification
- Le journal de création du Post existe
- Le journal de la requête add() s'est également exécuté correctement
- L'ordre réel des requêtes et l'ordre d'appel de la tâche sont corrects
- Le problème réside dans le fait que la base de données au moment de la consultation était la réplique
7. Solutions
1. Forcer la lecture depuis la base de données maîtresse dans la tâche Celery
# Méthode ORM
post = Post.objects.using('default').get(id=post_id)
tags = post.tags.using('default').all()
categories = post.categories.using('default').all()
2. Configuration d'un routeur de base de données spécifiquement pour Celery
Il est possible de configurer dans settings.py
pour que la base maître soit utilisée systématiquement pour les tâches Celery
3. Dans le cas où la réplique doit être à l'état le plus à jour :
- Il est nécessaire de configurer
synchronous_commit = on
dans PostgreSQL (cela peut entraîner une dégradation des performances)
8. Conclusion
Ce problème n'est pas un problème de Django, mais le résultat d'un conflit entre le retard dans un environnement de réplication asynchrone et la politique de priorité de lecture de l'ORM.
La clé de la résolution consiste à garantir que les tâches Celery utilisent la base de données maîtresse.
9. Commentaire de Jesse
"Bien que on_commit() ait clairement été exécuté au bon moment, un des problèmes était que la base de données lue était la réplique.
Django et Celery n'ont rien fait de mal. En fin de compte, c'est aux développeurs de comprendre et de coordonner l'architecture du système."
Add a New Comment