Можно ли передавать ORM объекты напрямую в задачи Celery или лучше передавать идентификаторы
Что проверяют
Понимаете ли вы, что задача Celery выполняется в другом процессе и, как правило, позже: всё, что вы кладёте в аргументы, должно пережить сериализацию и разрыв во времени. По ответу видно, работали ли вы с очередями на реальной нагрузке.
Как отвечать
Передавать нужно идентификаторы, а объект загружать уже внутри задачи. Причины по убыванию важности:
- сериализатор по умолчанию это JSON, и модель ORM в него просто не превращается. Включить
pickleтехнически можно, но тогда любой, кто допишется в брокер, получит выполнение кода на воркере; - объект привязан к сессии. У SQLAlchemy отсоединённый экземпляр упадёт на первом же ленивом атрибуте, у Django вы получите слепок полей на момент постановки задачи;
- данные успевают устареть. Между постановкой и стартом воркера проходит от секунд до часов, объект в аргументах хранит старое состояние, а по идентификатору вы всегда читаете свежее.
@shared_task
def process_user(user_id: int) -> None:
user = User.objects.get(pk=user_id)Отдельно упомяните гонку с транзакцией: если поставить задачу до коммита, воркер успеет прочитать базу раньше и не найдёт строку. В Django это лечится постановкой через transaction.on_commit.
Где спотыкаются
Говорят только про размер сообщения. Экономия трафика тут третьестепенна, главное это безопасность pickle и актуальность данных.
Не думают про исчезнувшую строку. Объект могли удалить между постановкой и выполнением, поэтому внутри задачи нужен честный путь на случай отсутствия записи, а не голый get, который роняет задачу в бесконечные повторы.
Кладут в аргументы «немного данных на всякий случай»: словарь полей рядом с идентификатором. Это возвращает ту же проблему устаревания, только тихо, потому что задача возьмёт свежую строку из базы и старые поля из аргументов.