1인마켓 이야기

와이어드가 만들어 온 1인마켓의 모든 것을 알려드릴게요!

1인마켓 개발기CloudFront와 AWS Lambda@edge를 활용한 이미지 최적화

개발그룹 엔지니어

안녕하세요. 저는 와이어드컴퍼니 개발팀입니다.

Kemi에서 리사이징된 이미지를 받아 빠르게 컨텐츠를 로딩할 수 있도록 구현했던 경험을 공유하고자 합니다.



문제 인식 


"컨텐츠를 빠르게 보여주고 싶다."

Kemi에서는 상품 상세페이지와 같이 고화질의 이미지가 포함된 컨텐츠를 용량 제한 없이 모두 그대로 다운 받아 보여주고 있었습니다. 

또한 작은 썸네일 화면에서도 풀사이즈의 이미지를 다운 받아야 하는 문제가 있었습니다.

썸네일 사이즈에 딱 맞게 최적화된 이미지를 보여주고 싶다.

게다가 S3(Origin Server)에 직접 접근하여 이미지를 받아오고 있었기 때문에 요청이 있을 때마다 이미지를 새롭게 다운 받고 있었습니다. S3의 리전은 서울이었기 때문에 서울에서 접근하는 것은 상관 없지만, 거리가 멀어진다면 전송 속도에서도 지연이 있었을 것입니다.

첫 로딩의 지연을 확실하게 줄인다면 이용자에게 좋은 첫인상을 줄 수 있을 것이라고 생각했습니다. 그리고 “느리다” 라는 말은 개발자로서 참을 수 없기 때문에 빠르게 작업을 해야 하겠다고 생각했습니다.



"업로드하는 유저들의 피로감을 낮추고 싶다."

이미지 등록 관련 사내 피드백

기존에는 이미지 업로드 용량을 제한하여 최소한으로 관리하고 있었습니다. 그렇다보니 위와 같은 피드백을 받기도 하였습니다. 사내 피드백이긴 하지만 일반 유저들에게도 비슷한 사이즈 제한을 두고 있었기 때문에 일반적인 상황에서 유저가 이미지 사이즈를 줄여야 하는 불편함이 있었습니다.

이런 피로감을 줄이고 케미를 사용함에 있어 유저의 경험이 끊기지 않도록 유지하는 것이 중요하다고 생각했기 때문에 리사이징 작업의 필요성을 느꼈습니다.

그래서 다음의 목표를 설정하였습니다.

  • 이미지를 출력하는 사이즈에 맞춰서 리사이징하여 네트워크 용량을 줄이자.
  • S3의 연산을 분리하여 전송속도를 높이자.
  • 한번 로딩된 이미지를 캐싱하여 네트워크 효율성을 높이자.




해결의 여정


Sharp

먼저 이미지 리사이징을 프로그래밍으로 가능하게 만들어야 했습니다. 정확히는 출력할 이미지의 width와 height를 인풋으로 넣으면 리사이징된 이미지를 받을 수 있어야 했습니다. 그래서 찾은 게 Sharp 모듈이었습니다.

The typical use case for this high speed Node.js module is to convert large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.

Sharp는 대부분의 이미지 형식을 지원하고 빠른 처리를 가능하도록 도와줍니다. 다음과 같은 간단한 코드로 리사이징된 이미지를 받을 수 있는 함수를 제공하였습니다.


await Sharp(body)

  .rotate()

  .resize(width, height)

  .toFormat(format, {

    quality

  })

  .toBuffer();



자세한 코드 설명은 sharp API 링크 로 대체하겠습니다.(중요하지 않기 때문에)

이로써 Node 환경에서 프로그래밍으로 이미지를 리사이징 할 수 있게 되었습니다. 이제 이 코드를 실행할 환경을 구성해야 합니다.


이미지를 저장하고 불러오는 전략 설정

실행할 환경을 구성하기 위해서는 이미지를 불러오는 전략을 먼저 설정해야 했습니다. Client(웹 사이트)에서 리사이징 된 이미지를 받으려면 2가지 방법이 있습니다. 

1. 업로드할 때 S3(Origin Server)에 자주 쓰는 썸네일 규격을 정의하여 리사이징이 완료된 이미지를 저장한다. 그리고 리사이징된 이미지를 불러온다.


2. S3(Origin Server)는 원본 이미지만 저장하고, 다운로드 할 때 리사이징을 실시하고 가져온다.

1번 방법은 미리 리사이징된 이미지를 저장하므로 장점은 다운로드할 때 따로 서버 연산이 필요 없다는 장점이 있습니다. 반면 S3의 저장 공간을 상대적으로 많이 사용하고 규격이 정의되지 않은 이미지는 대응할 수 없다는 단점이 있습니다. 이 방법을 채택할 경우에는 upload function에 리사이징 코드가 작동할 수 있도록 구성해야 합니다.

반면 2번 방법은 자유롭게 리사이징이 가능하고, 원본 이미지만 저장하므로 S3의 저장 공간을 상대적으로 덜 사용하는 장점이 있습니다. 하지만, 다운로드 시 연산 과정이 있어 이미지 로드에 추가 지연이 있을 수 있다는 단점이 있습니다. 이 방법을 채택할 경우에는 S3에서 response를 줄 때 작동하는 function에 리사이징 코드가 작동해야 합니다.

Kemi에서는 2번 방법을 채택하였습니다. 이제 막 런칭한 서비스이기 때문에 디자인이 자유롭게 변경될 가능성이 있으므로 이미지 사이즈는 규격으로 정해놓기가 어려웠기 때문입니다. 그리고 저장공간을 얼마나 많이 사용해야 할지 예상이 안되는 상황이었기 때문에 모든 규격의 이미지를 저장하는 것이 비효율을 만들어 낼 수 있다고 생각했습니다.

2번 방법을 채택함과 동시에 앞서 언급한 단점을 해결해야 했습니다.

"이미지를 불러올 때 리사이징 연산을 최소화 할 수 없을까"



CloudFront

Amazon CloudFront는 .html, .css, .js 및 이미지 파일과 같은 정적 및 동적 웹 콘텐츠를 사용자에게 빠르게 배포하는 웹 서비스입니다. CloudFront는 엣지 로케이션이라고 하는 전 세계 데이터 센터 네트워크를 통해 콘텐츠를 제공합니다. 사용자가 CloudFront에서 제공하는 콘텐츠를 요청하면 가장 짧은 지연 시간(시간 지연)을 제공하는 엣지 로케이션으로 요청이 라우팅되므로 콘텐츠가 가능한 최고의 성능으로 제공됩니다. from AWS Docs

 파란, 보라점들이 Edge Location들입니다.

AWS에서 제공하는 CloudFront는 전세계에 퍼져있는 Regional Edge를 통해서 Content를 전송하는 CDN 서비스입니다. Edge가 전 세계 퍼져있어서 요청이 들어오면 가장 가까운 Edge에서 요청을 담당합니다. 게다가 각 Edge에서는 요청에 대한 Response들을 캐싱하여 동일한 요청이 들어오면 Origin Server로 요청을 보내지 않고도 빠르게 응답을 내려줍니다.

동일한 요청(2번 요청이 반복된 경우)이 들어오면 Edge Location에서 바로 응답을 내려줍니다(3/3c)

CloudFront의 CDN을 이용하면 이미지를 불러올 때의 연산을 최소화 할 수 있다고 생각했습니다. 이미지에 대해 1번 연산을 해두면 response가 캐싱되어 있다가 Origin Server로의 요청 없이 바로 Edge Location에서 응답을 빠르게 받을 수 있기 때문입니다.

또한 CloudFront는 EdgeLocation에서 사용자가 지정한 함수를 실행할 수 있게 해주는 컴퓨팅 서비스를 제공합니다.



Lambda@Edge

Lambda@Edge는 AWS Lambda가 확장된 컴퓨팅 서비스로 Edge Location에서 실행되는 함수를 정의할 수 있습니다. Lambda 함수 정의가 전 세계의 Edge에 복사되어 뿌려집니다. 따라서 이용자와 가장 가까운 곳에 위치한 Edge에서 함수를 실행할 수 있다는 장점이 있습니다.

맨 첫 단계에서 만들었던 리사이징 함수를 여기서 실행하면 될 것 같습니다. Origin Server에서 발생한 응답을 Trigger로 하여 실행하는 함수를 정의해둔다면, 이용자와 가장 가까운 곳에서 리사이징을 실시하여 response를 내려줄 수 있을 것입니다.

Origin response를 trigger로 하는 lambda 함수로 정의해야 한다.

Lambda@Edge를 사용하면 Node.js 및 Python Lambda 함수를 실행하여 CloudFront가 제공하는 콘텐츠를 사용자 지정하여 AWS 위치의 함수를 최종 사용자와 더 가깝게 실행할 수 있습니다. 

이 함수는 서버 프로비저닝 또는 관리 없이 CloudFront 이벤트에 응답하여 실행됩니다. Lambda 함수를 사용하여 CloudFront 요청 및 응답을 변경할 수 있습니다. 

AWS Lambda@edge문서 참조

 Edge에 올라간 Lambda 함수가 어떻게 리사이징 할 것인지를 알려주기 위해 쿼리스트링을 사용하였습니다. 쿼리스트링이 Request에 계속 남아있기 때문에 Lambda에서 확인하기가 간편하였고, 자유롭게 입력하여 리사이징을 할 수 있기 때문이었습니다.

 예시 


// image width가 100px로 리사이징된 이미지

https://image.kemi.io/static/kemi_with_money.png?w=100

// image width가 100px height 가 200px로 리사이징된 이미지

https://image.kemi.io/static/kemi_with_money.png?w=100&h=200





정리


CloudFront와 Lambda@Edge를 활용하여 이미지 response가 발생하였을 때 리사이징된 이미지가 서빙(serving)될 수 있도록 세팅하였습니다. 각 edge에서 한번 response가 발생하면 동일한 요청에 대해서는 캐싱된 콘텐츠로 빠르게 응답을 받을 수 있게 되었고, 서버의 연산을 최소화 하여 효율성을 추구할 수 있게 되었습니다.

또한 Origin Server(S3)의 리전과 상관없이 전 세계에 거의 동일한 네트워크 속도로 컨텐츠를 제공할 수 있게 되었습니다. 세팅한 결과물들을 보여드리면서 이 글을 마무리 하고자 합니다.

[캐싱된 이미지인지 확인하는 방법]

개발자 도구의 이미지 요청에 header를 통해 확인할 수 있습니다. “x-cache”라는 항목이 캐싱 여부에 따라 달라집니다.


Hit from cloudfront 이므로 캐싱되었던 콘텐츠입니다.



Miss from cloudfront 이므로 캐싱되어 있지 않아 오리진 서버에서 가져온 콘텐츠입니다.

[속도 비교]

보통의 이미지 사이즈로 테스트를 해보았습니다. 최초로 이미지를 받을 때는 Origin Server에 다녀오기도 하고, 리사이징 함수를 거쳐야 하기 때문에 상대적으로 속도가 많이 지연되는 것을 볼 수 있습니다. 반면 이 과정이 생략된 그 다음 요청부터는 굉장히 빠르게 이미지를 다운 받는 것으로 확인 되었습니다.

최초로 로드한 이미지 다운로드 속도

재요청한 이미지 다운로드 속도


추가 과제

토크쇼 주제 중에 하나로 논의가 되었다.


Kemi의 프론트엔드 팀은 매주 토크쇼라는 이름으로 프론트 기술이나 업무 상황을 이야기하고 있습니다. 이 자리에서 작업 내용이 공유가 되었고, 추가로 캐싱 주기나 용량 관련한 이슈가 이야기 되었습니다.

CloudFront에서 기본으로 제공하는 스펙에서 캐싱 주기를 24시간으로 지정하고 있습니다. 물론 이 스펙은 변경이 가능하며, response header로 컨트롤이 가능합니다.

또한 모든 이미지를 캐싱하다보면, 이미지 용량이 매우 클 경우, 캐시 용량이 모두 채워져서 다른 이미지들의 캐싱이 안될 수 있습니다. 캐싱하고자 하는 이미지 용량을 제한하여 해결할 수 있습니다. 이 문제는 추후 사용량을 보고 결정 될 것 같습니다.


구체적으로 어떻게 세팅하였나요? 

AWS를 다루는게 어려워서 구축 과정 하나하나 소개하고 싶지만, 워낙 자료도 많이 발행이 되어 있는 분야이고 서비스나 회사의 상황에 따라 다른 부분도 많을 것 같아서 참고한 아티클을 공유드리겠습니다. 이 글에서는 어떤 문제가 있고 어떤 목적으로 해결방법을 모색해왔는지에 대한 과정 위주로 보여드리고 싶었습니다 :)


참고자료




새로운 커머스의 시대를 만들고 있어요.

와이어드컴퍼니가 더 궁금하다면?


바로가기




와이어드컴퍼니 주식회사 I 대표 : 홍만의, 황봄님


사업자등록번호 : 631-87-01013

06619 서울시 서초구 서초대로 396, 강남빌딩 19층 1903호


개인정보 처리방침