Код-ревью Middle Потоки и буферы обновлено
Код-ревью: выгрузка и загрузка больших файлов
Вопрос
Эндпоинты отдают CSV-выгрузку и принимают загрузку файла. Найди проблемы с памятью, ошибками потоков и backpressure.
app.get('/export.csv', async (req, res) => {
const rows = await db.query('SELECT * FROM events');
let csv = '';
for (const r of rows) csv += r.id + ',' + r.name + '\n';
res.send(csv);
});
app.get('/download/:name', (req, res) => {
const stream = fs.createReadStream('/files/' + req.params.name);
stream.pipe(res);
});
app.post('/upload', (req, res) => {
const out = fs.createWriteStream('/tmp/upload.bin');
req.on('data', (chunk) => out.write(chunk));
req.on('end', () => {
out.end();
res.json({ ok: true });
});
});
Коротко
Правила работы с большими данными в Node: - не собирать всё в память: курсор БД → Transform в CSV → ответ, всё потоками; - соединять потоки через pipeline из node:stream/promises, а не .pipe(): pipeline пробрасывает ошибки, учитывает backpressure и уничтожает все потоки при сбое или обрыве клиента; - никогда не…
Что должен найти ревьюер
- Производительностьстрока 2Вся таблица событий грузится в память одним запросом. Нужен курсор или пагинация по ключу и стриминг ответа.
- Ошибкастрока 4Значения не экранируются: запятая, кавычка или перевод строки в
nameломает CSV; а строка, начинающаяся с=, это CSV-инъекция в Excel. - Производительностьстрока 4Склейка огромной строки в памяти: пик памяти растёт с размером таблицы. Писать в ответ построчно через поток.
- Безопасностьстрока 9Имя файла из URL без проверки: path traversal (
..%2F..%2Fetc%2Fpasswd). - Ошибкастрока 10
.pipe()не обрабатывает ошибки: нет файла или ошибка чтения дают необработанное событие error (падение процесса) и висящий ответ; при обрыве клиента поток чтения не закрывается. Нуженpipelineс обработкой ошибки. - Производительностьстрока 15Результат
out.writeигнорируется: если диск медленнее сети, данные копятся в памяти без ограничений (нет backpressure).pipeline(req, out)решает это. - Ошибкастрока 14Все загрузки пишутся в один и тот же файл: параллельные загрузки перезапишут друг друга. Нет лимита размера.
- Ошибкастрока 18Ответ «ok» уходит до того, как файл реально записан (
endасинхронный), и ошибки записи не обрабатываются. Отвечать поfinishили послеawait pipeline.
Разбор
Правила работы с большими данными в Node:
- не собирать всё в память: курсор БД → Transform в CSV → ответ, всё потоками;
- соединять потоки через
pipelineизnode:stream/promises, а не.pipe():pipelineпробрасывает ошибки, учитывает backpressure и уничтожает все потоки при сбое или обрыве клиента; - никогда не игнорировать
falseизwrite(); - на загрузку ставить лимит размера, уникальное имя файла и отвечать только после
finish.
Отдельно про CSV: экранирование полей по RFC 4180 (кавычки вокруг поля, удвоение кавычек) и защита от формул в Excel (префикс ' для значений, начинающихся с =, +, -, @).
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Предыдущий вопрос
Сессии пользователей по потоку событий
Следующий вопрос
Загрузка файлов по 5 ГБ через Node-сервис