1. Übersicht des Problems
In einer Django-Umgebung, in der transaction.on_commit()
verwendet wird, um Celery-Tasks aufzurufen,
tritt das Problem auf, dass die Daten des ManyToMany-Felds innerhalb des Celery-Tasks leer erscheinen.
2. Phänomen
- Nachdem sowohl
post.categories.add(...)
als auchpost.tags.add(...)
abgeschlossen sind, - wird
translate_post.delay()
über einentransaction.on_commit()
-Callback aufgerufen. - Wenn innerhalb des Celery-Tasks
post.categories.all()
undpost.tags.all()
abgefragt werden, wird eine leere Liste ([]
) zurückgegeben.
3. Konfiguration der Umgebung
- WRITE: PostgreSQL Master auf einer GCP-VM
- READ: PostgreSQL Replica (Streaming-Replikation) auf einem Raspberry Pi
- Die Leseanfragen werden über den Django-DB-Router an die Replica gesendet.
- Post-Modell mit ManyToMany-Relationen
4. Log-Analyse
✅ Zusammenfassung der Reihenfolge (Zeitstempel Beispiel)
13:13:56.728
— Post-Erstellung13:14:00.922
— LetzteTaggedItem
(ManyToMany) Abfrage13:14:01.688
—on_commit()
ausgeführt → Celery aufgerufen13:14:01.772
— Abfrageergebnisse intranslate_post()
:- Kategorien:
[]
- Tags:
[]
- Kategorien:
Das bedeutet, dass die Reihenfolge korrekt ist, aber der Inhalt nicht reflektiert wird.
5. Ursachenanalyse
✅ Verzögerung in PostgreSQL-Replica
- Die Standardreplikation von PostgreSQL funktioniert asynchron.
- Änderungen im Master-DB werden mit einer Verzögerung von einigen Millisekunden bis zu mehreren Hundert Millisekunden an die Replica übertragen.
- Die Aufzeichnungen der Zwischentabelle, die mit
add()
verbunden sind, wurden noch nicht in die Replica übertragen.
✅ Funktionsweise von Djangos on_commit()
on_commit()
wird sofort nach dem Commit der Django-Transaktion ausgeführt.- Aber Celery ist ein separater Prozess, und die Leseanfragen verwenden die Replica-DB.
- Folglich ist die Replica zum Zeitpunkt von on_commit noch nicht aktualisiert.
6. Validierungspunkte
- Es sind Protokolle zur Post-Erstellung vorhanden.
- Die Abfrageprotokolle von add() wurden normal ausgeführt.
- Die tatsächliche Abfolge der Abfragen und der Aufruf der Tasks ist korrekt.
- Das Problem liegt darin, dass die Abfrage zur Zeit der Abfrage die DB der Replica war.
7. Lösungen
1. Zwinge die Celery-Tasks, von der Master-DB zu lesen
# ORM-Methode
post = Post.objects.using('default').get(id=post_id)
tags = post.tags.using('default').all()
categories = post.categories.using('default').all()
2. Konfiguration eines speziellen DB-Routers für Celery
In settings.py
kann eingestellt werden, dass im Falle eines Celery-Tasks immer der Master verwendet wird.
3. Wenn die Replica immer auf dem neuesten Stand sein muss:
- Es ist notwendig, die Einstellung
synchronous_commit = on
in PostgreSQL zu aktivieren (kann die Leistung beeinträchtigen).
8. Fazit
Dieses Problem ist kein Fehler von Django, sondern resultiert aus der Verzögerung in einer asynchronen Replikationsumgebung und dem Konflikt mit den Leseprioritätsrichtlinien des ORM.
Der Schlüssel zur Lösung besteht darin, sicherzustellen, dass Celery-Tasks gezwungen sind, die Master-DB zu verwenden.
9. Jesses Kommentar
"on_commit() wurde zweifellos zum richtigen Zeitpunkt ausgeführt, aber die Tatsache, dass die gelesene DB die Replica war, war das Kernproblem.
Weder Django noch Celery haben einen Fehler gemacht. Letztendlich liegt es am Entwickler, die Systemarchitektur zu verstehen und abzustimmen."
Add a New Comment