1. 問題の概要
Django 環境で transaction.on_commit()
を活用して Celery タスクを呼び出す構造において,
ManyToMany フィールドのデータが Celery タスク内では空である現象が発生。
2. 現象
post.categories.add(...)
,post.tags.add(...)
をすべて完了した後transaction.on_commit()
コールバックでtranslate_post.delay()
を呼び出し- Celery タスク内で
post.categories.all()
,post.tags.all()
を照会したところ 空のリスト([]
)が返される
3. 環境構成
- WRITE: GCP VMの PostgreSQL マスター
- READ: Raspberry Piの PostgreSQL レプリカ (streaming replication)
- Django DB ルーターを介して読み取りリクエストをレプリカに送信するよう設定
- ManyToMany 関係フィールドを使用する Post モデル
4. ログ分析
✅ 順序の要約 (例示時間基準)
13:13:56.728
— Post 作成13:14:00.922
— 最後のTaggedItem
(ManyToMany) クエリ13:14:01.688
—on_commit()
実行 → Celery 呼び出し13:14:01.772
—translate_post()
内の照会結果:- categories:
[]
- tags:
[]
- categories:
つまり、順序は正しいが内容が反映されていない
5. 原因分析
✅ PostgreSQL レプリカ遅延
- PostgreSQL の基本的なレプリケーションは 非同期(async) で動作
- マスターデータベースの変更がレプリカに 数 ms ~ 数百 ms 遅れて反映される
add()
で接続される ManyToMany の中間テーブル記録が まだレプリカに反映されていなかったこと
✅ Django の on_commit() 動作方式
on_commit()
は Django トランザクションがコミットされた直後に実行- しかし、Celery は別プロセスであり、読み取りはレプリカ DB を使用
- 結果として on_commit 時点ではレプリカがまだ更新前の状態
6. 検証ポイント
- Post 作成ログは存在
- add() クエリログも正常に実行
- 実際のクエリの順序とタスク呼び出しの順序は正しい
- 問題は 照会時点の DB がレプリカであったこと
7. 解決策
1. Celery タスクで マスターデータベースから強制読み取り
# ORM の方法
post = Post.objects.using('default').get(id=post_id)
tags = post.tags.using('default').all()
categories = post.categories.using('default').all()
2. Celery 専用 DB ルーター構成
settings.py
に Celery タスクの場合は必ずマスターを使用するよう設定可能
3. レプリカが必ず最新の状態である必要がある場合:
- PostgreSQLの
synchronous_commit = on
設定が必要 (パフォーマンス低下の可能性あり)
8. 結論
この問題は Django の問題ではなく、非同期レプリケーション環境での遅延と ORM の読み取り優先ポリシーが衝突した結果だ。
解決の鍵は Celery タスクで強制的にマスターデータベースを使用することを保障することである。
9. Jesse のコメント
"on_commit()は明確に正しいタイミングで実行されたが、読んだDBがレプリカであったという事実が問題の本質だった。
DjangoとCeleryは何の問題もない。結局、システムアーキテクチャを理解し調整するのは開発者の責任である。"
Add a New Comment