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

문제 상황

레거시 시스템에서 신규 DB로 사용자 데이터를 마이그레이션하는 작업을 맡았다. 약 5GB 크기의 CSV 파일을 읽어서 가공 후 새 스키마로 저장해야 했는데, fs.readFileSync로 전체 파일을 메모리에 올리자 Node 프로세스가 죽었다.

해결 방법

Node.js의 Stream API를 사용해서 청크 단위로 처리하도록 변경했다.

const fs = require('fs');
const readline = require('readline');
const { pipeline } = require('stream');

const processCSV = async (filePath) => {
  const fileStream = fs.createReadStream(filePath);
  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });

  let count = 0;
  const batch = [];

  for await (const line of rl) {
    const userData = parseCSVLine(line);
    batch.push(userData);

    if (batch.length >= 1000) {
      await saveBatch(batch);
      batch.length = 0;
      count += 1000;
      console.log(`Processed: ${count} rows`);
    }
  }

  if (batch.length > 0) {
    await saveBatch(batch);
  }
};

성능 개선

  • 메모리 사용량: ~2GB → ~200MB
  • 처리 시간: 배치 처리로 DB 커넥션 효율 증가
  • 안정성: OOM 에러 없이 완료

배치 크기를 1000개로 설정했는데, DB 쓰기 성능과 메모리 사용량의 균형점이었다. readline 모듈이 내부적으로 버퍼링을 처리해줘서 편했다.

교훈

대용량 파일 처리 시 Stream을 기본으로 고려해야 한다. 특히 파일 크기를 예측할 수 없는 프로덕션 환경에서는 필수다. 초기 개발 시점에 작은 샘플 데이터로만 테스트하면 이런 문제를 놓치기 쉽다.

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