Иногда запрос надо обработать быстро, но часть работы можно отложить. Отправить письмо, обновить кэш, уведомить другой сервис — всё это не обязательно делать до того, как клиент получит ответ. FastAPI даёт для этого инструмент BackgroundTasks. Но у него есть жёсткие ограничения, и если их не знать, можно потерять данные.
Зачем откладывать работу на после ответа
Представьте: пользователь регистрируется, и ему надо отправить письмо с подтверждением. Если отправлять письмо синхронно прямо в обработчике, пользователь ждёт. Почтовый сервер может тормозить, сеть — барахлить. Зачем держать клиента, если он уже зарегистрирован?
Ответ можно отправить сразу, а письмо — чуть позже. Это и есть фоновая задача.
BackgroundTasks: как это работает
BackgroundTasks — объект из стандартной библиотеки FastAPI. Он хранит список функций и запускает их после того, как ответ отправлен клиенту. Подключается как параметр обработчика — FastAPI создаёт его автоматически.
from fastapi import BackgroundTasks, APIRouter
router = APIRouter()
@router.post("/register", status_code=201)
async def register_user(
body: RegisterRequest,
background_tasks: BackgroundTasks,
):
user = await create_user(body)
background_tasks.add_task(send_welcome_email, user.email)
return {"id": user.id}
Клиент получает 201 сразу, как только пользователь создан. Письмо уходит после — в том же процессе, в той же памяти.
Функция в add_task может быть как обычной (def), так и асинхронной (async def) — FastAPI разберётся.
Где BackgroundTasks работает, а где нет
Главное, что надо понять: BackgroundTasks живёт внутри процесса. Никакой базы, никакой очереди — просто список функций в памяти. Отсюда все ограничения:
Не переживает остановку приложения. Процесс перезапустили, упал контейнер, развернули новую версию — задача в памяти исчезла. Никто об этом не узнает, никакого журнала нет.
Нет повторных попыток. Если функция упала с ошибкой — ошибка попадёт в лог (или не попадёт, если не настроено), но задача не повторится автоматически.
Делит ресурсы с обработкой запросов. Если фоновая задача тяжёлая — она конкурирует за CPU и память с запросами пользователей.
Из этого следует простое правило:
BackgroundTasksгодится только для того, что не жалко потерять.
Отправить необязательное уведомление — подходит. Провести платёж, подтвердить заказ, гарантированно что-то доставить — нет.
Когда нужна внешняя очередь задач
Как только задача становится важной, тяжёлой или должна повторяться при сбое — её выносят из процесса в отдельную систему. В мире Python для этого есть два основных инструмента:
- arq — асинхронная очередь задач на базе Redis. Хорошо вписывается в async-стек FastAPI.
- Celery — зрелый, функциональный, поддерживает Redis и RabbitMQ как брокер.
Принцип работы одинаковый: приложение кладёт задачу в очередь (запись в Redis или брокер), отдельный процесс-воркер её забирает и выполняет. Если воркер упал — задача остаётся в очереди и будет обработана при следующей попытке.
@router.post("/import", status_code=202)
async def import_catalog(body: ImportRequest, queue: QueueDep):
job = await queue.enqueue_job("process_import", body.file_id)
return {"job_id": job.job_id}
Код ответа 202 Accepted здесь точнее, чем 200: «задача принята», а не «задача выполнена». Это честная коммуникация с клиентом — он знает, что результат будет позже.
Расписание: почему его нет в FastAPI
FastAPI не предоставляет встроенного планировщика по времени — и это осознанное решение.
Веб-приложение обычно запускают в нескольких экземплярах, чтобы выдержать нагрузку. Если у каждого экземпляра есть свой планировщик, одна и та же задача «по расписанию» выполнится столько раз, сколько экземпляров запущено. Это почти всегда не то, чего хотят.
Регулярные задачи выносят наружу:
- Системный cron — если задача простая и запускается раз в сутки.
- CronJob в Kubernetes — если приложение работает в контейнерах; задача запускается отдельным контейнером по расписанию.
- Планировщик в очереди задач — arq и Celery умеют запускать задачи по расписанию через воркер; расписание живёт в одном месте и не зависит от числа экземпляров приложения.
Коротко
BackgroundTasksзапускает функции после отправки ответа — в том же процессе, в памяти.- Задача в
BackgroundTasksтеряется при рестарте процесса; повторных попыток нет. - Используйте
BackgroundTasksтолько для необязательной работы — той, потерю которой можно пережить. - Для важного, тяжёлого или повторяемого — arq или Celery с отдельным воркером и Redis-брокером.
- Статус
202 Acceptedчестнее200, когда работа только поставлена в очередь. - Встроенного планировщика в FastAPI нет намеренно; расписание выносят в cron, CronJob или в планировщик очереди задач.
Что почитать дальше
- Конкурентность и async в FastAPI — как устроена асинхронная обработка запросов.
- Наблюдаемость FastAPI — как следить за фоновыми задачами через логи и метрики.