← назад к разделу

Иногда запрос надо обработать быстро, но часть работы можно отложить. Отправить письмо, обновить кэш, уведомить другой сервис — всё это не обязательно делать до того, как клиент получит ответ. 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 — как следить за фоновыми задачами через логи и метрики.