BackgroundTasks в FastAPI или очередь: куда отправить долгую работу
Пользователь нажимает «Сформировать отчёт». Сервер должен прочитать данные, собрать файл и отправить письмо. Держать вкладку в ожидании несколько минут неудобно, поэтому хочется сразу вернуть «принято», а работу закончить в фоне. В FastAPI для этого есть BackgroundTasks. Вопрос в том, что именно мы обещаем пользователю этим коротким ответом.
Если отчёт можно повторно запросить, потеря отдельного запуска неприятна, но поправима. Если это оплаченный документ, приложение должно помнить о нём после перезапуска. На уровне HTTP оба случая выглядят одинаково; устройство фоновой работы у них разное.
Что произойдёт после ответа
Вот небольшой файл main.py. Для запуска установите python -m pip install fastapi uvicorn, затем выполните python -m uvicorn main:app.
import logging
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
log = logging.getLogger("uvicorn.error")
def record_request(report_id: int):
log.info("Requested report %s", report_id)
@app.post("/reports/{report_id}", status_code=202)
def request_report(report_id: int, tasks: BackgroundTasks):
tasks.add_task(record_request, report_id)
return {"report_id": report_id, "status": "accepted"}
Запрос curl -X POST http://127.0.0.1:8000/reports/42 вернёт 202, а в журнале появится номер. Здесь мы только записываем сообщение: генератор отчёта подключается на место record_request. Функция выполняется в процессе приложения после отправки ответа. Брокер и отдельный воркер от добавления BackgroundTasks не появляются.
Где проходит граница обещания
Для короткого необязательного действия этого достаточно. Для отчёта, который должен пережить сбой, сначала нужна запись задания: идентификатор, параметры, статус и место результата. Клиент получает идентификатор и может отдельно спросить о состоянии. Сохранённая запись позволяет отличить «ещё не начали» от «запустили, но не закончили».
Отдельный обработчик забирает работу из очереди. После сбоя он может повторить её, поэтому генерация и особенно отправка письма должны учитывать повтор. Например, один и тот же report_id не должен создавать две платные операции. Очередь сама по себе не решает эту часть бизнес-логики.
Есть и разрыв между записью в базе и отправкой сообщения брокеру. Если обе операции важны, имеет смысл сохранять событие рядом с заданием в одной транзакции, а публиковать его отдельным процессом. Такой подход обычно называют transactional outbox.
Как проверить своё решение
На учебном стенде запустите долгую задачу и перезапустите приложение после ответа 202. Затем проверьте, можно ли найти задание, понять его состояние и безопасно повторить работу. Именно этот опыт показывает, подходит ли выбранный механизм вашему обещанию пользователю.
Если основная трудность уже в запуске нескольких процессов, продолжите с числом Uvicorn-воркеров и памятью.
Собрать следующий этап проекта
Хотите собрать API целиком, а не останавливаться на отдельном обработчике? На бесплатном курсе FastAPI вы сделаете сервис с базой, регистрацией, проверкой прав и тестами. Это проект, который можно развивать дальше и показывать вместе с исходниками.
Посмотреть программу: FastAPI для начинающих: API с базой данных и тестами
Проверить детали: FastAPI BackgroundTasks, Starlette background tasks.
Продолжить на практике
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
- Docker и Docker Compose: контейнеризация приложений ↗
Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.
