[영상후기] [10분 테코톡] 백호, 망쵸의 Spring Boot와 AWS를 이용한 이미지 업로드 및 최적화

박철현·2024년 12월 12일
0

영상후기

목록 보기
157/160

어떻게 S3 이미지 업로드 할까?

  • Aws S3 방식 이용
    • MultipartFile : Spring MVC 제공 인터페이스 활용 방식
    • Stream : Server를 byPass 하는 형태로 바로 S3에 업로드
    • Pre-Signed URL : client가 직접 S3에 업로드 하는 방식

일반적인 MultipartFile 방식 이용

  • 메모리나 임시 디스크에 저장
    • 업로드 완료된 이후 제거
    • 파일 사이즈 제한하는 설정 하여 무분별한 파일이 업로드 되는것을 방지
    • 파일이 서버를 거쳐가서 큰 부하를 주는 단점
  • 단점
    • 서버의 디스크 용량에 따라 실패할 수 잇다.
    • 서버에 부하를 주는 방식

Stream 방식

  • client가 Server에 Stream 방식으로 전달
  • Server에서 S3에 Stream 방식으로 바로 전달
@PostMapping("/stream")
public String uploadFile (HttpServletRequest request) {
	try (InputStream inputStrem = request.getInputStream()) {
      String fileName = request.getHeader("file-name");
      long contentLength = request.getContentLengthLong();
      
      s3UploadService.uploadFileToS3(fileName, inputStream, contentLength);
      return "업로드 완료"
    } catch (IOException e) {
    	return "업로드 실패 : " + e.getMessaget();
     }
}

Stream 방식과 MultiPartFile 업로드 방식 비교

구분CPU 사용률가비지컬렉션
MultipartFile50%0.8 ops/s
Stream10%0.06 ops/s
  • Stream 방식이 서버에 부하를 주지 않는 방식임을 확인

Stream의 장점

  • 서버의 부하가 적다.
  • 서버의 디스크 용량과 무관하다.

10MB 100개 업로드 테스트

  • Stream 방식은 100% 다 업로드 성공을 하지 못했다.
    • 원인 : Connection Pool Timeout Exception 발생
      • Stream 방식의 Connection 타임아웃
구분잠실캠퍼스선릉캠퍼스우리집
MultipartFile100100100
Stream5010049

원인 : Server에서 S3로 맺을 수 있는 Connection 최대 수가 50으로 제한

  • 50개는 커넥션을 맺을 수 있지만 나머지 50개는 대기하게 됨
  • 잠실의 경우 인터넷 속도가 좀 느림
    • 업로드 속도 : 27Mbps
    • EC2에서의 업로드 속도 : 185Mbps -> 27Mbps 로 단축되는 효과를 얻음..
    • 50개가 빠르게 처리되지 못해서 대기중이던 요청 50개는 대기하다가 타임아웃
  • 선릉의 경우 650 Mbps의 속도로 굉장히 빠름
    • 650 -> 185Mbps 로 먼저 50개 처리
    • 대기중이던 요청도 커넥션을 맺어서 처리할 수 있음

반면 MultipartFile 방식

  • 어떻게 100개를 안정적으로 하는가?
  • client의 속도가 느리더라도 MultipartFile로 변경하는 과정에서 임시 디스크에 저장
    • S3에 업로드 속도는 EC2의 속도 그대로 (Client의 업로드 속도가 느려도 영향을 받지 않음)
    • Stream 방식은 cilent의 업로드 속도와 무관하게 좀 더 안정적으로 업로드 할 수 있다는 장점이 있음

Stream 방식의 단점

  • 클라이언트의 업로드 속도에 따라 S3 업로드에 실패한다.
  • 파일을 업로드하는 긴 시간동안 서버의 커낵션을 소모한다.

Pre-Signed URL 방식

  • Client가 S3에 바로 파일을 업로드 하는 방식
    • Server가 검증을 해주는 역할을 해줌

동작 방식

  • Client가 Server에 start 요청
  • Server가 S3에 Upload Id 요청
  • S3에서 Upload Id Server로 응답
  • Server에서 S3에 Pre-Signed URL 요청
  • S3에서 Server로 Pre-Signed URL 응답
  • Server에서 Client로 Upload Id, Pre-Signed URL 응답
  • Client는 S3에 파일을 업로드 할 수 있는 권한을 가짐
    • 파일을 분할해서 업로드 하는 방식
    • Client는 파일을 분할해서 S3에Pre-Signed URL로 업로드
    • S3에서 각각 파일들의 Etag 들을 Client로 응답
    • Client에서Etag 모두 응답 받음
  • Client는 S3에 complete 요청
  • Server는 S3에 요청해서 S3는 파일을 하나로 합침
    • 파일을 합쳐 업로드 되는 형식

구현

  • 서버는 API 2개를 지원해야 함
@PostMapping("/pre-signed/start")
public ResponseEntity<AwsMultipartStartResponse> start(
	@RequestParam String fileName,
    @RequestParam int partCount
) {
	String uniqueFileName = UUID.randomUUID() + fileName;
    String uploadId = s3UploadService.createMultipartUpload(uniqueFileName);
    List<URL> urls = s3UploadService.generatePresignedUrls(uniqueFileName, uploadId, partCount);
    
    return ResponseEntity.ok(new AwsMultipartStartResponse(uploadId, urls, uniqueFileName));
}

@PostMapping("/pre-signed/complete")
public String complete(
	@RequestParam String fileName,
    @RequestParam String uploadId,
    @requestBody List<String> etags
) {
	List<CompletedPart> completedParts = s3UploadService.getCompletedParts(eTags);
    s3UploadService.completeMultipartUpload(fileName, uploadId, completedParts);
    
    return "업로드 완료";
}
  • 파일 자체가 서버를 거치지 않는 방식
    • start, end API 구현 필요
    • 서버에 부하를 주지 않는 방식

1GB 5개 업로드 비교

구분시간(m)
MultipartFile21
Stream21
Pre-Signed URL7.5
  • Pre-Singed URL 방식이 파일을 쪼개서 보낼 수 있어 훨씬 빠르게 보낼 수 있음

주의할 점

  • CORS 설정
    • Client가 S3에 이미지 바로 업로드
    • S3에서 Client가 요청을 받을 수 있도록 CORS 설정
  • Pre-Signed URL 만료시간
    • 만료시간이 지나면 S3에 요청을 보낼 수 없음
    • 시간을 너무 짧게 잡으면 업로드 실패, 너무 길게 잡으면 보안상 취약
    • 적절한 시간 설정 필요
  • LifeCycle
    • 불안전한 파일 존재할 수 있음
      • 요청 4개 중 3개만 전송 성공
    • 라이프 사이클 설정하여 주기적으로 파일 제거해주는 작업 필요

Pre-Signed URL 단점

  • 다소 복잡한 Flow
  • S3 의존성

이미지 최적화와 작업 방식

현재 구현 사항 문제

  • PNG, JPEG 등 용량이 큰 이미지 포맷으로 응답
    • (해결) 저용량, 고화질 이미지 포맷으로 변경
      • JPEG -> webp : -74.91%
      • JPEG -> avif : -90.72%
    • 사람이 체감하기 어려울 정도
  • UI 요구사항보다 큰 사이즈의 원본 이미지 응답
    • UI 요구사항에 따라 필요한 이미지 사이즈는 다름
      • 배경 이미지
      • 썸네일 이미지
    • UI는 변경이 잦음
    • (해결) UI 요구사항에 맞게 리사이징
      • 768 x 1024 -> 300 x 400 => -90.51%
  • 불필요한 네트워크 비용

언제 어디에서 이미지 최적화를 진행할까?

방식 1) 이미지 업로드 및 최적화 작업 서버

  • 사용자가 별도 서버 이미지 업로드
  • 이미지 최적화 및 저장 S3
  • Cloud front 업로드
  • 사용자는 Cloud Front 접근

문제

  • pre-signed url 업로드 방식을 사용할 수 없음
  • 이미지 업로드와 최적화 작업에 대한 서버 구현
  • 장애가 발생했을 때 재시도와 롤백 로직 구현
  • 별도의 서버를 관리해야 하는 비용 발생

방식 2) 이미지 업로드되는 시점에 최적화

  • 사용자 S3 이미지 업로드 (Pre-Signed Url 방식)
  • 이벤트 Trigger되어 Lambda가 실행되어 이미지 최적화 하여 S3에 사본 저장 (최적화 완료)
  • 이후 사용자가 요청 시 CloudFront를 통해 S3 이미지를 가져온다

장점

  • AWS Lambda를 사용한 서버리스
    • 서버 관리 비용 줄임
  • 스크립트만 작성하면 되서 구현 간편

단점

  • 이미지 요청이 들어온 시점에 사본이 생성되지 않을 수 있다.
    • 404 Not Found 응답 받음
  • 모든 이미지에 대해 사본 생성
    • 만약 UI가 변경되어 새로운 사이즈의 이미지가 필요하다면?
      • 저장된 모든 이미지의 사본을 생성해야 한다.

방식 3) 이미지 요청 시점에 최적화

  • 사용자 S3 이미지 업로드
  • 사용자가 이미지 요청 -> CloudFront가 S3 원본 이미지 요청 -> S3가 원본 이미지 반환할 때 Lambda가 가로채서 이미지 최적화 및 캐싱 -> 사용자에게 응답

장점

  • 사용자의 요청에 정상 응답
  • 사본 이미지를 저장하지 않음

단점

  • 이미지를 처음 요청하는 사용자는 긴 시간 대기

  • CDN 캐싱 전 최적화 작업 여러번 반복

  • 이미지 변경 요구사항으로 대규모 캐시 미스 발생

  • 동시에 많은 리사이징 요청으로 이미지 변환 실패

profile
비슷한 어려움을 겪는 누군가에게 도움이 되길

0개의 댓글

관련 채용 정보