Node.js 스트림으로 대용량 CSV 파싱 메모리 이슈 해결
문제 상황
고객사 주문 데이터를 CSV로 받아 DB에 적재하는 배치 작업을 구현했다. 초기에는 fs.readFileSync로 파일을 전부 읽어 처리했는데, 150만 건 규모의 파일에서 메모리 부족으로 프로세스가 죽는 현상이 발생했다.
// 기존 방식 - 메모리 3GB 이상 사용
const data = fs.readFileSync('orders.csv', 'utf-8');
const rows = data.split('\n');
for (const row of rows) {
await processRow(row);
}
스트림 기반 처리로 변경
Node.js의 stream 모듈과 csv-parser 라이브러리를 사용해 청크 단위로 읽도록 수정했다.
const fs = require('fs');
const csv = require('csv-parser');
const { pipeline } = require('stream/promises');
await pipeline(
fs.createReadStream('orders.csv'),
csv(),
async function* (source) {
for await (const row of source) {
await processRow(row);
yield row;
}
}
);
배치 처리로 성능 개선
한 건씩 DB INSERT하면 느려서 100건 단위로 배치 처리를 추가했다.
let batch = [];
const BATCH_SIZE = 100;
await pipeline(
fs.createReadStream('orders.csv'),
csv(),
async function* (source) {
for await (const row of source) {
batch.push(row);
if (batch.length >= BATCH_SIZE) {
await db.insertMany(batch);
batch = [];
}
yield row;
}
if (batch.length > 0) {
await db.insertMany(batch);
}
}
);
결과
- 메모리 사용량: 3GB → 300MB
- 처리 시간: 45분 → 12분
- 150만 건 데이터 안정적 처리 가능
스트림은 대용량 파일을 다룰 때 필수다. backpressure 처리도 자동으로 되어 안정성이 높다.