이 글은 「PDF 기반 publication 업로드 구조 개선기」 시리즈의 3편입니다.
(1편) 기존 구조의 문제점 → (2편) S3 presigned URL 설계 → (3편) Spring Boot 구현
앞선 글에서
- 왜 기존 텍스트 기반 구조가 한계였는지
- 왜 서버가 파일을 직접 받지 않는 구조를 선택했는지
를 정리했다.
이번 글에서는 그 설계를 바탕으로
Spring Boot에서 실제로 어떤 API를 만들었는지,
그리고 구현하면서 중요했던 포인트들을 정리해보려고 한다.
구현 목표 다시 정리
이번 구현에서의 목표는 명확했다.
- 서버는 파일 데이터를 직접 받지 않는다
- 프론트가 S3로 직접 업로드한다
- 서버는 업로드를 허용하고, 파일 상태를 확정하는 역할만 한다
그래서 서버에 필요한 API는 딱 두 개였다.
- 임시 업로드용 presigned URL 발급 API
- 파일 확정(confirm) API
1. 임시 업로드용 API
역할
- 프론트가 PDF 파일을 업로드할 수 있도록
- presigned URL과 tempKey를 발급해준다
이 API에서 서버가 하는 일은 단순하다.
- UUID를 생성한다
- temp/{uuid}.pdf 형태의 key를 만든다
- 해당 key에 대한 presigned URL을 발급한다
이 단계에서는:
- DB 접근 ❌
- 파일 처리 ❌
- 비즈니스 로직 ❌
오직 "업로드를 허가해주는 역할”만 한다.
2. 프론트에서의 업로드
프론트는:
- 업로드 버튼 클릭
- 서버에서 받은 presigned URL로
- PDF 파일을 S3에 직접 업로드
이때 파일은:
temp/{uuid}.pdf
경로에 저장되고, 아직 어떤 publication과도 연결되지 않은 임시 상태다.
3. 파일 확정(confirm) API
publication 생성이 끝난 뒤, 파일을 확정하기 위해 호출되는 API다.
이 API의 책임
- 임시 파일을 확정 경로로 이동
- DB에는 관여하지 않음
입력값은 단순하다.
- tempKey
- publicationId
서버는 이 요청을 받으면:
- tempKey가 유효한지 확인하고
- paper/{publicationId}.pdf 경로를 생성한 뒤
- S3에서 copy → delete로 파일을 이동한다
- 최종 key(finalKey)를 반환한다
이렇게 하면:
- DB에는 항상 확정된 파일 경로만 저장된다
- temp 파일이 그대로 남는 상황을 피할 수 있다
IAM Role 기반 인증
S3 접근을 위해
access key를 코드에 넣지는 않았다.
EC2에 IAM Role을 연결하고,
서버에서는 AWS SDK가 해당 Role을 자동으로 사용하도록 했다.
덕분에:
- 키 노출 위험이 없고
- 환경별 설정도 단순해졌다
temp 파일 정리 전략
임시 업로드 파일은
publication으로 확정되지 않으면 결국 필요 없는 파일이다.
그래서 S3에 Lifecycle Rule을 추가했다.
- temp/ prefix
- 3일 후 자동 삭제
이 설정 하나로:
- 업로드만 하고 취소된 파일
- confirm이 호출되지 않은 파일
을 자동으로 정리할 수 있었다.
전체 흐름 다시 한 번 정리
1. 프론트 → 서버 임시 업로드 URL 요청
2. 프론트 → S3 PDF 파일 임시 업로드
3. 프론트 → publication API tempKey 전달
4.publication 처리 완료 → 파일 확정 API 호출
5. 서버 temp → paper 경로로 이동
서버는 끝까지:
- 파일 내용을 직접 다루지 않고
- 파일의 상태만 관리한다
구현하면서 느낀 점
이 구조의 가장 큰 장점은
책임이 명확해진다는 점이었다.
- 파일 업로드/이동: 파일 담당
- PDF 파싱/DB 저장: publication 담당
서로의 영역을 침범하지 않으니
기능 변경이나 확장도 훨씬 편해졌다.
마무리
이번 시리즈에서는
단순히 “S3 업로드 구현”이 아니라,
- 왜 기존 구조가 문제였는지
- 어떤 기준으로 설계를 바꿨는지
- 그 설계를 어떻게 최소한으로 구현했는지
를 정리해보려고 했다.
비슷한 상황에서
파일 업로드 구조를 고민하고 있다면,
이 방식이 하나의 선택지가 되었으면 한다.
'💻 프로젝트 > 트러블 슈팅' 카테고리의 다른 글
| [QRoad]운영 전 API 부하 테스트를 진행하며 403을 마주하다 (문제 추적 과정 정리) (0) | 2026.03.02 |
|---|---|
| 나는 -i 옵션을 줬는데, 왜 다른 SSH 키가 사용됐을까? (0) | 2026.02.26 |
| [QRoad]PDF 기반 publication 업로드 구조 개선기 – 2편: S3 presigned URL 설계 (0) | 2026.02.11 |
| [QRoad]PDF 기반 publication 업로드 구조 개선기 - 1편 구조의 문제 (0) | 2026.02.11 |
| [QRoad]GitHub Actions로 EC2 CI/CD 구축하며 겪은 문제들과 해결 과정 (0) | 2026.01.26 |