Как избежать проблем с удалёнными объектами при десериализации в Celery
Что проверяют
Понимаете ли вы, что задача Celery выполняется позже и в другом процессе, а данные за это время успевают измениться или исчезнуть. Следом почти всегда спрашивают: что будет, если поставить задачу внутри транзакции, которая ещё не закоммичена.
Как отвечать
Начните с главного правила: в задачу передают идентификатор, а не объект модели. Сериализатор по умолчанию с Celery 4 это JSON, объект Django-модели он не упакует, а включать ради этого pickle плохо по двум причинам: воркер получит снимок устаревшего состояния, а десериализация pickle из брокера открывает дорогу выполнению чужого кода.
Дальше разберите три сценария:
- объект ещё не виден: задачу поставили внутри
transaction.atomic(), воркер забрал её раньше коммита и получилDoesNotExist. Лечится постановкой после коммита черезtransaction.on_commit(lambda: task.delay(obj.pk)), в свежих версиях Celery для Django есть готовыйdelay_on_commit(); - объект удалили до выполнения: ловите
Model.DoesNotExist, пишите в лог и завершайте задачу штатно. Повторять её бессмысленно, объект не вернётся; - объект изменился: читайте его заново в начале задачи, а если задача меняет данные, берите блокировку
select_for_update()внутри транзакции или обновляйте точечно черезupdate()иF().
Добавьте идемпотентность: задача может прийти повторно при acks_late=True или после падения воркера, и второй запуск не должен ломать данные. Если удаление в проекте мягкое (флаг вроде is_deleted), проверяйте и его, иначе задача обработает логически удалённую запись.
Где спотыкаются
Передают в задачу сам объект и потом вызывают у него save(). Воркер перезаписывает свежие изменения старым снимком, и такой баг ищут неделями.
Ставят задачу внутри транзакции, а гонку с коммитом «чинят» через retry(countdown=5). Это маскирует проблему и ломается под нагрузкой, правильный ответ здесь on_commit.
Вешают autoretry_for=(Exception,) на всё подряд, и задача с удалённым объектом ретраится до лимита, забивая очередь. Или наоборот, глушат исключение без лога, и потерю данных никто не замечает.