Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Archives
Today
Total
관리 메뉴

csct3434

Amazon S3 트리거와 Lambda를 활용한 썸네일 이미지 생성 (2) - 코드 구현 본문

개발 일지

Amazon S3 트리거와 Lambda를 활용한 썸네일 이미지 생성 (2) - 코드 구현

csct3434 2024. 4. 20. 05:39

인트로

지난 글에서는 Amazon S3 트리거와 Lambda를 활용하여 썸네일 이미지를 생성하는 방법에 대해 알아봤습니다.

 

AWS S3와 Lambda를 활용한 썸네일 이미지 생성 (1)

인트로 여느 때처럼 시즈닝 사이트를 둘러보던 중, 친구의 프로필 사진이 끊기면서 로딩되는 것이 눈에 띄었습니다. 크롬 개발자 도구로 확인해 보니 6.5Mb 크기의 3024 x 4032 이미지를 다운받아 40

csct3434.tistory.com

 

이번 글에서는 생성된 썸네일을 실제 서비스에서 활용하는 방법에 대해 알아보겠습니다.


코드 예시

@Service
@RequiredArgsConstructor
public class S3Service {

    private final AmazonS3 amazonS3;
 
    public UploadFileInfo uploadProfileImage(MultipartFile image) {
        String key = "origin/profile/" + UUID.randomUUID();
        return upload(image, key, true);
    }

    public UploadFileInfo uploadArticleImage(MultipartFile image) {
        String key = "origin/article/" + UUID.randomUUID();
        return upload(image, key, false);
    }

    private UploadFileInfo upload(MultipartFile multipartFile, String key, boolean resized) {
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(multipartFile.getSize());
        metadata.setContentType(multipartFile.getContentType());

        amazonS3.putObject("image-bucket", key, multipartFile.getInputStream(), metadata);
		
        return new UploadFileInfo(key, resolveImageUrl(resized, key));
    }
    
    private String resolveImageUrl(boolean resized, String originalKey) {
        String CDN_URL = "https://anfjkqw1.cloudfront.net/";
        if(resized) {
            return CDN_URL + originalKey.replaceFirst("origin/", "resize/");
        }
        return CDN_URL + originalKey;
    }
}

하나씩 살펴보겠습니다. (가독성을 위해 코드를 간추렸습니다 : 예외처리, 환경변수, 중복개선 생략 등)


public UploadFileInfo uploadProfileImage(MultipartFile image) {
    String key = "origin/profile/" + UUID.randomUUID();
    return upload(image, key, true);
}

public UploadFileInfo uploadArticleImage(MultipartFile image) {
    String key = "origin/article/" + UUID.randomUUID();
    return upload(image, key, false);
}

현재 서비스에서 게시글은 원본 이미지를 사용하고, 회원 프로필은 썸네일 이미지를 사용합니다.

S3 버킷상에서 게시글 이미지와 프로필 이미지를 구분하기 위해, 각각 Prefix를 다르게 구성하여 업로드 했습니다.


private UploadFileInfo upload(MultipartFile multipartFile, String key, boolean resized) {
    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentLength(multipartFile.getSize());
    metadata.setContentType(multipartFile.getContentType());

    amazonS3.putObject("image-bucket", key, multipartFile.getInputStream(), metadata);

    return new UploadFileInfo(key, resolveImageUrl(resized, key));
}

private String resolveImageUrl(boolean resized, String originalKey) {
    String CDN_URL = "https://anfjkqw1.cloudfront.net/";
    if(resized) {
        return CDN_URL + originalKey.replaceFirst("origin/", "resize/");
    }
    return CDN_URL + originalKey;
}

썸네일 생성 여부에 따라 데이터베이스에 저장할 이미지 주소가 달라지기 때문에, 이를 구분하기 위하여 resized 매개변수를 사용했습니다.

게시글 이미지 (원본: resized = false)

  • 원본 이미지 Key : 'origin/article/a.jpg'
  • 응답 주소 : 'https://anfjkqw1.cloudfront.net/origin/article/a.jpg'
  • 원본 이미지를 그대로 사용하기 때문에, 해당 이미지의 주소를 데이터베이스에 저장합니다.

프로필 이미지 (썸네일: resized = true)

  • 원본 이미지 Key : 'origin/profile/a.jpg'
  • 썸네일 이미지 Key : 'resize/profile/a.jpg'
  • 응답 주소 : 'https://anfjkqw1.cloudfront.net/resize/profile/a.jpg'
  • 클라이언트는 프로필 이미지로 원본이 아닌 썸네일을 조회하기 때문에, prefix를 변경하여 썸네일 이미지의 주소를 데이터베이스에 저장합니다.

썸네일 이미지 다운로드 실패 시

앞서 살펴본 방식에서는 썸네일 생성 여부를 확인하지 않고 이미지 주소를 예측하여 데이터베이스에 저장하고 있습니다.

만약 썸네일 생성 작업이 실패하거나 지연될 경우, 유효하지 않은 이미지 주소가 클라이언트에게 전송될 가능성이 존재합니다.

실제로 저희 서비스에서도 프로필 수정을 완료한 후 리다이렉트 페이지에서 곧바로 자신의 프로필을 조회하기 때문에, 거의 100%의 확률로 썸네일 이미지 요청에 실패합니다.

 

이러한 문제점을 해결하기 위해, 클라이언트에서 다음과 같이 동적으로 렌더링을 처리해 주었습니다.

  • 썸네일 다운로드 실패 시, 첫번째로 매칭되는 'resize/'를 'origin/'으로 변경하여 썸네일 대신 원본 이미지를 요청합니다.
    (https://cloudfront.net/resize/profile/a.jpg -> https://cloudfront.net/origin/profile/a.jpg)
  • 실제 코드에서는 원본 이미지 업로드 실패 시 예외가 발생하여 트랜잭션이 롤백되기 때문에, 일단 데이터베이스에 저장된 썸네일 이미지 주소의 원본 주소는 항상 유효합니다.
  • 위의 스크린샷을 보면, 썸네일 다운로드에 실패하자 원본 이미지를 요청하여 렌더링 한 것을 확인할 수 있습니다.

약 1초 후 페이지를 새로고침 하자, 썸네일 요청에 성공하여 썸네일 이미지가 렌더링 된 것을 확인할 수 있습니다.


마무리

  • 썸네일 이미지를 활용하여 화질에 크게 민감하지 않은 프로필 이미지의 로딩 속도를 개선해봤습니다.
  • 썸네일 다운로드 실패 시 추가로 발생하는 53ms의 지연을 대가로, 이의 약 21배인 1131ms의 지연을 줄일 수 있었습니다.
  • 파일 크기의 경우 8.4 MB에서 74.1 KB로 줄어들며 약 99% 감소했습니다.
  • 전송 시간의 경우 1.2s에서 0.069s으로 줄어들며 약 94%의 감소했습니다.

이상으로 'Amazon S3 트리거와 Lambda를 활용한 썸네일 이미지 생성' 글을 마치겠습니다.