[kt cloud TECH UP] 서버 부하 없는 대용량 미디어 처리: S3 Presigned URL과 상태 관리
2026. 2. 21.본 포스팅은 계속해서 진화하는 kt cloud TECH UP 실무 통합 프로젝트 마라톤 티켓팅 플랫폼 구축기의 이어지는 기록입니다.)
1. Multipart/form-data의 덫: 백엔드 서버가 죽어간다
성공적으로 티켓팅 시스템을 안착시킨 뒤, 우리는 마라톤 러너들이 자신의 [달리기 완주 기록 사진]이나 [프로필 이미지]를 업로드하는 커뮤니티/마이페이지 기능을 개발하고 있었습니다.
초기에는 가장 고전적인 방식인 Multipart/form-data를 사용했습니다.
클라이언트 브라우저가 사진을 백엔드(Spring)로 보내면, 백엔드가 그 바이트 스트림을 메모리에 올린 뒤 다시 AWS S3 버킷으로 전송하는 방식이었습니다.
하지만 이 방식은 티켓팅이라는 고부하 텍스트 트랜잭션과 완벽한 상극이었습니다. 서버가 10MB짜리 사진 1,000장을 동시에 중계하게 되면 막대한 네트워크 대역폭(I/O)과 메모리가 낭비되며, 정작 가장 중요한 티켓팅 결제 API들이 파일 업로드 스레드들에 막혀 응답 지연을 일으키는 심각한 병목 현상이 발생했습니다.
2. 병목의 우회: S3 Presigned URL의 도입
진짜 비즈니스 로직(티켓팅 등) 처리를 위해, 백엔드 서버에서 "파일 입출력(I/O) 중계"라는 무거운 책임을 완전히 덜어내야 했습니다. 우리는 S3 Presigned URL(미리 서명된 URL) 아키텍처를 도입했습니다.
변경된 프로세스
- 클라이언트: 백엔드 서버에 "저 이미지 하나 업로드 할게요" 라고 가벼운 JSON 요청을 보냅니다.
- 백엔드: AWS SDK를 통해 S3에 "이 특정 경로(Key)로 5분 동안 누군가 파일을 업로드할 수 있는 임시 권한 티켓(Presigned URL)을 발급해 줘" 라고 요청하고, 발급받은 URL을 클라이언트에게 문자열만 달랑 반환합니다.
- 클라이언트: 백엔드를 전혀 거치지 않고, 브라우저에서 직접 S3의 해당 URL로 무거운 이미지 바이너리 파일을
PUT요청으로 쏴버립니다.
결과는 극적이었습니다. 백엔드 서버는 무거운 이미지를 단 1바이트도 만지지 않고 그저 짧은 문자열(URL)만 발급해 줌으로써 I/O 병목에서 완전히 해방되었습니다.
3. 새로운 숙제 찾기: 클라우드 생태계 쓰레기(고아 객체) 문제
성능 문제는 완벽히 잡았지만, 이 우아한 아키텍처에는 치명적인 부작용이 있었습니다.
클라이언트는 마음이 갈대와 같습니다. 백엔드에서 Presigned URL을 10개나 발급받아 놓고 (S3에 올릴 준비를 다 해놓고) 정작 사용자가 "취소" 버튼을 누르거나 창을 닫아버릴 수 있습니다. 반대로, 브라우저가 S3에 이미지를 성공적으로 올려놓고 정작 백엔드 서버에 "업로드 완료했어요. 이 글을 저장해 주세요"라는 필수 API 호출을 실패하면 어떻게 될까요?
S3 버킷 안에는 "아무 게시글에도 속하지 않는 좀비 이미지 파일(고아 객체, Orphaned Object)"들이 통제 불가능하게 쌓이게 되고, 이는 곧 불필요한 스토리지 과금(비용) 폭탄으로 돌아옵니다.
4. 미디어 테이블(Media State Management) 설계
우리는 파일과 생명주기를 완벽하게 통제하기 위해 RDBMS(DB)에 media라는 상태 관리 테이블을 별도로 편입시켰습니다.
- 백엔드가 Presigned URL을 발급해 줄 때, DB 미디어 테이블에 해당 파일의 메타데이터를 저장하고 상태를
PENDING(임시)으로 기록합니다. - 클라이언트가 S3에 무사히 업로드하고 글쓰기 저장을 요청할 때, 해당 미디어의 상태를
CONFIRMED(확정)으로 변경합니다. - 정리(Cleanup) 스케줄러: 백엔드 서버에 Spring Scheduled Batch를 등록하여 매일 자정, 생성된 지 24시간이 지났음에도 여전히
PENDING상태인 좀비 미디어 레코드들을 색출합니다. 그리고 AWS SDK를 통해 실제 S3에서 이 좀비 파일들을 일괄 삭제(DeleteObjects)한 뒤, DB 레코드도 폐기합니다.
이러한 [상태 관리 + 비동기 가비지 컬렉터] 구조를 통해, 시스템은 무거운 파일 업로드 트래픽을 완벽하게 외부 망(S3)으로 떠넘기면서도, 스토리지 공간이 쓰레기 파일로 가득 차는 리스크까지 말끔하게 통제할 수 있게 되었습니다.
프로젝트 트러블슈팅의 연속. 다음 편에서는 팀원 간 커뮤니케이션 빙하 시대를 녹여준 Exception과 ErrorCode 의 전역 리팩토링 스토리를 다루겠습니다.