![[이미지 리사이징] Lambda@Edge 502, 503 에러](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkcSmZ%2FbtrAJTFQv57%2FhH7PIxTOkk5lCNykTDL8H0%2Fimg.png)
어느덧 벌써 벡엔드 스린이로 활동한지 4주차가 되어가는 시점입니다..
기본도 없이 시작한 백엔드가 많이 발전해나가는 모습을 보며 참으로 고생하고 있구나 싶습니다..
오늘은 lambda@Edge로 이미지 리사이징을 하는 방법을 소개드리고,
제가 정말 삽질했던 부분을 코멘트 드리려고 합니다. 이 정도로 열렬히 삽질한 적은 오랜만이라,,
바로 글을 써서 많은 분들께 실낱같은 힘을 보태드려야겠다는 생각이 지배적인 순간입니다.
Cloudfront, node.js를 사용하시며 502에러 503에러가 발생하신 분들께 바칩니다.
+ Lambda의 1MB 요청, 응답 제한과 base64 인코딩과 싸우셧던 분들도 도움이 되실겁니다.
+ 처음 작업하시는 분들도 도움이 되실 겁니다. (참고문헌 빵빵)
서론
우리는 서비스 내에서 썸네일 이미지와 같은 작은 이미지들을 처리할일이 많습니다.
이때 우리는 resizing이라는 작업을 진행해야합니다. (용량도 줄이고, 화소도 줄이고, pixel도 작게 만들고, 확장자 바꾸고)
이 resizing을 하는 방법은 2가지가 존재합니다. 첫번째는 "모든 case의 이미지를 생성하는 방식"과 두번째는 "그때 그때 바꿔주는 방식"입니다. 딱봐도 두번째가 용이하겠죠? 오늘 다룰 내용은 두번째 방식에 삽질 내용을 공유하려 합니다.
본 포스팅은 AWS 셋팅과 node.js 코드를 다루지 않습니다.
AWS를 셋팅하는 내용과 node.js의 코드는 다른 많은 포스팅에서 다루니 본 포스팅 가장 하단에 있는 Ref를 참고하세요. 제가 크게 도움 받았던 블로그를 언급하겠습니다.
CloudFront의 내용 및 성능 -> Ref 6 참고
node.js 코드의 디테일 -> Ref 8 참고
현 시점에서 AWS 셋팅과 코드에서 가장 도움된 블로그 -> Ref 9 참고
두번째 방식에 대한 자세한 비교와 설명그리고 코드 -> Ref 10 참고
"모든 case의 이미지를 생성하는 방식"
첫번째는 모든 case의 이미지를 생성하는 방식입니다. 아래와 같은 순서로 진행됩니다.
해당 방법을 이해하려면 이미지가 최초에 저장되는 시점 부터 알고 있어야합니다.
이미지 저장 순서
1. Client에서 이미지 전달
2. Application Server에서 이미지를 S3로 전달
3. S3에 붙어있던 AWS lambda Trigger 동작
(이때 람다 트리거는, 200x200, 400x400, 600x600 등 각 사용하는 pixel case마다 미리 사진을 생성해 놓는 동작 진행)
4. lambda Trigger를 통해 생성된 이미지 Endpoint(URL)을 DB에 담음
이미지 요청 순서
1. Client에서 이미지 요청
2. Application Server에서 S3에 원하는 픽셀의 이미지 요청
3. 이미지 요청 시점(썸네일인지, 원본인지)에 따라 필요한 픽셀의 이미지를 던져줌.
문제
같은 이미지를 여러번 중복저장 하는 낭비 발생 -> "그때 그때 바꿔주는 방식"이 필요합니다.
"그때 그때 바꿔주는 방식(on-the-fly)"
두번째 case 입니다. 이번에는 Lambda@Edge와 CloudFront 방식을 통해서 이미지가 들어온다면,
이를 그때그때 작업하여 url query 파라미터에 섞인 값에 의해 이미지를 변환하게 됩니다.
이미지 저장 순서
1. Client에서 이미지 전달
2. Application Server에서 이미지를 S3로 전달
3. S3를 통해 생성된 이미지 Endpoint(URL)을 DB에 담음
리사이징된 이미지 요청 순서
1. Client에서 이미지 요청
2. Application Server에서 CloudFront에 원하는 픽셀 이미지 요청(URL 쿼리에 섞어서)
ex) https://d111111111.cloudfront.net/dev/test.jpg?s=200x200&q=60&t=crop&f=webp
3. CloudFront는 캐시된 이미지가 있는가 체크
4. 없으면 Lambda@Edge Trigger를 발생시킴.
5. Lambda@Edge는 S3에서 이미지를 가져옴
6. Lmabda@Edge 내의 javascript를 통해, 이미지를 변환함
7. 변환된 이미지를 DB에 담음
추가로 원본을 요청하는 경우도 리사이징된 이미지 요청에서 다르지 않습니다.
왜냐면 Lambda@Edge의 js 코드 내에서 쿼리파라미터가 없다면 그냥 원본 파일을 반환합니다.
위 1~7번 설명을 가시화 시킨 것이 위 사진 <Figure 2>입니다.
ex) https://d111111111.cloudfront.net/dev/test.jpg?s=200x200&q=60&t=crop&f=webp
참고로 파라미터의 설명은 아래와 같습니다. (Ref 10 코드 사용)
s(size)는 200x200 pixel이고,
q(quality)는 원본대비 60% 이며
t(type)은 node.js의 sharp라는 libaray 내에 주는 option 값 (모르셔도 됩니다.)
f(format)은 반환받을 이미지 확장자 입니다.
본론
이 포스팅을 장황하게 집필한 이유를 지금 부터 서술하도록 하겠습니다.
먼저 아래와 같은 표면적 오류를 겪었습니다.
1. 502 Error
2. 503 Error
하지만 이를 해결하기 위해선 많은 인고의 시간을 겪어야 했습니다.
우선 502에러 부터 소개하도록 하겠습니다.
1. 502 Error 왜 뜸?
502 오류는 상대적으로 감사한 오류입니다.
502(불량 게이트웨이) 오류는 서버 A가 다른 서버 B로부터 올바른 요청을 받지 못했음을 의미하는 HTTP 상태 코드입니다.
이를 풀어 설명하자면, 자네 지금 쿼리가 틀리지 않았는가? 로 오인할 수 있겠지만,
Lambda@Edge를 사용하는 제 경우에는 문제가 달랐습니다. 바로, "Lambda@Edge에 응답 용량 제한" 입니다.
Ref 11을 살펴보시면, 하단부에 요청용량제한과 응답용량제한을 발견할 수 있습니다.
lmabda@Edge의 요청 용량은 1MB로 제한되며
lmabda@Edge는 응답 용량은 1.33MB로 제한됩니다.
바로 우리가 원하는 이미지 요청이, 리사이즈를 열심히 했는데 응답 용량을 넘겨서 발생하는 문제입니다.
즉 1MB 보다 작은 이미지가 정상적으로 막 리사이징 되다가,
특정 size를 넘기면 1.33MB보다 크게 생성되어 응답하지 못하는 것 입니다.
따라서 이미지를 크게 요청하지 않으시면 됩니다. (원본이 큰 이미지는 502가 아니라 503 에러를 만나셨을 것 같습니다.)
pixel이 큰 이미지 요청이 만약 필요하시다면 아래와 같은 코드를 사용하시길 바랍니다.
while (1) {
resizedImage = await sharp(s3Object.Body).rotate();
metaData = await resizedImage.metadata();
//If the original h and w are larger than the request,
if (metaData.width > width || metaData.height > height) {
resizedImage
.resize(width, height, { fit: type });
}
if (byteLength >= 1046528 || originalFormat != requiredFormat) {
resizedImage
.toFormat(requiredFormat, { quality: quality });
}
resizedImage = await resizedImage.toBuffer();
byteLength = Buffer.byteLength(resizedImage, 'base64');
if (byteLength >= 1046528) {
quality -= 10;
}
else {
break;
}
}
만약 사이즈가 1046528 (1MB) 보다 크다면, Quality를 줄이는 방향으로 가면 됩니다.
썸네일 이미지의 경우 100x100등 작은 이미지를 사용하기에 사용자 경험을 크게 해치지 않을 것으로 판단했습니다.
또한 필요하다면 해당 이미지를 클릭했을 때 원본 이미지를 보여주는 방향으로 작업할 수 있겠지요.
저희 Application Server의 경우 사전에 S3에 업로드 되는 이미지의 용량제한을 10MB로 설정 하였기에
502에러는 이제 안녕입니다. (제가 짠 코드는 아니구요 Ref 10 코드에요.)
502 예시
test.jpg가 60KB 일 때
ex) https://d111111111.cloudfront.net/dev/test.jpg?s=200x200&q=60&t=crop&f=webp -> 정상
ex) https://d111111111.cloudfront.net/dev/test.jpg?s=3000x3000&q=100&t=crop&f=webp -> 502 ERROR
Tip
앞서 요청 1MB와 응답 1.33MB의 비밀은 바로 base64 Encoding 때문입니다.
lambda@Edge의 응답은 무조건 base64 Encoding을 해야 하는데요,
binary에서 TEXT로 변경하는 base64 Encoding 방식은 약 용량의 33%를 증가 시킵니다.
해당 용량 때문에 에러가 발생했다.. 도 틀린말은 아닌 것 같으나..
이는 어쩔 수 없는 lambda@Edge의 제한이기 때문에 해당 용량을 초과 할 일 없게 줄이도록 해야겠습니다..
2. 503 Error 왜 뜸?
저를 삽질시킨 장본인 입니다.
실제 사용하는 사용자들은 보통 3~4MB의 이미지를 전달할 것이고, 이런 원본을 S3에서 보관해야 합니다.
사용자가 필요하다면 좋은 화질의 원본을 제공해야할 수도 있으니깐요!
하지만, 1MB를 초과하는 이미지를 넣고 이를 Lambda@Edge로 변환하기만 하면 503 에러를 마주칠 수 있습니다..
이는 502 에러가 뜬 이유가 파일의 용량이었으니 이를 내재적으로 포함하고 있으나,
가장 큰 이유는....
님들 혹시 Lmabda@Edge 메모리 용량 얼마 쓰세요?
초기 값 안바꾸셨으면 128MB 아니에요?
건방진 말투 제송합니다.
제가 겪은 이슈는 Quality도 줄이는 걸로 이제 더 이상 용량에는 문제가 없다! 라고 생각했던 사안에서 왔습니다.
base64 인코딩이랑도 싸우고, Quality 까지 조절하는 방안을 찾아 적용시키니 용량에 문제가 없어야 할텐데 말입니다..
그래서 열 받아서 메모리 용량을 2GB를 줬습니다. 그랬더니 해결했어요.
람다 엣지에서 애용하는 라이브러리인 sharp의 경우 내부 recursive 한 동작이 많아서
메모리를 엄청 잡아먹는데 더불어, 502 막겠다고 Quality 조절하면서 이를 while문으로 때린 결과 입니다.
메모리를 음층나게 잡아먹었고 503 즉, 서버가 바쁘다 에러가 발생했습니다.
현재 10MB 이미지를 넣어보고 테스트 해보니 잘 나오는 결과를 볼 수 있었습니다.
돈은 밑에서 계산해봅시다.
3. CloudWatch 어서봄?
S3 버킷 리전이 서울이라서 CloudFront 및 Lmabda@Edge 트리거가 서울 쪽에서 잡힙니다.
전 Lambda@Edge 리전인 버지니아 북부에서 찾을라니깐 절대 안나오더라구요. 서울에서 CloudWatch를 들어가 주세용
Cloud Watch에 남긴 log를 통해서 몇가지 테스트를 진행해봤습니다.
위에처럼 2GB 메모리를 때린 Lambda가 엄청나게 많은 호출을 이룬다? 곤란하겠죠?
위 그림은 1ms 당 lambda 사용 요금입니다.
9.7MB 짜리를 리사이징 해본 결과, 아래와 같은 시간이 걸렸습니다.
Duration: 597.56 ms Billed Duration: 598 ms Memory Size: 2048 MB Max Memory Used: 207 MB Init Duration: 674.32 ms
Billed Duration의 경우 598 ms 니깐, 하나의 이미지를 호출하기 위해 0.0000199134 USD 원을 썼습니다.
10만장 정도 곱하면 1.99134 USD 니깐, 크게 신경써야할 금액은 아닌 것 같습니다. 생각보다 안 곤란하네요.
어차피 CloudFront에 캐시되서 크게 호출을 안하니깐요. 이미지가 안떠 사용자 경험이 깨질바엔 람다에 돈을 더 투자하겠습니당. (바꾸기 귀찮은거 아님)
마치며
나처럼 고생한 사람이 없었으면 좋겠다에서 시작한 바램이 세 시간 가까이 글을 집필하게 되었네요.
물론 저는 4주차 코린이기 때문에 제 글을 무조건 신용하는 것 보다는 비판적인 시선에서 바라봐 주시는 것이 옳을 것 같습니다. 스택오버플로우의 Lambda@Edge 글과, 많은 커뮤니티를 뒤져 보았으나,, 해결하기 참 어려운 주제였습니다.
다음 번에도 제가 글을 쓰러 온다면 굉장히 힘든상황이니 당분간은 글 안쓰길 바래주세요.
고생하셨습니다. 화이팅
Ref
Ref 1. AWS 공식 가이드
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/with-s3-tutorial.html
Ref 2. @Edge 사용 리사이징 // CloudFront Cache 사용
https://mygumi.tistory.com/377?category=744793
Ref 3. 여러 크기의 이미지 저장 // lambda만 사용
https://mygumi.tistory.com/324?category=744793
Ref 4. Lambda Edge
https://tesilio.github.io/Lambda@Edge
Ref 5. AWS official Image Handler
https://github.com/aws-solutions/serverless-image-handler
https://heowc.dev/2022/04/11/serverless-image-handler/
Ref 6. CloudFront 속도 측정
https://dev.classmethod.jp/articles/how-fast-is-cloudfront-speed-test/
Ref 7. 코드가 엄청 자세히 나오는 가이드
https://bokyung.dev/2021/05/14/lambda-edge-resize/
Ref 8. 딱봐도 엄청 자세히 알려주는 것 같은 가이드
https://devhaks.github.io/2019/08/25/aws-lambda-image-resizing/
Ref 9. 가장 최신버전, Node.js 14에 AWS도 화면 같음
https://velog.io/@su-mmer/CloudFront와-LambdaEdge를-이용한-이미지-리사이징
Ref 10. 당근마켓 on-the-fly
https://medium.com/daangn/lambda-edge로-구현하는-on-the-fly-이미지-리사이징-f4e5052d49f3
Ref 11. AWS lambda@Edge 제한
스타트업의 이야기
연락은 inventer@nangman.im 으로 부탁드려요