Ответ
Задача: Обработка большого CSV-файла (сотни МБ) с трансформацией данных и записью результата в базу данных.
Наивное (медленное) решение: Загрузка всего файла в память.
const fs = require('fs');
const csv = require('csv-parser');
// Проблема: весь файл загружается в память
fs.readFile('huge-file.csv', 'utf8', (err, data) => {
if (err) throw err;
const lines = data.split('n');
const results = [];
lines.forEach(line => {
// Обработка каждой строки
const processed = transformLine(line);
results.push(processed);
});
// Массив results может занимать гигабайты памяти
saveToDatabase(results);
});
Оптимизированное решение с использованием Streams и пайплайнов:
const fs = require('fs');
const { pipeline } = require('stream');
const csv = require('csv-parser');
const { Transform } = require('stream');
// Кастомный Transform stream для обработки данных
const transformer = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
// Обработка одной строки CSV
const processed = {
id: chunk.id,
value: calculateValue(chunk),
timestamp: new Date()
};
callback(null, processed);
}
});
// Пайплайн: чтение → парсинг CSV → трансформация → запись в БД
pipeline(
fs.createReadStream('huge-file.csv'),
csv(),
transformer,
async function (source) {
for await (const chunk of source) {
// Пакетная запись в БД
await saveToDatabaseBatch(chunk);
}
},
(err) => {
if (err) {
console.error('Pipeline failed:', err);
} else {
console.log('Pipeline succeeded');
}
}
);
Ключевые оптимизации:
- Streams вместо загрузки в память — обрабатываем данные по частям.
- Backpressure управляется автоматически.
- Асинхронная обработка не блокирует Event Loop.
- Пакетная запись в БД уменьшает количество запросов.
Дополнительные подходы для CPU-heavy задач:
- Вынос тяжёлых вычислений в Worker Threads.
- Использование setImmediate() для разбивки долгих синхронных задач.
Ответ 18+ 🔞
А, ну ты глянь, какие задачи у людей бывают! Обработка CSV-файла на сотни мегабайт. Ёпта, классика жанра. Каждый второй думает: «Да чё там, файлик как файлик», а потом получает овердохуища данных в оперативке и сервер накрывается медным тазом.
Вот смотри, как обычно делают новички, у которых доверия ебать ноль к объёмам данных. Берут и грузят весь файл в память разом.
const fs = require('fs');
const csv = require('csv-parser');
// Проблема: весь файл загружается в память
fs.readFile('huge-file.csv', 'utf8', (err, data) => {
if (err) throw err;
const lines = data.split('n');
const results = [];
lines.forEach(line => {
// Обработка каждой строки
const processed = transformLine(line);
results.push(processed);
});
// Массив results может занимать гигабайты памяти
saveToDatabase(results);
});
Ну и что мы видим? fs.readFile – это пиздопроебибна идея для большого файла. Он тупо вывалит всё содержимое в переменную data. Потом её ещё на строки режут, потом в массив пихают... Да это же хитрая жопа, которая взорвётся при первом же гигабайте! Памяти ебушки-воробушки начнут летать, а приложение просто бздеть и умрёт.
А теперь, чувак, смотри как надо, по-взрослому. Берём потоки (Streams). Это как водопровод: данные текут по трубам, обрабатываются по чуть-чуть и не забивают всю систему.
const fs = require('fs');
const { pipeline } = require('stream');
const csv = require('csv-parser');
const { Transform } = require('stream');
// Кастомный Transform stream для обработки данных
const transformer = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
// Обработка одной строки CSV
const processed = {
id: chunk.id,
value: calculateValue(chunk),
timestamp: new Date()
};
callback(null, processed);
}
});
// Пайплайн: чтение → парсинг CSV → трансформация → запись в БД
pipeline(
fs.createReadStream('huge-file.csv'),
csv(),
transformer,
async function (source) {
for await (const chunk of source) {
// Пакетная запись в БД
await saveToDatabaseBatch(chunk);
}
},
(err) => {
if (err) {
console.error('Pipeline failed:', err);
} else {
console.log('Pipeline succeeded');
}
}
);
Вот это уже дело! Ёперный театр, да? Всё красиво и правильно.
- Потоки вместо грубой силы. Файл читается кусочками, парсится по строчкам, и каждая строчка сразу уходит на обработку. В памяти в один момент времени — лишь маленький кусочек данных, а не хуй с горы.
- Backpressure. Это когда следующая труба в пайплайне говорит: «Э, подозрение ебать чувствую, я ещё не переварил предыдущий кусок, притормози!» И чтение файла автоматически замедляется. Всё само регулируется, терпения ноль ебать не нужно.
- Асинхронность. Event Loop не блокируется долгими операциями, сервер может параллельно другие запросы обрабатывать.
- Пакетная запись. Это вообще святое. Не надо дёргать базу данных на каждую строчку — э сабака сука по производительности. Накопили 1000 обработанных записей — одним запросом вкинули. Экономия — ядрёна вошь.
А если там внутри calculateValue(chunk) такая манда с ушами, которая ядро процессора в труху превращает? Тогда уже э бошка думай про Worker Threads. Выноси эти тяжёлые вычисления в отдельный поток, чтобы главный не подвис. Или хотя бы setImmediate() используй, чтобы дать Event Loop'у передохнуть между порциями данных.
Короче, мораль простая: не лезь в пизду с readFile для больших данных. Потоки — твой друг. Иначе будет тебе хиросима в оперативной памяти, а не обработка файла.