Node.js 스트림으로 대용량 CSV 파싱 메모리 문제 해결
문제 상황
고객사에서 200MB 크기의 CSV 파일을 업로드하면 서버가 OOM으로 종료되는 이슈가 발생했다. 기존 코드는 파일 전체를 메모리에 로드한 후 파싱하는 방식이었다.
const csv = require('csv-parser');
const fs = require('fs');
// 기존 방식 - 전체 로드
const data = fs.readFileSync('large.csv', 'utf-8');
const parsed = csvParser(data); // 메모리 폭발
해결 방법
Node.js 스트림을 활용해 청크 단위로 처리하도록 변경했다.
const results = [];
let processedCount = 0;
fs.createReadStream('large.csv')
.pipe(csv())
.on('data', async (row) => {
// DB에 배치 insert
results.push(row);
if (results.length >= 1000) {
await batchInsert(results);
results.length = 0;
processedCount += 1000;
console.log(`Processed: ${processedCount}`);
}
})
.on('end', async () => {
if (results.length > 0) {
await batchInsert(results);
}
});
결과
- 메모리 사용량: 800MB → 80MB
- 처리 시간: 45초 → 52초 (약간 증가)
- 안정성: 서버 크래시 없이 안정적으로 처리
1000개씩 배치로 끊어서 DB에 insert하는 방식으로 변경하니 메모리 사용량이 크게 줄었다. 처리 시간은 소폭 증가했지만 안정성이 훨씬 중요했다.
교훈
대용량 파일 처리 시 스트림을 기본으로 고려해야 한다. 특히 사용자 업로드 파일은 크기를 예측할 수 없으므로 스트림 기반 처리가 필수다.