Node.js 스트림으로 대용량 CSV 파일 처리 최적화

문제 상황

데이터 분석팀에서 매일 생성되는 10GB 이상의 CSV 로그 파일을 파싱하여 DB에 적재하는 배치 작업이 필요했다. 기존 코드는 fs.readFileSync로 전체 파일을 메모리에 올린 후 처리했는데, 힙 메모리 부족으로 프로세스가 죽는 문제가 발생했다.

기존 코드의 문제

const fs = require('fs');
const data = fs.readFileSync('huge-file.csv', 'utf-8');
const lines = data.split('\n');
// OOM 발생

전체 파일을 메모리에 로드하면서 Node.js의 기본 힙 제한(약 1.4GB)을 초과했다.

스트림 기반 해결

readlinestream 모듈을 활용해 라인 단위로 처리하도록 변경했다.

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 batch = [];
  for await (const line of rl) {
    const parsed = parseLine(line);
    batch.push(parsed);
    
    if (batch.length >= 1000) {
      await insertBatch(batch);
      batch = [];
    }
  }
  
  if (batch.length > 0) {
    await insertBatch(batch);
  }
}

결과

  • 메모리 사용량: 10GB → 200MB 이하로 감소
  • 처리 시간: 메모리 부족으로 실패하던 작업이 약 15분 내 완료
  • 배치 사이즈 조정으로 DB INSERT 성능도 개선

스트림은 Node.js의 핵심 강점이지만 실제로 활용하지 않는 경우가 많았다. 이번 계기로 대용량 파일 처리 시 스트림을 기본 선택지로 고려하게 되었다.