1. Probleemoverzicht
In een Django-omgeving, waar gebruik wordt gemaakt van transaction.on_commit()
om een Celery-taak aan te roepen,
Treedt het probleem op dat de gegevens van ManyToMany-velden binnen de Celery-taak leeg zijn.
2. Symptomen
- Na het afronden van zowel
post.categories.add(...)
alspost.tags.add(...)
transaction.on_commit()
callback roepttranslate_post.delay()
aan- Bij het opvragen van
post.categories.all()
enpost.tags.all()
binnen de Celery-taak, is het resultaat een lege lijst ([]
)
3. Omgevingsconfiguratie
- WRITE: PostgreSQL master op GCP VM
- READ: PostgreSQL replica (streaming replicatie) op Raspberry Pi
- Instelling zodat leesverzoeken via Django DB router naar de replica worden gestuurd
- Post-model dat gebruik maakt van een ManyToMany relatie veld
4. Loganalyse
✅ Samenvatting van de volgorde (tijdens gespecificeerde tijd)
13:13:56.728
— Post aangemaakt13:14:00.922
— LaatsteTaggedItem
(ManyToMany) query13:14:01.688
—on_commit()
uitgevoerd → Celery aangeroepen13:14:01.772
— Resultaten vantranslate_post()
:- categorieën:
[]
- tags:
[]
- categorieën:
Met andere woorden, de volgorde klopt, maar de inhoud is niet bijgewerkt
5. Oorzaakanalyse
✅ Vertraging van PostgreSQL Replica
- De standaard replicatie van PostgreSQL werkt asynchroon
- Wijzigingen in de Master DB worden met een vertraging van enkele ms tot honderden ms naar de Replica doorgevoerd
- De records van de ManyToMany-midden tabel, gemaakt via
add()
, zijn nog niet naar de replica doorgevoerd
✅ Werking van Django's on_commit()
on_commit()
wordt uitgevoerd direct na het committen van de Django-transactie- Echter, Celery draait in een aparte proces en leest uit de replica DB
- Als gevolg hiervan is de replica bij het on_commit-tijdstip nog niet bijgewerkt
6. Verificatiepunten
- Er zijn logs van de aanmaak van de Post
- De add() query logs zijn ook normaal uitgevoerd
- De volgorde van de daadwerkelijke queries en de aanroepen van de taken zijn correct
- Het probleem was dat de DB op het moment van opvragen de replica was
7. Oplossingen
1. Dwing lezen van master DB in Celery-taken
# ORM methode
post = Post.objects.using('default').get(id=post_id)
tags = post.tags.using('default').all()
categories = post.categories.using('default').all()
2. Configureer een speciale DB Router voor Celery
In settings.py
kan worden ingesteld dat altijd de master wordt gebruikt voor Celery-taken
3. Als de replica altijd up-to-date moet zijn:
- Instelling
synchronous_commit = on
in PostgreSQL nodig (kan prestatieproblemen veroorzaken)
8. Conclusie
Dit probleem is geen probleem van Django, maar een resultaat van de vertraging in een asynchroon replicatie-omgeving en het leesprioriteitsbeleid van ORM dat met elkaar in conflict is.
De kernoplossing is om ervoor te zorgen dat Celery-taken de master DB gebruiken.
9. Jesse's commentaar
"on_commit() werd op het juiste moment uitgevoerd, maar het feit dat de gelezen DB de replica was, was de essentie van het probleem.
Django en Celery zijn niet in fout. Uiteindelijk is het de verantwoordelijkheid van de ontwikkelaar om de systeemarchitectuur te begrijpen en te coördineren."
댓글이 없습니다.