Node.js Express에서 파일 업로드 시 메모리 초과 문제 해결

문제 상황

3월 말부터 재택근무가 시작되면서 서비스 사용자가 2배 이상 증가했다. 특히 문서 공유 기능 사용량이 급증하면서 파일 업로드 API에서 간헐적으로 JavaScript heap out of memory 에러가 발생하기 시작했다.

기존에는 multer의 memoryStorage를 사용해 파일을 메모리에 올린 후 S3에 업로드하는 방식이었다.

const upload = multer({ 
  storage: multer.memoryStorage(),
  limits: { fileSize: 50 * 1024 * 1024 } // 50MB
});

app.post('/upload', upload.single('file'), async (req, res) => {
  const result = await s3.upload({
    Bucket: BUCKET_NAME,
    Key: req.file.originalname,
    Body: req.file.buffer
  }).promise();
});

동시에 여러 대용량 파일이 업로드되면 Node 프로세스의 메모리가 순간적으로 치솟았다.

해결 방법

스트림 방식으로 전환했다. multer로 받은 파일을 임시 디스크에 저장하고, 이를 스트림으로 읽어 S3에 직접 파이핑했다.

const upload = multer({ 
  storage: multer.diskStorage({
    destination: '/tmp/uploads',
    filename: (req, file, cb) => {
      cb(null, `${Date.now()}-${file.originalname}`);
    }
  })
});

app.post('/upload', upload.single('file'), async (req, res) => {
  const fileStream = fs.createReadStream(req.file.path);
  
  const result = await s3.upload({
    Bucket: BUCKET_NAME,
    Key: req.file.originalname,
    Body: fileStream
  }).promise();
  
  // 업로드 완료 후 임시 파일 삭제
  fs.unlinkSync(req.file.path);
  
  res.json({ url: result.Location });
});

결과

메모리 사용량이 안정화되었고, 100MB 이상의 파일도 문제없이 처리된다. PM2로 모니터링한 결과 평균 메모리 사용량이 약 40% 감소했다.

다만 임시 파일 정리를 위한 로직이 추가로 필요했고, 디스크 I/O가 발생하므로 업로드 속도는 소폭 감소했다. 하지만 안정성이 우선이라 판단해 이 방식을 유지하기로 했다.

Node.js Express에서 파일 업로드 시 메모리 초과 문제 해결