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

문제 상황

레거시 시스템에서 신규 DB로 사용자 데이터를 이관하는 작업을 맡았다. 약 50GB 크기의 CSV 파일을 읽어서 변환 후 MongoDB에 삽입해야 했는데, 처음 fs.readFileSync로 시도했다가 힙 메모리 에러가 발생했다.

해결 방법

Node.js의 스트림을 활용해 청크 단위로 처리하는 방식으로 변경했다.

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

const transformStream = new Transform({
  objectMode: true,
  transform(chunk, encoding, callback) {
    // CSV 행 파싱 및 변환
    const row = chunk.toString().split(',');
    const user = {
      email: row[0],
      name: row[1],
      createdAt: new Date(row[2])
    };
    this.push(user);
    callback();
  }
});

const rl = readline.createInterface({
  input: fs.createReadStream('users.csv'),
  crlfDelay: Infinity
});

let batch = [];
rl.on('line', async (line) => {
  batch.push(line);
  
  if (batch.length >= 1000) {
    rl.pause();
    await insertBatch(batch);
    batch = [];
    rl.resume();
  }
});

배치 단위로 끊어서 DB에 삽입하는 방식으로 구현했다. 1000개씩 모아서 bulkWrite를 사용하니 성능도 개선됐다.

결과

  • 메모리 사용량: 8GB → 300MB로 감소
  • 처리 시간: 약 2시간 소요
  • 안정적으로 완료

스트림은 대용량 데이터 처리에서 필수적이다. backpressure 처리만 잘 해주면 메모리 걱정 없이 안정적으로 동작한다.