Код-ревью Middle Celery и очереди спрашивают часто обновлено
Код-ревью задачи Celery
Вопрос
Найдите проблемы в задаче начисления бонусов и месте её вызова.
from celery import shared_task
from django.db import transaction
from .models import Order, Bonus
@shared_task(autoretry_for=(Exception,), max_retries=None)
def accrue_bonus(order):
Bonus.objects.create(
user_id=order.user_id,
amount=order.total // 10,
)
notify_user(order.user_id)
def checkout(cart):
with transaction.atomic():
order = Order.objects.create(user=cart.user, total=cart.total)
accrue_bonus.delay(order)
cart.items.all().delete()
return order
Коротко
Исправленный вариант: Для строгой гарантии у модели Bonus стоит уникальное ограничение на orderid.
Что должен найти ревьюер
- Ошибкастрока 6Ретраи на любое исключение без ограничения попыток и без задержки: баг в коде будет повторяться бесконечно
- Ошибкастрока 7В задачу передаётся ORM-объект: с JSON не сериализуется, с pickle устареет; передавать
order.id - Ошибкастрока 8Задача не идемпотентна: при ретрае бонус начислится повторно; нужна уникальность по заказу (
get_or_createили уникальный индекс) - Ошибкастрока 18Задача ставится до коммита: воркер может не найти заказ, а при откате бонус начислится за несуществующий заказ;
transaction.on_commit
Разбор
Исправленный вариант:
@shared_task(autoretry_for=(ConnectionError, TimeoutError), retry_backoff=True,
retry_jitter=True, max_retries=5, acks_late=True)
def accrue_bonus(order_id: int) -> None:
order = Order.objects.get(pk=order_id)
bonus, created = Bonus.objects.get_or_create(
order_id=order.id,
defaults={'user_id': order.user_id, 'amount': order.total // 10},
)
if created:
notify_user(order.user_id)
def checkout(cart):
with transaction.atomic():
order = Order.objects.create(user=cart.user, total=cart.total)
cart.items.all().delete()
transaction.on_commit(lambda: accrue_bonus.delay(order.id))
return order
Для строгой гарантии у модели Bonus стоит уникальное ограничение на order_id.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.