Как реализовать взаимодействие фронтенда и бэкенда для генерации тяжелого отчета без таймаута
Что проверяют
Понимаете ли вы, что долгую работу нужно вынести из HTTP-запроса, и как фронтенд узнаёт о готовности результата. Следом спрашивают, что будет, если воркер упадёт посреди задачи, и как не запустить один и тот же отчёт десять раз при повторных кликах.
Как отвечать
Опишите схему из четырёх шагов:
- постановка:
POST /reportsпроверяет параметры, создаёт запись задачи в базе, кладёт задачу в очередь (Celery, RQ, Dramatiq, arq) и сразу отвечает202 Acceptedс идентификатором; - выполнение: воркер читает данные курсором или пачками (в SQLAlchemy это
yield_per), пишет CSV или XLSX по частям, кладёт файл в S3-совместимое хранилище и обновляет статус и прогресс в базе; - статус: фронтенд опрашивает
GET /reports/{id}раз в несколько секунд. Если нужен живой прогресс, подойдут SSE (StreamingResponseсtext/event-streamв FastAPI) или WebSocket, но поллинг проще и спокойно переживает перезапуск сервера; - выдача: по готовности отдаёте ссылку на скачивание с ограниченным сроком жизни, а не гоните файл через приложение.
Про надёжность: acks_late=True в Celery возвращает задачу в очередь, если воркер умер, поэтому сама задача должна быть идемпотентной. soft_time_limit и time_limit страхуют от зависаний. Повторные клики гасятся дедупликацией: хэш параметров плюс проверка, нет ли уже такой задачи в работе. BackgroundTasks в FastAPI для тяжёлого отчёта не годится: задача выполняется в том же процессе и погибает вместе с ним.
Где спотыкаются
Поднимают таймауты nginx и gunicorn до десяти минут. Воркер веб-сервера занят, соединение всё равно рвёт какой-нибудь прокси по пути, а повтор запускает второй экземпляр отчёта.
Верят статусу PENDING в Celery. Для несуществующего или устаревшего идентификатора AsyncResult тоже возвращает PENDING, поэтому статус задачи надёжнее хранить в своей таблице.
Грузят весь набор данных в память через .all() или pandas и кладут готовый файл в result backend на Redis. Воркер упирается в память, а Redis разрастается.