Урок по треку Python для веба: Django и FastAPI 8 мин

Celery и очереди задач: ретраи, идемпотентность, гарантии доставки

Брокер и воркеры, ретраи и идемпотентность, acks_late, периодические задачи и альтернативы Celery.

Письма, отчёты, обработка файлов, вызовы медленных API: всё, что не должно держать HTTP-запрос, уходит в очередь задач. В Python-мире это чаще всего Celery. На собесе проверяют не синтаксис @shared_task, а понимание гарантий: что будет при падении воркера, повторе задачи, перезапуске брокера.

Как устроено

веб-процесс --(сообщение)--> брокер (RabbitMQ, Redis) --> воркеры Celery
                                                        \--> backend результатов (опционально)
@shared_task(bind=True, autoretry_for=(httpx.TransportError,),
             retry_backoff=True, retry_backoff_max=600, retry_jitter=True, max_retries=5)
def send_receipt(self, order_id: int) -> None:
    order = Order.objects.get(pk=order_id)
    mailer.send(order.email, render_receipt(order))

send_receipt.delay(order.id)                       # поставить в очередь
send_receipt.apply_async(args=[order.id], countdown=60, queue='mail')
  • Брокер хранит и доставляет сообщения. RabbitMQ даёт подтверждения и маршрутизацию, Redis проще в эксплуатации.
  • Воркер забирает сообщения и выполняет задачи. Параллелизм задаёт --concurrency и пул (prefork процессы по умолчанию, threads, gevent, solo).
  • Result backend хранит результаты, если они нужны. Не нужны: ignore_result=True.
  • Beat это планировщик периодических задач (аналог cron). Запускается одним экземпляром, иначе задачи задвоятся.

Что передавать в задачу

Только идентификаторы и простые данные: send_receipt.delay(order.id), а не объект модели. Объект сериализуется на момент постановки, к выполнению он устареет, а сериализатор json его вообще не примет.

Ставить задачу, которая читает только что созданные данные, нужно после коммита: transaction.on_commit(lambda: send_receipt.delay(order.id)). Иначе воркер может прийти раньше коммита и не найти заказ.

Гарантии и подтверждения

По умолчанию Celery подтверждает (ack) сообщение брокеру до выполнения задачи. Если воркер упадёт посередине, задача потеряна: это семантика «не более одного раза».

acks_late=True подтверждает после выполнения: при падении воркера сообщение вернётся в очередь и выполнится снова. Это «как минимум один раз», и задача обязана быть идемпотентной. Вместе с ним часто включают task_reject_on_worker_lost=True и worker_prefetch_multiplier=1 для длинных задач.

У Redis-брокера есть visibility_timeout (по умолчанию час): если задача не подтверждена за это время, её отдадут другому воркеру. Задачи с eta или countdown дальше этого таймаута начнут выполняться повторно.

Идемпотентность

Повторное выполнение не должно давать повторный эффект:

  • проверять состояние перед действием (if order.receipt_sent: return);
  • уникальные ключи и INSERT ... ON CONFLICT DO NOTHING;
  • ключ идемпотентности во внешних API (платёжные провайдеры его поддерживают);
  • атомарный захват: UPDATE ... SET status='processing' WHERE id=... AND status='new' и проверка числа обновлённых строк.

Ретраи

Повторяют только временные ошибки (таймаут, 503, обрыв соединения), с экспоненциальной задержкой и джиттером, с ограничением попыток. Ошибки данных (404, валидация) ретраить бессмысленно. После исчерпания попыток задачу логируют, алертят и откладывают для ручного разбора.

Альтернативы и паттерны

  • RQ, Dramatiq, arq, Taskiq: проще или асинхронные.
  • Kafka для потоков событий, где важны порядок внутри партиции и повторное чтение.
  • Transactional outbox: событие пишется в таблицу в той же транзакции, что и данные, отдельный процесс публикует его в брокер. Решает проблему «данные сохранили, а сообщение потеряли».

Частые ошибки

  • Передача ORM-объектов в задачу.
  • Постановка задачи до коммита транзакции.
  • acks_late без идемпотентности: двойные письма и списания.
  • Ретраи всех исключений подряд без ограничения.
  • Несколько экземпляров beat.
  • Долгие задачи без time_limit, зависшие навсегда.

Что спросят на собесе

  • Как устроен Celery: брокер, воркер, backend, beat.
  • Что передавать в задачу и почему не объект модели.
  • Что такое acks_late, at-most-once и at-least-once.
  • Как сделать задачу идемпотентной.
  • Как настроить ретраи и что ретраить нельзя.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.