Node.js 스트림으로 대용량 CSV 파일 처리하기
문제 상황
고객사에서 주문 데이터를 CSV로 업로드하는 기능을 만들었는데, 테스트 때는 괜찮았지만 실제 500MB 파일이 들어오자 서버 메모리가 부족해졌다. fs.readFile로 전체를 읽고 파싱하는 방식이었다.
스트림 기반 처리로 변경
Node.js의 스트림 API를 사용하면 파일을 청크 단위로 읽어 메모리 사용량을 크게 줄일 수 있다.
const fs = require('fs');
const readline = require('readline');
const stream = require('stream');
async function processLargeCSV(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let lineCount = 0;
const batchSize = 1000;
let batch = [];
for await (const line of rl) {
if (lineCount === 0) {
lineCount++;
continue; // 헤더 스킵
}
const row = line.split(',');
batch.push({
orderId: row[0],
amount: parseFloat(row[1]),
date: row[2]
});
if (batch.length >= batchSize) {
await insertBatch(batch);
batch = [];
}
lineCount++;
}
if (batch.length > 0) {
await insertBatch(batch);
}
console.log(`처리 완료: ${lineCount}건`);
}
async function insertBatch(batch) {
// DB 배치 인서트
await db.orders.bulkInsert(batch);
}
결과
- 메모리 사용량: 1.2GB → 120MB
- 처리 시간: 큰 차이 없음 (오히려 배치 인서트로 약간 빨라짐)
- 서버 안정성 확보
배치 크기는 DB 성능에 따라 조정이 필요하다. 1000건 단위가 현재 환경에서는 적절했다.
참고사항
스트림을 사용할 때는 에러 핸들링과 스트림 종료 처리를 꼼꼼히 해야 한다. pipe 메서드를 쓰면 더 간결하게 구현할 수 있지만, 비즈니스 로직이 복잡하면 위처럼 명시적으로 제어하는 게 나았다.