Какие варианты реализации взаимодействия фронтенда и бэкенда для долгих задач с отображением прогресса
Что проверяют
Понимаете ли вы, что долгую задачу нельзя держать внутри HTTP-запроса, и умеете ли выбрать способ доставки прогресса под нагрузку и инфраструктуру. Следом обычно спрашивают, что будет, если пользователь перезагрузит страницу или сервис запущен в нескольких экземплярах.
Как отвечать
Сначала общая схема, от неё выбор транспорта не зависит. Запрос ставит задачу в очередь и сразу отвечает 202 Accepted с идентификатором задачи. Выполняет её воркер: Celery, RQ, Dramatiq или arq. Прогресс воркер пишет в Redis или базу, в Celery это self.update_state(state="PROGRESS", meta={"done": i, "total": n}) в задаче с bind=True, а читается он через AsyncResult(task_id). Состояние живёт вне веб-процесса, поэтому после перезагрузки страницы клиент просто запросит его заново.
Способы доставить прогресс на фронтенд:
- короткий опрос (polling): клиент раз в пару секунд запрашивает
GET /tasks/{id}. Проще всего, работает через любые прокси, для большинства задач этого достаточно; - long polling: сервер держит запрос, пока статус не изменится. Меньше пустых ответов, но больше возни с таймаутами;
- SSE: однонаправленный поток
text/event-stream, в браузереEventSourceс автоматическим переподключением. В FastAPI черезStreamingResponse, в Django через асинхронныйStreamingHttpResponse. За nginx нужно выключить буферизацию ответа; - WebSocket: двусторонний канал,
WebSocketв FastAPI или Django Channels. Оправдан, если клиент тоже что-то шлёт, например отмену задачи.
Для SSE и WebSocket при нескольких экземплярах нужен канал между воркером и веб-процессом, обычно Redis pub/sub: воркер Celery не знает, в каком процессе открыто соединение пользователя.
Где спотыкаются
Выполняют задачу прямо в обработчике. Через 30 секунд gunicorn по умолчанию убивает воркер, а прокси и балансировщики рвут долгие соединения по своим таймаутам.
Берут BackgroundTasks из FastAPI вместо очереди. Задача живёт в том же процессе, пропадает при рестарте и не видна другим экземплярам.
Хранят прогресс в памяти процесса. С двумя воркерами gunicorn часть запросов статуса вернёт «задача не найдена».