Node.js 스트림으로 대용량 CSV 파일 처리하기

문제 상황

어드민에서 고객 데이터를 CSV로 일괄 업로드하는 기능을 만들었다. 처음엔 fs.readFile()로 전체 파일을 메모리에 올려 파싱했는데, 테스트 중 5GB 파일이 들어오자 서버가 OOM으로 죽었다.

// 기존 코드 - 메모리 터짐
const data = fs.readFileSync(filePath, 'utf8');
const rows = data.split('\n');
rows.forEach(row => processRow(row));

Stream으로 해결

Node.js의 스트림을 사용하면 파일을 청크 단위로 읽어 메모리 사용량을 대폭 줄일 수 있다. csv-parser 라이브러리와 조합했다.

const fs = require('fs');
const csv = require('csv-parser');

fs.createReadStream(filePath)
  .pipe(csv())
  .on('data', async (row) => {
    await processRow(row);
  })
  .on('end', () => {
    console.log('처리 완료');
  })
  .on('error', (err) => {
    console.error('에러:', err);
  });

성능 개선

  1. 메모리 사용량: 2.5GB → 250MB
  2. 처리 시간: 큰 차이 없음 (DB 저장이 병목)
  3. 서버 안정성: OOM 에러 완전 해결

추가 고려사항

스트림 방식은 백프레셔(backpressure) 관리가 중요하다. DB 저장 속도보다 읽기 속도가 빠르면 메모리가 쌓인다. highWaterMark 옵션으로 버퍼 크기를 조절하거나, async/await를 활용해 저장이 끝날 때까지 다음 청크를 대기시키는 방법을 썼다.

const stream = fs.createReadStream(filePath, {
  highWaterMark: 64 * 1024 // 64KB
});

대용량 파일 처리는 무조건 스트림이다. 메모리 효율과 안정성 면에서 확실한 개선을 체감했다.