Как реализовать endpoint для формирования и скачивания CSV отчета с большим объемом данных
Что проверяют
Понимаете ли вы, что большой отчёт нельзя собрать целиком в памяти процесса. Следом спрашивают, что делать, если выгрузка идёт минуты и упирается в таймауты прокси.
Как отвечать
Хороший ответ называет два режима, выбор между ними зависит от объёма и времени генерации.
Потоковая отдача, если отчёт собирается за секунды или десятки секунд:
- генератор строк: данные читаются порциями и сразу уходят в ответ. В Django это
StreamingHttpResponseсcsv.writerповерх объекта, чейwriteпросто возвращает строку (пример есть в документации), в FastAPIStreamingResponseс генератором; - чтение порциями: в Django
queryset.iterator(chunk_size=2000), на PostgreSQL он работает через серверный курсор, в SQLAlchemyyield_per(). Самый быстрый путь в PostgreSQL этоCOPY (SELECT ...) TO STDOUT WITH CSV HEADERчерезcursor.copy()в psycopg 3; - заголовки:
Content-Type: text/csv; charset=utf-8иContent-Disposition: attachment; filename="report.csv", для кириллицы в имени добавляютfilename*=UTF-8''.... Длина заранее неизвестна, ответ уходит чанками; - прокси: nginx по умолчанию буферизует ответ, для стрима отключите это заголовком
X-Accel-Buffering: noи проверьтеproxy_read_timeout.
Фоновая генерация, если отчёт тяжёлый: endpoint ставит задачу в Celery или другую очередь и отвечает 202 с ID задачи, воркер пишет файл в объектное хранилище, клиент по готовности получает presigned URL. Так вы не держите воркер gunicorn, у которого таймаут по умолчанию 30 секунд, и один готовый файл можно отдать нескольким людям.
Для пользователей Excel добавьте BOM (utf-8-sig), иначе кириллица откроется нечитаемыми символами.
Где спотыкаются
Собирают всё в io.StringIO или pandas.DataFrame и отдают через getvalue(). На миллионе строк это сотни мегабайт памяти на один запрос.
Забывают, что после первого чанка статус 200 уже отправлен. Ошибку посреди выгрузки не превратить в 500, клиент получит обрезанный файл, поэтому такие сбои нужно логировать отдельно.
Не экранируют ячейки, начинающиеся с =, +, - или @. Табличный редактор выполнит их как формулу, это CSV injection, такие значения предваряют апострофом.