Node.js 스트림으로 대용량 CSV 파일 처리 최적화
문제 상황
고객사에서 제공한 15GB 크기의 주문 데이터 CSV를 DB로 마이그레이션하는 작업을 맡았다. 처음엔 fs.readFile로 전체 파일을 읽어 처리했는데, 서버 메모리가 부족해 프로세스가 죽는 문제가 발생했다.
해결: Stream API 활용
Node.js의 스트림을 사용해 파일을 청크 단위로 읽고 처리하도록 변경했다.
const fs = require('fs');
const readline = require('readline');
const { pool } = require('./db');
async function processLargeCSV(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let batch = [];
const BATCH_SIZE = 1000;
for await (const line of rl) {
const row = line.split(',');
batch.push(row);
if (batch.length >= BATCH_SIZE) {
await insertBatch(batch);
batch = [];
}
}
if (batch.length > 0) {
await insertBatch(batch);
}
}
async function insertBatch(rows) {
const values = rows.map(r => `('${r[0]}', '${r[1]}', ${r[2]})`).join(',');
await pool.query(`INSERT INTO orders (id, name, amount) VALUES ${values}`);
}
결과
- 메모리 사용량: 8GB → 400MB
- 처리 시간: OOM 발생 → 23분 소요
- 배치 처리로 DB 연결 오버헤드도 감소
추가 고려사항
실제 프로덕션에선 에러 핸들링과 재시도 로직을 추가했다. 특히 BATCH_SIZE는 DB 성능과 메모리를 고려해 조정이 필요하다. PostgreSQL의 경우 1000~5000 정도가 적당했다.
스트림은 대용량 파일 처리뿐 아니라 HTTP 응답, 파일 압축 등 다양한 상황에서 유용하다. Node.js의 핵심 개념이니 확실히 익혀두는 게 좋다.