Инфраструктура сервиса: очереди задач (Celery)
Как только в запросе появляется долгая работа - письмо, отчёт, обработка загрузки - синхронный HTTP перестаёт годиться, и на собесе спрашивают про очереди задач. Ценят понимание at-least-once и идемпотентности: без них фоновые задачи задваивают эффекты.
Типовые формулировки: «что вынести в фон, а что оставить в запросе?», «что такое брокер?», «сколько раз выполнится задача».
Очередь, брокер и что выносить
Очередь задач (Celery, RQ) выносит долгую работу из HTTP-запроса в фон: эндпоинт кладёт задачу и СРАЗУ отвечает, а воркер разбирает её асинхронно. Запросы остаются быстрыми, клиент не ждёт. Брокер (Redis, RabbitMQ) это транспорт: приложение кладёт задачу в очередь, воркеры забирают и исполняют; сам брокер задачи не выполняет, он их только передаёт.
Что выносить: долгое и терпящее задержку - письма, отчёты, обработку загрузок, обращения к медленным API. Что оставить в запросе: то, без чего не собрать ответ - валидацию, чтение нужных данных. Держать долгую операцию прямо в запросе - обрекать клиента на ожидание и таймаут.
// Ментальная граница: в запросе - только то, что нужно для немедленного ответа; всё, что может подождать, в очередь.
- очередь задач
- вынос долгой работы из запроса в фон (Celery/RQ)
- брокер
- транспорт задач между приложением и воркерами
At-least-once и идемпотентность
Брокеры дают at-least-once: задача может выполниться ДВАЖДЫ - например, при ретрае после сбоя, когда результат уже был, а подтверждение потерялось. Значит обработчик обязан быть идемпотентным: проверять по ключу или статусу, что эффект ещё не применён, и не задваивать его (не списать деньги, не отправить письмо повторно).
И порядок: очередь задач НЕ гарантирует строгий порядок выполнения - задачи разбираются воркерами конкурентно. Если логика зависит от порядка, это надо решать явно, а не полагаться на очередь.
// Идемпотентность здесь - та же идея, что Idempotency-Key в API: повтор не должен менять итог против одного выполнения.
- at-least-once
- задача может доставиться повторно - нужна идемпотентность
Как отвечать: «Сколько раз выполнится задача из очереди?»
По умолчанию - как минимум один раз, но возможно и больше: брокеры дают гарантию at-least-once. Классический сценарий - воркер выполнил задачу, но упал или потерял связь до того, как подтвердил брокеру приём, и тот, не увидев подтверждения, отдаёт задачу снова. Поэтому я не рассчитываю на ровно один раз, а делаю обработчик идемпотентным: перед эффектом проверяю по ключу или статусу, не выполнена ли задача уже, и если да - просто выхожу. Так повторная доставка не задваивает списание или письмо. И на порядок выполнения не закладываюсь - очередь его не гарантирует, воркеры разбирают конкурентно.
Названа гарантия (at-least-once) с конкретным сценарием дубля, дано решение через идемпотентность и добавлена оговорка про порядок - ответ инженера, знающего распределённые грабли.
На чём валят
- −Держать долгую операцию в HTTP-запросе: клиент ждёт и ловит таймаут.
- −Считать задачу выполняемой ровно раз: at-least-once допускает повтор - делай идемпотентной.
- −Ждать от очереди задач строгого порядка выполнения - она его не гарантирует.
- −Путать брокер и воркер: брокер лишь передаёт задачу, исполняет её воркер.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Зачем в бэкенде очередь задач (Celery, RQ)?A)Заменить базу данных для хранения результатов работы приложенияB)Обеспечить, что задачи выполнятся строго в порядке их созданияC)Вынести долгую работу из запроса в фон, не заставляя клиента ждатьD)Ускорить сам код задачи, распараллелив его по ядрам процессора
показать ответ и разбор
+C)Вынести долгую работу из запроса в фон, не заставляя клиента ждать// разбор: Долгие операции (отправка писем, генерация отчёта, обработка видео, тяжёлый счёт) нельзя держать в HTTP-запросе — клиент ждёт и таймауты. Их кладут в очередь, воркер разбирает асинхронно, а эндпоинт сразу отвечает «принято». Так запросы остаются быстрыми, а тяжёлое уезжает в фон.
- Какую роль играет брокер (Redis, RabbitMQ) при работе Celery?A)Хранит исходный код задач и раздаёт его воркерам при стартеB)Исполняет сами задачи вместо воркеров, чтобы разгрузить приложениеC)Транспорт задач: держит очередь между продюсером и воркерамиD)Балансирует HTTP-трафик между инстансами веб-приложения по ролям
показать ответ и разбор
+C)Транспорт задач: держит очередь между продюсером и воркерами// разбор: Брокер — транспорт сообщений: приложение кладёт задачу (имя + аргументы) в очередь брокера, а воркеры Celery её оттуда забирают и исполняют. Redis прост и быстр, RabbitMQ богаче по гарантиям доставки и маршрутизации. Сам брокер задачи не выполняет — он лишь надёжно переносит их до воркеров.
- Celery-задача может выполниться дважды (ретрай, at-least-once). Каким должен быть её код?A)Обёрнутым в одну транзакцию, и тогда повтор станет невозможен сам собойB)Идемпотентным: повтор не должен задваивать эффектC)Строго однопоточным, ведь при повторе виноват параллелизм воркеровD)Максимально быстрым, чтобы просто не успеть выполниться повторно
показать ответ и разбор
+B)Идемпотентным: повтор не должен задваивать эффект// разбор: Брокеры обычно дают at-least-once: при сбое подтверждения задача доставляется повторно, поэтому она может выполниться дважды. Значит, эффект должен быть идемпотентным — проверять, не сделано ли уже (по ключу/статусу), прежде чем списывать деньги или слать письмо. Полагаться на «ровно один раз» нельзя.
- Что обычно выносят в фоновые задачи, а что оставляют в запросе?A)В фон выносят вообще всё, чтобы запросы отвечали мгновенноB)Ничего: фоновые задачи усложняют систему и обычно лишниеC)В фон — долгое и терпящее задержку; в запросе — быстрое и нужное сразуD)В фон — только чтение из базы, а запись держат в запросе
показать ответ и разбор
+C)В фон — долгое и терпящее задержку; в запросе — быстрое и нужное сразу// разбор: В фон уходит то, что долго и терпит задержку: рассылки, генерация отчётов/тумбнейлов, обработка загрузок, интеграции с медленными внешними API. В запросе оставляют то, без результата чего клиенту нельзя ответить: валидацию, авторизацию, чтение для рендера. Критерий — длительность и срочность, а не тип операции.
- Зачем выносить отправку письма в очередь задач, а не слать прямо в обработчике запроса?A)Чтобы письма отправлялись строго в том же порядке, в котором пришли запросыB)Чтобы эндпоинт ответил сразу, а долгую работу воркер сделал в фонеC)Чтобы зашифровать содержимое писем перед их отправкой пользователямD)Чтобы полностью отказаться от базы данных, храня все задачи только в очереди
показать ответ и разбор
+B)Чтобы эндпоинт ответил сразу, а долгую работу воркер сделал в фоне// разбор: Отправка письма (или обработка загрузки, генерация отчёта) — долгая и терпящая задержку операция. Делать её синхронно в обработчике значит заставить клиента ждать и держать веб-воркер занятым. Очередь задач принимает задание, эндпоинт сразу отвечает, а фоновый воркер выполняет работу асинхронно. Запросы остаются быстрыми, а тяжёлое уходит с горячего пути.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.