1. 問題の状況: on_commit()が早すぎる?

最近、Djangoプロジェクトで transaction.on_commit() を使用して
Celeryタスクをトランザクションが成功した後に実行するように設定しました。

しかし、ログを見たところ奇妙な現象が発生しました。

まだ post.categories.add(...) のような作業が完了していないのに、
on_commit() の中にあったCeleryタスクが実行されたんです。


2. コードを見てみると…

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()

最初は問題なさそうに見えましたが…
まさにこの threading.Thread() が罠でした。


3. 原因: トランザクションはスレッドローカルに依存する

Djangoのトランザクションは 現在のスレッドに依存します。
つまり、 transaction.atomic()on_commit()このスレッドで開かれたトランザクションにのみ適用されます。

新しいスレッドで開かれた transaction.atomic() は、
メインのトランザクションとは全く関係ありません。


4. 結果的に起こったこと

  • Celeryタスクは「トランザクションコミット後」に実行されましたが、
  • そのトランザクションは サブスレッド内の小さなトランザクション に過ぎませんでした。
  • post.categories.add(...) などはまだ実行中の状態です。

つまり、意図よりも早くCeleryが実行され
不完全なデータを参照する問題が発生したのです。


5. 解決方法

ただ、このように修正すれば大丈夫です:

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(...)
✅ 単にメインスレッドで on_commit() を登録する。


6. まとめ

  • Djangoのトランザクションは スレッドローカル
  • on_commit()現在のスレッドのトランザクションコミット後 のみ実行されます
  • 他のスレッドで登録すると、そのスレッドのトランザクションにのみ反応します

この経験は非常に強力な教訓となりました。
トランザクション + Celery + スレッドを一緒に使用する際には、
本当に気をつける必要があると感じました。