1. Problembeschreibung: Wird on_commit() zu früh ausgeführt?

In einem kürzlich abgeschlossenen Django-Projekt habe ich transaction.on_commit() verwendet, um
Celery-Tasks nach erfolgreichem Abschluss der Transaktion auszuführen.

Als ich die Logs überprüfte, stellte ich jedoch ein merkwürdiges Phänomen fest.

Obwohl Arbeiten wie post.categories.add(...) noch nicht abgeschlossen waren,
wurde der in on_commit() befindliche Celery-Task bereits ausgeführt.


2. Überprüfung des Codes…

def view():
    post = Post.objects.create(...)

    def post_process():
        with transaction.atomic():
            post.categories.add(...)
            post.tags.add(...)

            def run_async():
                translate_post.delay(post.id)

            transaction.on_commit(run_async)

    threading.Thread(target=post_process).start()

Zu Beginn schien alles in Ordnung zu sein…
Aber genau dieses threading.Thread() war die Falle.


3. Ursache: Transaktionen sind thread-lokal

Die Transaktionen von Django sind an den aktuellen Thread gebunden.
Das bedeutet, dass transaction.atomic() und on_commit() nur für die gerade in diesem Thread geöffneten Transaktionen gelten.

Eine transaction.atomic(), die in einem neuen Thread geöffnet wurde,
hat keinerlei Bezug zur Haupttransaktion.


4. Was letztendlich passiert ist

  • Der Celery-Task wurde "nach dem Commit der Transaktion" ausgeführt, jedoch
  • war diese Transaktion nur eine kleine Transaktion im Sub-Thread
  • und post.categories.add(...) war noch nicht abgeschlossen.

Das bedeutet, dass Celery zu früh ausgeführt wurde,
was zu einem Verweis auf unvollständige Daten führte.


5. Lösung

So kann es einfach behoben werden:

def view():
    post = Post.objects.create(...)

    with transaction.atomic():
        post.categories.add(...)
        post.tags.add(...)

        def run_async():
            translate_post.delay(post.id)

        transaction.on_commit(run_async)

🚫 threading.Thread(...)
✅ Registrieren Sie on_commit() einfach im Haupt-Thread.


6. Zusammenfassung

  • Djangos Transaktionen sind thread-lokal
  • on_commit() wird nur nach dem Commit der Transaktion im aktuellen Thread ausgeführt
  • Wenn in einem anderen Thread registriert, reagiert es nur auf die Transaktionen dieses Threads

Diese Erfahrung war eine sehr starke Lektion.
Ich habe erkannt, dass ich beim Arbeiten mit Transaktionen + Celery + Threads
wirklich vorsichtig sein muss.