Node.js 스트림으로 대용량 CSV 파일 처리하기
문제 상황
데이터 마이그레이션 작업 중 12GB 크기의 CSV 파일을 파싱해서 데이터베이스에 삽입해야 했다. 처음에는 fs.readFile()로 전체를 읽어서 처리하려 했는데, 당연히 메모리 부족으로 프로세스가 죽었다.
스트림 기반 해결
Node.js의 스트림을 사용하면 파일을 청크 단위로 읽으면서 처리할 수 있다. csv-parser 라이브러리와 조합했다.
const fs = require('fs');
const csv = require('csv-parser');
const { pipeline } = require('stream');
const { Transform } = require('stream');
const batchSize = 1000;
let batch = [];
const batchTransform = new Transform({
objectMode: true,
async transform(row, encoding, callback) {
batch.push(row);
if (batch.length >= batchSize) {
const currentBatch = [...batch];
batch = [];
try {
await db.insertMany(currentBatch);
callback();
} catch (err) {
callback(err);
}
} else {
callback();
}
},
async flush(callback) {
if (batch.length > 0) {
try {
await db.insertMany(batch);
callback();
} catch (err) {
callback(err);
}
} else {
callback();
}
}
});
pipeline(
fs.createReadStream('large-file.csv'),
csv(),
batchTransform,
(err) => {
if (err) {
console.error('처리 실패:', err);
} else {
console.log('완료');
}
}
);
주요 포인트
백프레셔 처리
스트림은 자동으로 백프레셔를 처리한다. DB 삽입이 느려지면 파일 읽기 속도도 자동으로 조절된다.
배치 처리
한 줄씩 DB에 삽입하면 너무 느리다. 1000개씩 묶어서 insertMany()로 처리하니 속도가 10배 이상 빨라졌다.
에러 핸들링
pipeline()을 사용하면 에러가 발생했을 때 자동으로 모든 스트림을 정리해준다. 직접 pipe()를 체이닝하는 것보다 안전하다.
결과
12GB 파일을 약 15분만에 처리했고, 메모리 사용량은 150MB 내외로 일정하게 유지됐다. 스트림의 위력을 제대로 느낀 작업이었다.