Код-ревью 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 и уничтожает все потоки при сбое или обрыве клиента; - никогда не…

Что должен найти ревьюер

  1. Производительностьстрока 2
    Вся таблица событий грузится в память одним запросом. Нужен курсор или пагинация по ключу и стриминг ответа.
  2. Ошибкастрока 4
    Значения не экранируются: запятая, кавычка или перевод строки в name ломает CSV; а строка, начинающаяся с =, это CSV-инъекция в Excel.
  3. Производительностьстрока 4
    Склейка огромной строки в памяти: пик памяти растёт с размером таблицы. Писать в ответ построчно через поток.
  4. Безопасностьстрока 9
    Имя файла из URL без проверки: path traversal (..%2F..%2Fetc%2Fpasswd).
  5. Ошибкастрока 10
    .pipe() не обрабатывает ошибки: нет файла или ошибка чтения дают необработанное событие error (падение процесса) и висящий ответ; при обрыве клиента поток чтения не закрывается. Нужен pipeline с обработкой ошибки.
  6. Производительностьстрока 15
    Результат out.write игнорируется: если диск медленнее сети, данные копятся в памяти без ограничений (нет backpressure). pipeline(req, out) решает это.
  7. Ошибкастрока 14
    Все загрузки пишутся в один и тот же файл: параллельные загрузки перезапишут друг друга. Нет лимита размера.
  8. Ошибкастрока 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 ошибок.