Node.js 스트림으로 대용량 CSV 파일 처리 최적화
문제 상황
데이터 마이그레이션 작업 중 5GB CSV 파일을 파싱해야 했다. 기존 코드는 fs.readFile로 전체 파일을 메모리에 올려 처리했는데, 프로세스가 계속 죽었다. Node.js의 기본 메모리 제한은 1.5GB 정도였다.
해결 방법
fs.createReadStream과 readline 모듈을 조합해 라인 단위로 처리하도록 변경했다.
const fs = require('fs');
const readline = require('readline');
async function processLargeCSV(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let processedCount = 0;
const batchSize = 1000;
let batch = [];
for await (const line of rl) {
const row = parseCSVLine(line);
batch.push(row);
if (batch.length >= batchSize) {
await insertBatch(batch);
batch = [];
processedCount += batchSize;
console.log(`Processed: ${processedCount}`);
}
}
if (batch.length > 0) {
await insertBatch(batch);
}
}
function parseCSVLine(line) {
return line.split(',').map(col => col.trim());
}
성능 개선
- 메모리 사용량: 4.8GB → 250MB
- 처리 시간: 실패 → 약 8분
- 배치 처리로 DB INSERT 성능도 개선
스트림은 백프레셔도 자동으로 처리해주기 때문에 안정적이었다. DB 쓰기가 느려지면 자동으로 파일 읽기 속도가 조절됐다.
추가 고려사항
CSV 파싱은 csv-parser 라이브러리를 쓰는 게 더 안전하다. 따옴표 이스케이핑, 멀티라인 필드 등 엣지 케이스가 많기 때문이다. 프로덕션에서는 이를 적용할 예정이다.
const csv = require('csv-parser');
fs.createReadStream(filePath)
.pipe(csv())
.on('data', (row) => {
// 처리 로직
});
대용량 파일 처리는 스트림이 답이다.