HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
♥️
2기 최종 프로젝트 팀별 공간
/
팀 02 : 머쓱한녀석들
팀 02 : 머쓱한녀석들
/
🎏
BackEnd
/
🛕
이미지
🛕

이미지

S3 폴더 및 파일명동작 방식구현 코드타임아웃 설정 이유비동기 처리 방식 선택 이유응답 시간 비교

S3 폴더 및 파일명

  • S3 폴더 구조는 {개발환경}/{멤버ID}/{yyyy-MM-dd} 형태로 가져감
  • S3에 업로드 되는 이미지의 파일명은 UUID를 사용
    • ex) e2eb545f-6bc7-4219-bb52-f72e951c5ae8.jpg
    •  

동작 방식

  • S3에 이미지를 업로드해 url을 내려주는 방식 사용
  • 다중 이미지 업로드 시 비동기 처리 구현
    • CompleatbleFuture 사용
    • 비동기 처리 시 디폴드 스레드 풀 사이즈는 5로 구성
      • 디폴트 스레드 풀 사이즈 2로 구성해서 테스트해보기도 했지만 응답 시간이 둘다 비슷함
    • 한번에 최대 5개의 이미지 업로드 요청이 올 수 있으므로 5개의 스레드를 미리 만들어놓는 방식 사용
  • 단일 이미지 업로드는 동기 처리
 

구현 코드

@Override public List<String> uploadImages(Long id, List<MultipartFile> files) { List<CompletableFuture<String>> imageUrlFutures = files.stream() .map(file -> CompletableFuture.supplyAsync(() -> uploadImage(id, file), executor) .orTimeout(3, TimeUnit.SECONDS)) .collect(Collectors.toList()); return imageUrlFutures.stream( .map(CompletableFuture::join) .collect(Collectors.toList()); }
 
  1. 비동기로 S3에 이미지 업로드 요청을 전부 보낸 뒤 (supplyAsync) - 비동기 처리
  1. CompletableFuture.join()을 통해 업로드 요청이 전부 끝나길 기다린다 - 블로킹
 

타임아웃 설정 이유

  • 비동기로 동작하던 와중에 에러 발생하게 된다면 해당 스레드에서 리턴값을 주지못하므로 join() 메소드에서 영원히 기다릴 수 있음 (블록 문제)
  • 이를 방지하고자 비동기 요청에 대한 timeout 3초를 걸어서 해당 시간내에 요청을 처리하지 못하면 TimeoutException을 던짐
 

비동기 처리 방식 선택 이유

  • 자바에서 비동기 처리를 구현하는 방법으로는 여러 방법이 있음 그 중 2가지의 방식 중 고민
    • parallelStream (병렬 스트림)
      • I/O가 포함되지 않은 계산 중심의 동작을 실행할 때는 스트림 인터페이스가 구현하기 간단하며 효율적
    • CompletableFuture
      • I/O를 기다리는 작업을 병렬로 실행할때 많은 유연성을 제공. 대기/계산(W/C)의 비율에 적합한 스레드 수를 설정할 수 있음
 
이미지 업로드는 I/O 작업이므로 CompletableFuture를 선택
 

응답 시간 비교

동기, 비동기로 5개의 이미지 요청을 보냈을때의 시간을 비교합니다.
 
동기 처리 시
  • 코드
@Override public List<String> uploadImages(Long id, List<MultipartFile> files) { return files.stream() .map(file -> uploadImage(id, file)) .collect(Collectors.toList()); }
  • 응답 시간: 568 ms
notion image
notion image
 
비동기 처리 시
  • 코드
@Override public List<String> uploadImages(Long id, List<MultipartFile> files) { List<CompletableFuture<String>> imageUrlFutures = files.stream() .map(file -> CompletableFuture.supplyAsync(() -> uploadImage(id, file), executor) .orTimeout(3, TimeUnit.SECONDS)) .collect(Collectors.toList()); return imageUrlFutures.stream( .map(CompletableFuture::join) .collect(Collectors.toList()); }
  • 응답 시간: 201 ms
 
notion image
notion image