Spring Boot에서 Multipart 파일 처리 안정화 방법
Spring Boot 환경에서 멀티파트 파일 업로드의 설정, 예외 대응, 저장 전략을 한데 정리한 기술 설명서
목차
개요
웹 애플리케이션에서 파일 업로드는 흔한 요구사항이다. 다만 설정이 부적절하거나 예외 처리가 빠져 있으면 서비스 안정성에 치명적일 수 있다. 이 글은 Spring Boot 기반에서 multipart 설정부터 예외 처리, 저장 전략까지 실무에서 유용한 요소를 중심으로 정리한 기술 설명서이다.
기본 설정 이해
application.properties 또는 application.yml 설정
Spring Boot는 기본으로 multipart 지원을 제공한다. 다만 업로드 가능한 파일 크기와 요청 크기 제한을 명확히 지정하는 것이 중요하다. 아래는 기본적인 프로퍼티 예시이다.
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.file-size-threshold=1MB
spring.servlet.multipart.location=/tmp/uploads
max-file-size와 max-request-size는 파일 하나의 최대 크기와 전체 요청의 최대 크기이다. file-size-threshold는 메모리와 임시 파일 저장 기준으로 사용된다. production에서는 location을 외부 디스크나 전용 스토리지로 지정하는 편이 안전하다.
컨트롤러 구현
단일 파일과 다중 파일 처리
컨트롤러는 MultipartFile을 활용해 파일을 받는다. 기본 검증을 추가하고, 파일명을 무작위화하거나 디렉토리 구조를 만들어 충돌을 피하는 것이 좋다.
package com.example.upload;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class UploadController {
@PostMapping(/upload)
public ResponseEntity<String> upload(@RequestPart("file") MultipartFile file) throws Exception {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body(File is empty);
}
// 파일 확장자, 크기 추가 검증 및 저장 로직 호출
String saved = fileStorageService.store(file);
return ResponseEntity.ok(saved);
}
}
위 예시는 핵심 로직만 담았다. 파일명 검사, 확장자 화이트리스트, 콘텐츠 타입 검증을 함께 적용하면 보안성을 높일 수 있다.
저장 전략
로컬 파일 시스템 vs 외부 스토리지
개발 초반에는 로컬에 저장하는 것이 빠르다. 그러나 운영 환경에서는 분산 환경과 백업, 성능을 고려해 외부 객체 스토리지(S3, MinIO 등)를 권장한다. 로컬 저장 시에는 다음을 고려한다.
- 디렉토리 구조: 업로드 수가 많을 경우 해시 기반 분산 디렉토리 사용
- 파일명 정책: 원본 노출 방지를 위해 UUID 등으로 저장
- 권한 및 소유자: 웹 프로세스의 파일 접근 권한 제한
예외 처리 및 복구
MultipartException 대응
파일 크기 초과나 전송 중 중단 등은 MultipartException 계열 예외로 처리된다. 전역 예외 핸들러를 두어 사용자 친화적인 응답과 로깅을 수행하는 것이 바람직하다.
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.http.ResponseEntity;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<String> handleSizeException(MaxUploadSizeExceededException ex) {
return ResponseEntity.status(413).body(File size exceeds limit);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneric(Exception ex) {
// 상세 로그 기록
return ResponseEntity.status(500).body(Internal server error);
}
}
상태 코드 413(Payload Too Large)을 반환하면 클라이언트에서도 크기 초과를 인지하기 쉽다. 또한 로그에 원인과 요청 메타데이터를 남겨 문제 재현에 도움을 준다.
성능과 보안 고려사항
스트리밍 처리
큰 파일은 메모리에 모두 올리면 OOM 위험이 있다. 가능하면 스트리밍 방식으로 처리하거나 file-size-threshold를 낮춰 임시 파일로 전환하도록 설정한다. 대용량 업로드는 비동기 처리 또는 별도의 처리 큐로 분리하면 응답 지연을 줄일 수 있다.
바이러스 검사 및 콘텐츠 검증
업로드 파일은 신뢰할 수 없다. 확장자만 믿지 말고, 파일 헤더 및 MIME 타입을 확인한다. 필요 시 바이러스 검사 엔진 연동을 고려한다.
테스트와 모니터링
테스트 케이스 구성
- 정상 파일 업로드(다양한 크기와 타입)
- 크기 초과 요청
- 잘못된 바운더리 또는 손상된 업로드
- 동시 업로드에 따른 동시성 테스트
통합 테스트에서는 실제 multipart 요청을 생성해 컨트롤러와 저장 로직이 기대대로 동작하는지 검증한다.
모니터링 지표
- 업로드 요청 수와 실패율
- 평균 업로드 처리 시간
- 디스크 사용량 및 임시 파일 개수
운영 팁
운영 시에는 프로퍼티로 크기 제한을 관리하고, 롤링 로그와 스토리지 모니터링을 설정해 임시 파일이 누적되지 않도록 한다. 또한 보안 패치와 라이브러리 버전을 주기적으로 점검하면 취약점 노출을 줄일 수 있다.
맺음말
Spring Boot에서 multipart 처리는 단순해 보이지만, 설정과 예외 처리, 저장 전략을 놓치면 안정성 문제가 발생한다. 적절한 프로퍼티 설정, 예외 핸들링, 스트리밍과 외부 스토리지 연동을 통해 안정적인 파일 업로드 기능을 설계할 수 있다.