S3 Object Lambda를 이용한 On-Demand 이미지 변환 서비스 소개
안녕하세요. 무신사 커뮤니티 개발팀 백엔드 엔지니어 조유신입니다. 커뮤니티개발팀은 무신사에서 커뮤니티와 관련된 도메인을 맡고 있으며 스냅, 좋아요, 인플루언서 마케팅 등을 담당하는 팀입니다. 이번 포스트에서는 AWS S3 Object Lambda를 이용해 On-Demand 이미지 변환 서비스를 구축한 사례를 공유 드리고자 합니다.
이미지 변환 서비스란?
일반적으로 서버에서 이미지를 제공하기 위해서는 원본 이미지보다는 작은 사이즈의 형태로 제공해야 서버의 리소스 및 트래픽 비용을 절감할 수 있습니다. 최근 스마트폰의 발전으로 스마트폰의 카메라 이미지 사이즈가 기본적으로 10MB 내외로 저장되기 때문에 100장의 이미지만 제공하더라도 1Gbps의 네트워크 대역폭을 넘도록 점유하게 됩니다. 또한 이미지 사이즈가 클 경우 클라이언트 사이드에서도 이미지를 렌더링하는데 더 오랜 시간이 걸리기 때문에 사용자 경험에도 악영향을 미칠 수 있습니다.
전통적인 이미지 변환 및 제공 방식은 AWS Tutorial에도 나와있듯이, S3 저장 이벤트를 통해 리사이징된 이미지를 저장하고 제공합니다. 변환된 이미지가 원본 이미지처럼 스토리지에 함께 저장되어 있기 때문에 CDN을 통한 제공이 가능하며 현재도 매우 효과적으로 사용되고 있는 아키텍처입니다.
하지만 이러한 이미지 제공 방식은 이미지를 UI에 맞게 최적화 하기 위해서는 여러 사이즈 및 포맷의 이미지를 변환하여 미리 저장해야 하고, 비동기로 동작하는 시스템이기 때문에 서버 애플리케이션은 변환된 이미지를 클라이언트에게 제공하기 위해 원본 이미지가 변환 되었는지 여부를 지속적으로 추적해야 하고, 이는 애플리케이션의 복잡도를 증가 시킨다는 단점이 있습니다.
변화되는 UI와 클라이언트에게 맞게 다양하게 변환된 이미지를 스토리지에 계속 저장해야 하기 때문에 비용도 증가합니다. 만약 추가적인 이미지 변환 방식을 필요로 한다면 기존의 모든 이미지를 변환해야하는 과정도 필요해집니다. 서비스의 이미지는 언제 사용자에게 다시 제공될지 모르기 때문에 이미지 저장 비용은 서비스가 지속될수록 누적되며 증가합니다.
On-Demand 이미지 변환 서비스
위와 같은 전통적인 이미지 변환 서비스의 한계점을 극복하기 위해 나온 아키텍처가 On-Demand 이미지 변환 서비스입니다. On-Demand 이미지 변환 서비스는 일반적으로 클라이언트에서 이미지를 요청했을 때 변환과정이 이루어지며 변환된 이미지를 CDN에 캐싱하여 지속적으로 제공하는 방식입니다.
업로드는 S3에 동일하게 이루어지지만 이후 클라이언트에서 이미지를 요청하는 시점 (On-Demand)에 이미지 변환이 이루어지기 때문에 서버 애플리케이션은 이미지 변환 시점을 추적하지 않아도 된다는 장점이 있고, 미리 변환된 이미지를 미리 저장해놓을 필요가 없기 때문에 누적되는 스토리지 저장 비용을 절감할 수 있다는 장점이 있습니다.
On-Demand 이미지 변환 구현
최근 다양한 기업이 On-Demand 이미지 변환 서비스를 구축하고 공개하고 있습니다. 이미지라는 큰 데이터를 다루기 위해 Serverless 아키텍처를 사용하고 Front Layer에는 CDN 서비스(AWS CloudFront)가 있다는 전제하에 일반적인 On-Demand 이미지 변환 아키텍처 구현 사례는 아래와 같습니다.
- Lambda@Edge
- API Gateway + Lambda
- Lambda Function URL
- S3 Object Lambda
우선 저희는 제목에서와 같이 S3 Object Lambda를 이용해 구축했고 위 구성들의 장단점과 의사결정 과정을 소개해드리겠습니다.
Lambda@Edge
Lambda@Edge는 AWS의 엣지 로케이션 인프라를 활용해 엣지 컴퓨팅을 할 수 있도록 지원하는 도구입니다. CloudFront Function과 비슷한 형태로 동작하지만, Lambda라는 서비스를 이용해 프로비저닝할 수 있어서 다양한 도구의 지원을 받을 수 있는 것이 장점입니다. 또한 엣지 로케이션에서 컴퓨팅이 동작하기 때문에 사용자는 자신과 근접한 지점에서 컴퓨팅된 응답을 받아볼 수 있다는 장점이 있습니다. 다만 S3의 저장위치로부터 엣지로케이션에 있는 람다 함수까지 이미지가 전달되는 시간이 있기 때문에 다른 구성과의 실제 성능 벤치마크 비교는 필요할 것 같습니다. Lambda@Edge는 AWS CloudFront와 함께 사용하기 위해 나온 서비스이기 때문에 구성이 간단하지만 모든 엣지로케이션에 배포되기까지 시간이 길게 소요되고 다른 선택지보다 비용도 가장 크다는 단점이 있습니다.
API Gateway + Lambda
API Gateway는 대표적인 Serverless 도구 중 하나로, 서비스들의 Endpoint를 노출시키기 위해 사용하는 도구 중 하나입니다. API Gateawy를 이용해 Lambda의 엔드포인트를 생성해 CloudFront와 Lambda를 연결시키기 위해 사용할 수 있으며 엔드포인트 모니터링 및 보호, 다양한 API 또한 함께 관리할 수 있다는 것이 장점입니다. 대표적인 Serverless 도구 중 하나이기 때문에 다양한 프레임워크나 IaC 도구를 통해 한번에 프로비저닝하고 관리할 수 있다는 것도 장점이지만, On-Demand 이미지 변환 서비스만을 위해 구성하기에는 관리 포인트가 늘어날 수 있고 API Gateway에 대한 별도의 비용이 과금된다는 단점이 있습니다.
Lambda Function URL
Lambda Function URL은 Lambda에 직접적인 Endpoint를 부여할 수 있는 기능으로 모든 구성중에 구조가 가장 단순하며 비용도 저렴합니다. Lambda Function URL을 이용해 Public Endpoint를 생성하고 CloudFront에서 직접 연결이 가능합니다. 하지만 생성된 엔드포인트가 Public하고 WAF 등을 활용한 모니터링이 어렵고 리소스 보호가 쉽지 않아서 보안에 취약하다는 단점이 있습니다.
S3 Object Lambda
S3 Object Lambda는 S3 객체에 대한 프록시처럼 동작하며 검색한 객체에 대한 응답전 처리 과정을 추가할 수 있는 기능입니다. S3 Access Point, S3 Object Lambda Access Point를 이용해 S3파일과 Lambda 함수에 접근할 수 있는 Access Point를 획득한 후 서로 연결되며 Lambda Function URL과는 다르게 엔드포인트를 공개하지 않고 모니터링 및 보호가 가능하다는 것이 장점입니다.
비용 비교
Lambda Function URL은 보안적인 문제때문에 제외한 후 다른 구성들의 비용을 비교해보면 1) S3 Object Lambda 2) API Gateway + Lambda 3) Lambda@Edge 순서대로 비용이 저렴합니다. 같은 요청량을 이용해 비교해보았을 때 다음과 같습니다.
- S3 Object Lambda는 Lambda@Edge에 비해 약 65% 저렴합니다
- S3 Object Lambda는 API Gateway + Lambda에 비해 약 10% 저렴합니다
저희는 위와 같은 비용 비교를 통해 이미지 변환 서비스가 비용 최적화를 위한 아키텍처라는 것에 중점을 두고 S3 Object Lambda를 이용하여 이미지 변환 서비스를 구축했습니다.
S3 Object Lambda를 이용한 On-Demand 이미지 변환 서비스 구현
S3 Object Lambda를 구성하기 위해서는 우선 대상 버킷에 대한 S3 Access Point 생성이 필요합니다. 이 때 S3 Access Point의 Public Access 및 Network origin에 대한 설정이 가능합니다.
S3 Access Point에 CloudFront에서 접근이 가능하도록 Access Point Policy에 아래와 같이 설정합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:ap-northeast-2:000000000000:accesspoint/{s3 access point}",
"arn:aws:s3:ap-northeast-2:000000000000:accesspoint/{s3 access point}/object/*"
],
"Condition": {
"ForAnyValue:StringEquals": {
"aws:CalledVia": "s3-object-lambda.amazonaws.com"
}
}
}
]
}
이후 이미지 변환에 사용할 Lambda를 프로비저닝 해야 합니다. Object Lambda Access Point를 통해 들어오는 Reqeust는 아래와 같습니다. 아래의 Json Format을 이용하여 Lambda Console 및 local invoke를 통해 테스트해볼 수 있습니다.
{
"xAmzRequestId": "1a5ed718-5f53-471d-b6fe-5cf62d88d02a",
"getObjectContext": {
"inputS3Url": "https://your-bucket-name.s3-accesspoint.us-east-1.amazonaws.com/s3.txt?X-Amz-Security-Token=...",
"outputRoute": "io-iad-cell001",
"outputToken": "..."
},
"configuration": {
"accessPointArn": "arn:aws:s3-object-lambda:us-east-1:123412341234:accesspoint/myolap",
"supportingAccessPointArn": "arn:aws:s3:us-east-1:123412341234:accesspoint/myap",
"payload": "test"
},
"userRequest": {
"url": "/s3.txt",
"headers": {
"Host": "your-bucket-name.s3-object-lambda.us-east-1.amazonaws.com",
"Accept-Encoding": "identity",
"X-Amz-Content-SHA256": "e3b0c44297fc1c149afbf4c8995fb92427ae41e4649b934ca495991b7852b855"
}
},
"userIdentity": {
"type": "IAMUser",
"principalId": "...",
"arn": "arn:aws:iam::123412341234:user/myuser",
"accountId": "123412341234",
"accessKeyId": "..."
},
"protocolVersion": "1.00"
}
또한 Lambda의 Resourced Based Policy에서 CF가 Lambda를 invoke할 수 있도록 권한을 아래와 같이 허용해주어야합니다.
// Lambda Resource Based Policy
{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "cloudfront",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:ap-northeast-2:000000000000:function:sample-function"
}
]
}
Lambda의 프로비저닝이 성공적으로 끝났다면 S3 Object Lambda Access Point를 생성해야합니다. S3 Access Point와 마찬가지로 Public Access에 대한 권한 설정이 가능합니다. S3 Object Lambda 구성 설정에서 이전에 생성한 S3 Access Point와 Lambda를 선택해 프로비저닝할 수 있습니다.
또한 Lambda Resource Based Policy와 같이 AWS CloudFront에서 S3 Object Lambda Acess Point에 접근할 수 있도록 Object Lambda Access Point Policy를 아래와 같이 설정해주어야합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3-object-lambda:Get*",
"Resource": "${{ Object Lambda Access Point ARN }}",
"Condition": {
"StringEquals": {
"aws:SourceArn": "${{ CloudFront ARN }}"
}
}
}
]
}
마지막으로 CloudFornt에서 Origin을 생성합니다. S3 Object Lambda Access Point는 공개된 엔드포인트가 아니기 때문에 OAC 설정을 새로 생성하여 Singed Request (Signature V4)를 선택합니다.
이후 위에서 생성한 OAC를 이용해 CloudFront의 Origin과 연결하고 캐시 정책 및 원본 요청 정책 등을 설정하면 정상적으로 동작하는 것을 확인하실 수 있습니다.
S3 Object Lambda를 구성하는 방법을 중점적으로 다루었는데 자세한 것들은 아래와 같습니다.
- 람다 함수 구성은 Lambda Power Tuning을 이용해 최적화 했습니다
- 이미지 변환은 node.js로 작성되었고 node-sharp를 이용해 구현했습니다
- 애플리케이션 로깅은 middly와 Lambda Powertool을 이용해 작성했습니다
- 모니터링 및 로그 시각화는 Datadog을 이용해 구축했습니다
- CI/CD는 serverless framework와 Github Action을 이용해 구축했습니다
결론
전통적인 이미지 변환 아키텍처의 누적되는 스토리지 저장 비용을 절감하고 사용자의 UI에 더욱 최적화된 이미지를 제공하기 위해 S3 Object Lambda를 이용해 비용 최적화된 On-Demand 이미지 변환 시스템을 성공적으로 구축했습니다.
원본 이미지에 비해서는 95%의 압축률을 보였으며 기존 전통적인 아키텍처에서 제공하던 이미지에 비해서도 50% 이상의 압축률을 보였습니다. 이를 통해 CloudFront DataTransfer 비용 및 S3 저장 비용을 절감할 수 있었고 Client 에서도 TBT(Total Blocking Time)와 SI(Speed Index)를 각각 39%, 13% 감소시킬 수 있었습니다.
개발팀과 SRE팀 동료들의 적극적인 지원과 조언 덕분에 이미지 제공 시스템을 성공적으로 전환할 수 있었습니다. 무신사는 큰 규모의 트래픽을 다루며 빠르게 성장을 하고 있지만, 기술로 변화를 만들어내는 것에 열려있는 조직입니다. 기술을 통해 큰 변화를 만들어내고 싶으신 분들은 무신사에 많은 관심을 가져주시면 감사합니다.
Musinsa CAREER
함께할 동료를 찾습니다.
이처럼 무신사는 매년 빠르게 성장하며 새로운 문제를 마주하고, 문제를 해결하기 위해 새로운 기술을 적극적으로 도입하고 있습니다. 전국민이 사용하는 1위 패션 플랫폼 무신사에서 기술로 비즈니스를 성장시키는 경험을 함께하고 싶으시다면 아래 채용 페이지를 통해 지원해 주세요!
🚀 무신사 채용 페이지 : https://corp.musinsa.com/ko/career