본문 바로가기

aws

[AWS] cloudfront + Lambda@edge 를 활용한 이미지 리사이징하기 1

안녕하세요! 저의 첫 포스팅으로 AWS cloudfront + lambda@edge를 사용한 이미지 리사이징에 대해 이야기해볼 건데요. 제가 이미지 서버를 운영하면서 겪었던 문제를 lambda@edge를 적용해 해결했던 과정을 공유하려고 합니다.

 

현재 저는 지금 이커머스 플랫폼을 운영하는 회사에서 백엔드 개발자를 맡고 있습니다. 이커머스라는 플랫폼에는 상품에 관련된 이미지 등 많은 이미지들이 업로드되는 서비스인데요. 많은 이미지들을 판매자분들이 업로드하고 있고 그러다 보니 이미지 용량으로 인한 여러 문제들이 생겼습니다. 

 

지금 제가 맡고 있는 플랫폼에서 많은 마켓들이 인스타그램 등에서 활동하며 상품을 판매하는 인플루어서 분들이 많습니다. 그러다 보니 이미지를 직접 가공하여 상품 정보 이미지를 만들기보다는 직접 핸드폰으로 찍은 사진들을 바로 업로드하는 분들이 꽤 많으셨는데요. 이렇게 직접 찍어 올리는 이미지들은 용량자체가 큰 경우가 많습니다. 이때 가장 큰 문제는 이미지가 용량이 큰 경우 상품 상세정보를 보여주는 페이지에 진입 시 상품의 이미지들이 느리게 노출되는 것입니다. 이렇게 이미지가 느리가 노출된다면 저희 앱을 이용하시는 고객분들에게는 안 좋은 경험을 겪게 되겠죠. 그래서 이 문제를 해결하기 위한 방법은 리사이징을 통해 이미지 용량을 줄여 이미지 로딩 속도를 개선하는 것이었습니다. 

 

그럼 이미지 리사이징 어떻게 할까요? 

 

그럼 이제 이미지 리사이징을 처리하기 위해 어떤 식으로 구현하게 되었는지 이야기해 볼게요. 우선 제가 이전에 다니던 회사에서 구현된 방식을 설명드리겠습니다. 이전 회사에서의 이미지 리사이징 처리하는 방식은 이미지를 업로드하는 시점에 이미지를 필요한 사이즈에 맞게 리사이징 후 AWS S3에 업로드하는 방식이었습니다. 이런 구현 방식을 통해 이미지 저장소에는 원본 이미지와 리사이징 된 이미지들이 저장될 거예요.

 

예를 들어 썸네일 이미지를 업로드한다고 가정해 보겠습니다. 그럼 이미지 저장소에 업로드되는 이미지는 다음과 같습니다.

 

원본 이미지 썸네일 이미지 저장되는 이미지
1024x1024 640x640 원본 이미지(1024x1024), 리사이징 이미지(640x640)

 

지금 예시처럼 이미지가 단순히 하나의 정해진 사이즈로만 리사이징 된다면 크게 문제가 되진 않을 거예요. 그러나 기획의 요구사항이 바뀌거나 다른 이미지 사이즈가 필요해진다면 어떨까요? 이렇게 된다면 원본 이미지를 다시 리사이징 해주는 작업을 해야 하고 기존 이미지 업로드 API 에도 새로운 이미지 사이즈로 리사이징을 하도록 코드를 추가해야겠지요.

 

이런 방식의 문제사항을 정리해 보면 다음과 같습니다.

 

  1. 하나의 이미지를 저장할 경우 리사이징 된 여러 이미지를 저장하게 되어 용량 낭비가 심하다.
  2. 새로운 이미지 사이즈를 추가할 때마다 코드 변경을 해야 한다.
  3. 필요에 따라 기존 이미지를 다시 리사이징 해주는 작업이 생길 수 있다.

이러한 문제를 줄이는 방법 중 하나로 AWS Lambda@edge와 cloudfront를 활용하여 이미지 리사이징 및 캐싱 처리를 하는 방법이 있습니다. 

 

 

 


 

AWS Lambda@edge 이 무엇인가요?

 

Lamda@edge는 cloudfront의 기능으로 cloudfront의 CDN의 생성된 이벤트에 대한 응답코드를 실행합니다. Lamda@edge는 서버리스로 동작하기 때문에 개발자가 별도의 서버 관리할 필요가 없습니다. 그리고 사용자의 위치에 가장 가까운 AWS 로케이션에서 코드를 실행하기 때문에 지연시간이 더 단축되는 장점도 있습니다. 또한 lamda@edge를 사용한 컴퓨터 시간만큼만 비용이 불필요한 비용이 절감이 됩니다. 

 

이제 AWS Lamda@edge가 어떻게 동작하는지 알아보도록 하겠습니다.

 

먼저 lambda@edge 가 실행되는 방식을 알아보기 전에 cloudfront의 캐싱 방식에 대해 이해할 필요가 있습니다. Cloudfront에서 캐싱이 동작하는 순서는 아래와 같습니다.

 

사용자가 ①. cloudfront에 사용자가 이미지 조회를 요청하면 ②. cloudfront에서 이미지가 저장된 오리진 서버에 다시 요청을 합니다.  ③. 그럼 오리진 서버에서 이미지 응답값을 반환하고 cloudfront는 응답받은 값을 캐싱한 뒤 ④. 사용자에게 반환하게 됩니다. ⑤. 다시 동일한 경로의 이미지를 cloudfront에게 요청하게 되면 이미 캐싱이 되었기 때문에 cloudfront에서는 캐싱된 이미지를 바로 반환하게 됩니다.

 

AWS Cloudfront cahce 동작 방식 (AWS 블로그)

 

여기서 Cloudfront의 이벤트가 발생하면 Lambda 함수를 실행하도록 트리거를 연결할 수 있습니다. cloudfront에서는 총 네 가지의 이벤트 트리거를 설정할 수 있습니다. 자세한 설명은 아래를 참고해 주세요.

 

  • ❶ CloudFront 뷰어 요청 – CloudFront가 뷰어로부터 요청을 수신하고 요청된 객체가 엣지 캐시에 있는지 확인하기 전에 함수가 실행됩니다.
  • ❷ CloudFront 오리진 요청 – CloudFront가 오리진에 요청을 전달할 때만 함수가 실행됩니다. 요청한 개체가 엣지 캐시에 있으면 함수가 실행되지 않습니다.
  • ❸ CloudFront 오리진 응답 – CloudFront가 오리진으로부터 응답을 수신한 후 응답에 객체를 캐시 하기 전에 함수가 실행됩니다.
  • ❹ CloudFront 뷰어 응답 – 요청된 객체를 최종 사용자에게 반환하기 전에 함수가 실행됩니다. 이 함수는 객체가 이미 엣지 캐시에 있는지 여부에 관계없이 실행됩니다.

Lamda@edge와 CloudFront로 이미지 리사이징 처리를 하기 위해서는 위 네 가지의 트리거 중 CloudFront에서 오리진 응답 객체를 캐싱처리하기 바로 전에 실행되는  ❸ CloudFront 오리진 응답 트리거에 이미지 리사이징을 하도록 Lambda 함수를 연결해야 합니다. 

 

그렇게 되면 Cloudfront 에서 캐싱이 다음과 같은 순서로 처리되게 됩니다.

 

사용자가 ①. cloudfront에 사용자가 이미지 조회를 요청하면 ②. cloudfront에서 이미지가 저장된 오리진 서버에 다시 요청을 합니다.  ③. 그럼 오리진 서버에서 이미지 응답값 반환할 때 ❹.  트리거가 동작하여 Lambda 함수를 실행하여 이미지를 리사이징 합니다. ⑤ 그 후 cloudfront는 응답받은 값을 캐싱한 뒤 ⑥. 사용자에게 반환하게 됩니다. ⑦. 다시 동일한 경로의 이미지를 cloudfront에게 요청하게 되면 이미 캐싱이 되었기 때문에 cloudfront에서는 캐싱된 이미지를 바로 반환하게 됩니다.

 

이제 위에 설명된 Cloudfront 의 캐싱 순서에서 새로 추가된 번째  CloudFront 오리진 응답 이벤트에 실행할 이미지 리사이징 하도록 Lambda@edge에 배포할 코드를 작성하고 빌드 및 배포하여 Cloudfront에 이미지 리사이징을 적용해 보도록 하겠습니다.

 


 

⚠️ Lambda@edge 주의 사항

Lambda@edge 작업을 하기전에 주의해야 할 사항이 몇 가지 있습니다. 해당 내용에 대해 간단히 알아보고 다음 작업을 해보겠습니다.

  1. 원본 요청, 응답에 대한 response 사이즈는 1MB를 넘지 않도록 해야 합니다. 그래서 Lamdba@edge function에서 응답에 대한 결과 값을 반환하기 전 응답 사이즈를 검증하여 1MB가 넘을 경우에 대한 예외 처리를 추가해야 합니다.
  2. lambda@edge는 리전 us-east-1 (버지니아 북부)에만 지원됩니다. 다른 리전에는 생성할 수 없으니 유의해주세요.
  3. 별도의 환경변수를 설정할 수 없습니다. 

 

▼ 이벤트 유형에 따른 할당량

Entity Origin request and response event quotas (원본에 대한 요청 및 응답 이벤트) Viewer request and response event quotas (뷰어 요청 및 응답 이벤트)
Function memory size Same as Lambda quotas(128 MB to 10,240 MB) 128 MB
Function timeout 30 seconds 5 seconds
Size of a response 1 MB 40 KB
Maximum compressed size of a Lambda function and any included libraries 50 MB 1 MB

 

 

⚠️ Lamda@edge 캐싱 처리 시 주의 사항 

 

AWS Cloudfront에서 캐싱 처리를 할 때는 요청된 이미지 주소를 기반으로 캐싱됩니다. 예를 들어 서버에 아래와 같은 이미지 주소로 요청이 왔다고 가정해 보겠습니다.

 

https://static.lo_gos.com/food/chicken.jpg 

 

그럼 cloudfront는 오리진 서버에 "../food/chicken.jpg" 경로에 있는 이미지를 요청 후 응답을 보내기 전에 캐시에 저장합니다. 이번에는 기존 경로에 쿼리스트링을 추가 한 주소 "../food/chicken.jpg? w=640&h=640"으로 요청을 하게 된다면 cloudfront는 새롭게 이미지를 캐싱하게 됩니다. 

 

이런 이유로 쿼리스트링의 순서가 중요한데요. 클라이언트단에서 순서를 뒤죽박죽으로 요청하게 된다면 동일한 이미지에 대한 불필요한 캐시가 늘어나게 될 겁니다. 그래서 이미지를 요청하는 클라이언트에서 쿼리스트링을 항상 동일한 순서로 요청하도록 규칙을 미리 정하도록 하겠습니다. 

 

저희는 아래표와 작성된 형식으로 쿼리스트링을 구성할 예정입니다. 아래 표에 설명된 대로 클라이언트에서 순서를 지켜 이미지 주소 뒤에 쿼리스트링에 추가하면 됩니다. 

 

순서 이름 속성 필수 여부 설명
1 w 넓이 필수 width 값, 숫자만 가능, w,h 둘중 하나는 필수 항목
2 h 높이 필수 height 값, 숫자만 가능, w,h 둘중 하나는 필수 항목
3 f 이미지 포맷 선택  jpg, jpeg, webp, png 지원 gif는 현재 지원 안됨)
4 q 품질 선택 1~100까지 숫자만 가능
5 crop 이미지 크롭 선택 형식: ${top}x${height} 

top
: 이미지의 자를 시작 지점
height: 자를 이미지의 높이 0x100 : top: 0에서 100px 까지 crop

 

 

예시 ) 이미지 주소의 활용 예시.

https://static.lo_gos.com/product/main/20220118/some_image.jpg?w=640&f=webp&q=70

 

 

여기까지 Lambda@edge에 대한 사용하게 된 배경과 동작 방식 주의할 점에 대한 설명에 대한 글을 작성해 보았는데요. 생각보다 내용이 길어져서 본격적인 설정은 추가로 2부에 나눠서 작성하도록 하겠습니다. 여기까지 읽어주셔서 감사합니다 😊


[참고 사이트]