포폴/일정관리앱
AWS S3 Presigned URL을 이용한 업/다운로드 구현
slown
2025. 4. 4. 01:55
목차
1.PresingedUrl구조 및 흐름도
2. 코드 적용
3.회고
1.PresingedUrl구조 및 흐름도
우선은 지난 글에서 기존의 첨부파일의 로직의 단점을 설명했고 이번에는 S3를 활용한 PreSignedUrl을 구현해보겠습니다. 아래의 도식은 제가 작성한 업/다운로드의 흐름도 입니다.
위의 사진을 토대로 설명을 드리겠습니다.
우선 업로드의 흐름은 다음과 같습니다.
- 클라이언트가 첨부파일을 업로드를 합니다.
- 그 후 서버에서는 S3를 거쳐서 업로드를 할 수 있는 URL을 프론트에게 응답을 해줍니다.
- 클라이언트에서 서버가 보내준 해당 URL을 기준(제한 기간 있음)으로 첨부파일을 S3에 업로드를 합니다.
- 그 다음 서버에서 첨부파일을 디비에 저장을 하고 섬네일을 생성합니다.
다음은 다운로드의 흐름입니다.
- 프론트에서 다운로드에 관한 요청을 보냅니다.
- 서버에서는 S3를 거쳐서 다운로드를 할 수 있는 URL을 보냅니다.
- 클라이언트는 제한된 기간동안 다운로드를 할 수 있습니다.
2. 코드 적용
우선은 변경된 PreSingedUrl을 사용한 로직입니다.
// S3에 업로드할 Presigned URL을 생성 (PUT 방식, 유효시간 20분)
public List<String> generateUploadUrls(List<String> fileNames);
// S3에서 다운로드할 Presigned URL을 생성 (GET 방식, 유효시간 30분)
public String generateDownloadUrl(String fileName);
// 업로드 완료된 파일명 목록을 기반으로 AttachModel 생성 및 DB 저장
public List<AttachModel> createAttach(List<String> uploadedFileNames);
// 각 첨부파일에 대해 썸네일 비동기 생성 (이미지 파일만 처리)
@Async
public CompletableFuture<Void> createAndUploadThumbnail(AttachModel attachModel);
// S3에서 원본 파일 및 썸네일 삭제
public void deleteFileFromS3(String storedFileName);
// DB에서 Attach 삭제 + S3 파일 삭제
public void deleteAttachAndFile(Long attachId);
위의 메서드를 설명하면 다음과 같습니다.
2-1. Presigned URL 생성
public List<String> generatePreSignedUrls(List<String> fileNames)
- 업로드용 PUT Presigned URL 생성 (20분 유효)
- 프론트에서 바로 S3로 파일 PUT 요청함
2-2. 업로드 완료 후 처리
public List<AttachModel> createAttach(List<String> uploadedFileNames)
- 업로드된 파일의 메타데이터 (URL, 파일 크기 등)로 AttachModel 생성 및 DB 저장
- 이후 @Async로 썸네일 생성 (이미지 파일만 대상)
2-3. 썸네일 생성 (비동기)
@Async
public CompletableFuture<Void> createAndUploadThumbnail(AttachModel attachModel)
- S3에서 원본 이미지를 읽고 썸네일 생성
- thumb_ prefix 붙여서 S3에 업로드
- URL을 AttachModel에 저장 (DB 업데이트)
여기에서 CompletableFuture를 사용한 이유는 다음과 같습니다.
첨부파일 업로드 후 썸네일을 생성하는 과정은 이미지 다운로드, 리사이징, 업로드 등 시간이 소요되는 작업들로 구성되어 있습니다. 이러한 작업들을 동기적으로 처리하면 서버의 응답 시간이 길어지고, 리소스 사용률이 높아질 수 있습니다.
이를 개선하기 위해 Spring의 @Async 어노테이션을 활용하여 썸네일 생성을 비동기적으로 처리하였습니다. 이때 반환 타입으로 CompletableFuture<Void>를 사용한 이유는 다음과 같습니다:
- 작업 완료 추적 및 후속 처리 용이성: CompletableFuture를 사용하면 작업의 완료 여부를 추적할 수 있으며, 필요에 따라 후속 작업을 체이닝할 수 있습니다.
- 예외 처리의 유연성: CompletableFuture는 .exceptionally, .handle 등의 메서드를 통해 예외 발생 시 유연하게 대응할 수 있는 구조를 제공합니다.
- 비동기 작업의 유연한 제어: 필요에 따라 .get()이나 .join()을 사용하여 비동기 작업의 결과를 동기적으로 처리할 수 있어, 상황에 맞는 유연한 제어가 가능합니다.
이러한 이유로 CompletableFuture<Void>를 사용하여 썸네일 생성 작업을 비동기적으로 처리함으로써, 서버의 응답 속도를 개선하고 리소스 사용을 최적화할 수 있었습니다.
2-4. 다운로드용 Presigned URL 발급
@Transactional(readOnly = true)
public String generateDownloadPreSignedUrl(String fileName)
- Content-Disposition 헤더 포함하여 다운로드 링크 생성 (30분 유효)
2-5. 삭제 처리
@Transactional
public void deleteAttachAndFile(Long attachId)
- S3에서 원본 + 썸네일 삭제
- DB에서 AttachModel 삭제
3.회고
3-1. 시행착오
- 처음에는 Presigned URL 발급과 실제 업로드/다운로드의 동작 순서를 제대로 이해하지 못해 작업을 하는데 있어서 시간이 조금은 걸렸습니다.
- 썸네일 처리 시에는 이미지 유효성 검증을 하지 않아 오류가 발생했었고, 이후 확장자 기반 필터링을 적용해 해결했습니다.
- 파일이 비동기로 처리되다 보니 테스트에서 race condition 문제를 고려해야 했음.
3-2. 성장한 부분
- S3 Presigned URL의 사용 목적과 장단점을 명확히 이해하게 됨.
- 서버의 역할을 단순화하고, 파일 처리 로직을 외부(S3)로 위임함으로써 구조를 깔끔하게 만들 수 있었음.
- @Async를 활용한 비동기 썸네일 처리와 예외 발생 시 로깅 전략을 수립함.
3-3. 앞으로 개선할 점
- 파일 확장자 필터링을 MIME 타입 기반으로 더 견고하게 만들 예정.
- 테스트 코드가 부족했음. 특히 비동기 처리와 S3 Mocking 테스트에 대한 보완이 필요.
- 프론트엔드와의 연결성 및 Presigned URL 만료 처리에 대한 UX 개선도 고려할 것.