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