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.

Продолжить на практике