Как реализовать постепенную отдачу данных клиенту при формировании большого отчета
Что проверяют
Понимаете ли вы разницу между «собрать отчёт в памяти и отдать» и «отдавать по мере чтения» и где в Python-стеке эта потоковость может молча сломаться. Следом спросят, что делать, если отчёт считается десять минут.
Как отвечать
Сначала разведите два случая. Если строки можно отдавать по мере чтения, это потоковый ответ. Если отчёт долгий или его надо собрать целиком (XLSX, PDF), это фоновая задача: Celery, RQ или arq, ответ 202 с идентификатором, файл в S3-совместимом хранилище и ссылка для скачивания.
Потоковый ответ во фреймворках:
- FastAPI и Starlette:
StreamingResponseпринимает генератор. Синхронный генератор Starlette прогоняет в пуле потоков, асинхронный работает в цикле событий; - Django:
StreamingHttpResponseс генератором строк; - Flask:
Response(gen()), а если генератору нужен контекст запроса, его оборачивают вstream_with_context.
Генератор бесполезен, если данные сначала целиком загружены из базы. Читать надо курсором на стороне сервера: именованный курсор в psycopg, execution_options(stream_results=True) с yield_per в SQLAlchemy, .iterator(chunk_size=...) в Django ORM. Формат выбирайте построчный: CSV через csv.writer поверх io.StringIO пачками или NDJSON. Для индикатора прогресса подойдут Server-Sent Events.
Где спотыкаются
Внутри асинхронного генератора вызывают синхронный драйвер базы. Цикл событий блокируется, и пока отдаётся один отчёт, остальные запросы стоят; нужен либо асинхронный драйвер, либо обычный синхронный генератор.
Запускают долгую отдачу под Gunicorn с синхронными воркерами. Таймаут воркера по умолчанию 30 секунд, после чего его убивают посреди ответа. Нужны воркеры uvicorn или gthread и осознанные таймауты, в том числе на Nginx, который вдобавок буферизует ответ, пока не отключите это заголовком X-Accel-Buffering: no.
Не думают об ошибке на середине. Код 200 уже отправлен, поэтому в поток пишут явный маркер сбоя, а клиент проверяет, что получил завершающую строку. Иначе обрезанный CSV выглядит как нормальный отчёт, только короче.