Node.js 스트림으로 대용량 CSV 파일 처리 최적화
문제 상황
고객사 데이터 마이그레이션 작업 중 5GB 크기의 CSV 파일을 파싱해서 DB에 insert해야 했다. 초기에는 fs.readFile로 전체를 읽어서 처리했는데, 메모리 부족으로 프로세스가 죽는 문제가 발생했다.
해결 방법
Node.js의 스트림 API를 사용해 청크 단위로 처리하도록 변경했다. csv-parser 라이브러리와 조합해서 구현했다.
const fs = require('fs');
const csv = require('csv-parser');
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 (error) {
callback(error);
}
} else {
callback();
}
},
async flush(callback) {
if (batch.length > 0) {
await db.insertMany(batch);
}
callback();
}
});
fs.createReadStream('large-data.csv')
.pipe(csv())
.pipe(batchTransform)
.on('finish', () => console.log('완료'))
.on('error', (err) => console.error(err));
결과
- 메모리 사용량: 2.5GB → 120MB로 감소
- 처리 시간: 약간 증가했지만 안정성 확보
- 배치 단위로 DB insert해서 쿼리 수도 최적화
추가 고려사항
백프레셔(backpressure) 처리도 중요했다. DB insert 속도가 파일 읽기 속도보다 느릴 때 메모리가 쌓이는 문제가 있었는데, highWaterMark 옵션으로 버퍼 크기를 제한해서 해결했다.
스트림은 러닝 커브가 있지만, 대용량 데이터 처리에서는 필수적인 도구라는 걸 다시 한번 확인했다.