1. Обзор проблемы
В структуре, использующей transaction.on_commit()
для вызова задач Celery в среде Django,
возникает проблема: данные ManyToMany поля пусты внутри задачи Celery.
2. Симптомы
- После завершения
post.categories.add(...)
иpost.tags.add(...)
- Вызывается
translate_post.delay()
через обратный вызовtransaction.on_commit()
- Внутри задачи Celery при запросе
post.categories.all()
,post.tags.all()
возвращается пустой список ([]
)
3. Конфигурация окружения
- WRITE: Постгрес SQL Мастер на GCP VM
- READ: Постгрес SQL Реплика (потоковое репликация) на Raspberry Pi
- Запросы на чтение направляются к реплике через Django DB router
- Модель Post, использующая ManyToMany поля
4. Анализ логов
✅ Резюме последовательности (по времени для примера)
13:13:56.728
— Создание поста13:14:00.922
— Последний запросTaggedItem
(ManyToMany)13:14:01.688
— Выполнениеon_commit()
→ Вызов Celery13:14:01.772
— Результаты запроса внутриtranslate_post()
:- категории:
[]
- теги:
[]
- категории:
То есть, последовательность верная, но содержимое не обновлено
5. Анализ причин
✅ Задержка реплики PostgreSQL
- Основная репликация PostgreSQL работает в асинхронном режиме
- Изменения в Master DB отражаются в Replica с задержкой от нескольких миллисекунд до сотен миллисекунд
- Запись в промежуточной таблице ManyToMany, связанная с
add()
, еще не была отражена в реплике
✅ Рабочий механизм on_commit() в Django
on_commit()
выполняется сразу после коммита транзакции Django- Однако Celery является отдельным процессом, и чтение происходит из репликанта
- В результате на момент on_commit реплика еще не обновлена
6. Проверочные моменты
- Журнал создания поста существует
- Запросы
add()
также выполнены корректно - Фактический порядок запросов и порядок вызова задач правильный
- Проблема в том, что база данных в момент запроса была репликой
7. Решения
1. Принудительное чтение из мастера DB в задаче 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. В случае, если реплика обязательно должна быть актуальной:
- Необходима настройка
synchronous_commit = on
в PostgreSQL (может ухудшить производительность)
8. Заключение
Эта проблема не относится к Django, это результат конфликта между асинхронной репликацией и политикой приоритета чтения ORM.
Ключ к решению — обеспечить использование мастера DB в задачах Celery.
9. Комментарий Джесси
"on_commit() действительно был выполнен в точный момент, но факт, что база данных для чтения была репликой, был сутью проблемы.
Django и Celery не виноваты. В конечном итоге, разработчику нужно понять и согласовать архитектуру системы."
댓글이 없습니다.