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. Анализ логов

✅ Резюме последовательности (по времени для примера)

  1. 13:13:56.728 — Создание поста
  2. 13:14:00.922 — Последний запрос TaggedItem (ManyToMany)
  3. 13:14:01.688 — Выполнение on_commit() → Вызов Celery
  4. 13: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 не виноваты. В конечном итоге, разработчику нужно понять и согласовать архитектуру системы."