Amazon S3 보관 비용과 이미지 CDN 트래픽 최적화
안녕하세요, 인프랩 DevOps 엔지니어 제이크, 구피입니다.
인프랩은 최근 Amazon S3 보관 비용과 CDN 트래픽을 최적화하는 성과를 달성했습니다. S3 Intelligent-Tiering 스토리지 클래스 도입과, CDN 응답 이미지 AVIF·MP4 포맷 변환 지원에 관한 내용입니다.
인프랩이 무엇을 문제로 삼고 어떻게 최적화했는지, 그리고 실제로 전환하면서 고려했던 요소와 구체적인 경험을 함께 공유하고자 합니다.
자그마한 노하우지만서도 여러분께 도움이 되셨으면 좋겠습니다. 그러면 시작하겠습니다.
Amazon S3 비용 절약하기
왜 비용이 많이 나오는가?
인프런은 수많은 강의 영상을 Amazon S3 버킷에 보관하고 있습니다. 강사님께서 업로드한 원본 영상과, 이를 학습자 분들께서 재생하기에 최적의 품질과 크기로 인코딩한 버전을 모두 보관합니다.
VOD 서비스의 경우 클라이언트의 네트워크 사양에 적합한 영상을 서빙할 수 있도록 동영상을 해상도별로 인코딩합니다. 주로 540p, 720p, 1080p 등 다양한 해상도에 맞게 영상을 준비해야 합니다.
오디오 트랙도 별도로 관리하고 있습니다. 간혹 볼륨이 불안정한 영상의 경우 시청할 때 큰 불편을 초래하고는 합니다. 이용자 편의를 위해 모든 오디오 트랙에 대해 볼륨 정규화(volume normalization) 단계를 VOD 파이프라인에서 거치고 있습니다.
최근에는 재생 편의를 위해 각 구간별 프레임을 미리 볼 수 있는 썸네일 스프라이트 이미지도 VOD 파이프라인에서 처리하면서 기존 대비 훨씬 많은 객체를 관리하게 되었습니다.
VOD 파이프라인에서 처리하는 데이터와 지원하는 기능이 많아질수록, 여러 단계를 거치다 보니 각 단계에서 수 많은 파일이 생성됩니다. CDN 액세스 로그와 같이 대량의 로그 또한 S3 버킷에 꾸준히 적재되고 있어서, 날이 갈 수록 S3 보관 비용이 눈에 띄게 증가했습니다.
사용자 경험에 큰 영향을 주지 않으면서도 S3 보관 비용을 줄일 수 있는 방법을 모색하게 되었습니다.
S3 스토리지 클래스
Amazon S3는 여러 종류의 스토리지 클래스를 지원합니다. 보관 비용과 조회 시간을 트레이드 오프할 수 있는 옵션을 제공하는 거죠. 객체를 빠르게 조회하려면 높은 보관 비용을 지불해야 합니다. 반면 자주 접근하지 않는 파일이라면 싸게 보관하고 느린 조회 소요시간을 감수하는 겁니다.
쉽게 말해서 하드 디스크보다 보관 비용이 저렴한 자기 테이프(magnetic tape)로 비유해볼 수 있겠습니다. 보관 비용이 저렴한 대신 읽고 쓰는 시간이 오래 걸리는 구조이죠.
주로 자주 사용하는 스토리지 클래스는 다음과 같습니다.
Standard: 일반적인 데이터, 즉시 접근 필요Intelligent-Tiering: 접근 패턴에 따라 자동 최적화Glacier: 장기 보관, 복구(Retrieved) 시간 필요 (분~시간)Deep Archive: 가장 저렴, 복구(Retrieved) 시간 매우 긺 (시간~일)
객체의 특성에 따라서 4~5년 넘는 기간동안 보관해야 하고 접근을 몇 년에 한 번 꼴로 해야 하는, 로그 소산 백업 등에는 Glacier 혹은 Deep Archive 클래스도 고려해보면 좋습니다.
(데이터 복원에 시간과 비용이 상당히 소요되므로 트레이드 오프를 잘 계산해야 합니다. 객체에 대해 사용자 접근이 필요한 사용 사례에선 이어서 소개해 드릴
Intelligent-Tiering이면 충분합니다.)
몇 밀리 초 안에 응답해야 하는 정적 파일을 S3에서 CDN을 거쳐 서빙하거나, 로그 파일의 소산 백업을 위한 아카이빙 등 여러 사용 사례에 맞게 스토리지 클래스를 전환하면 비용을 절약할 수 있습니다.
그러나 이를 업로드 시점에 하나 하나 관리하기에는 쉽지 않을 것입니다. 그래서 주로 Lifecycle configuration을 이용해서 구성하곤 합니다.
S3 Lifecycle configuration
Amazon S3 서비스는 객체 관리를 자동화할 수 있도록 Lifecycle configuration이라는 기능을 제공합니다.
- 객체 업로드 시점으로부터 n일 경과시 스토리지 클래스 변경
- 이전 버전 객체의 스토리지 클래스 변경
- n일 경과시 객체 만료(expire), 즉 영구삭제
등 다양한 기능을 제공하는데요, 객체 이름의 접두사(prefix)나 태그, 파일 크기를 가지고 필터도 설정할 수 있습니다.
잘 응용하면 업로드 후 30일 경과 시점부터는 A, 90일 경과 시점부터는 B 스토리지 클래스로 자동 전환되도록 구성할 수 있습니다.
정말 유용한 기능이지만,
- 객체의 스토리지 클래스를 변경할 때에는 API 호출 비용이 부과됩니다.
- 객체 생성 일자 단위로만 클래스를 변경할 수 있어서, 자주 접근하는
hot, 그리고 가끔 접근하는cold객체를 서로 구분하여 관리하기 어렵습니다.
어려운 이유
모든 객체의 사용 패턴을 알고 있고, 변화가 적은 경우에는 직접 Lifecycle rule을 설정하는 것이 좋을 것입니다. 그러나 실제 서비스를 운영하다보면, 객체 접근 패턴은 예측이 매우 어렵습니다.
몇몇 스토리지 클래스에서는 최소 보관 기간을 두는데요, 이 기간이 지나기 전에 GetObject를 호출해서 해당 객체를 조회하면 추가 비용이 발생하게 됩니다. (GB 당 복구(Retrieval) 비용 부과)
또한 30일, 90일 경과 등 구간별로 스토리지 클래스를 전환할 때마다 추가 비용이 발생하게 됩니다.
- Intelligent-Tiering 클래스로 Lifecycle 전환시 1000개 요청당 $0.01
- Glacier Instant Retrieval 클래스로 Lifecycle 전환시 1000개 요청당 $0.02
인프랩 DevOps 팀에서도 이러한 이유로 인해서 S3 비용 최적화는 쉽게 엄두를 내지 못하고 있었습니다. 그러나 최근 스토리지 비용이 Top 10에 들 정도로 부담이 커지게 되었습니다.
이러한 와중 Intelligent-Tiering 스토리지 클래스가 전환 비용도 받지 않고,
복잡한 설정도 필요 없이 자동으로 스토리지 보관 비용을 절감해준다는 것을 알게 되어 바로 도입을 결정하게 되었습니다.
S3 Intelligent-Tiering
쉽고 명확하게 전달드리겠습니다. 만약 아래 조건을 충족한다면 바로 S3 Intelligent-Tiering 을 도입해보시는 걸 적극적으로 권해드리고 싶습니다.
- 버킷에 주로 보관하는 객체의 크기가 대부분 128KB를 넘는 편이다. (Intelligent Tiering의 티어 전환은 128KB를 초과하는 파일에만 적용되기 때문입니다.)
- 콜드 스토리지임에도 간헐적으로 랜덤 액세스가 있어서 모든 객체가 수 밀리 초 안에 조회가 가능해야 한다.
- S3 버킷을 접근하는 별도의 캐싱 계층이 있다.
(CDN-CloudFront/Cloudflare, WebServer-Nginx/Caddy 등) - 객체가 너무 많아서 어떤 패턴으로 접근하는지 모두 파악이 어렵다.
요청이 특정 객체로 쏠리는 편이라, 객체 단위로 스토리지 클래스 관리가 필요하다.
Intelligent-Tiering은 파일 개수 만큼 관리 요금만 지불하면 자동으로 스토리지 클래스를 관리, 보관 비용을 절약해줍니다. 복잡한 lifecycle rule을 설정하지 않아도 됩니다. 클래스 전환 비용이나 복구(retrieval) 비용을 부과하지 않습니다. (!!)
기본적으로 업로드 시점으로부터 30일간 접근이 없으면 IA 티어로, 90일간 접근이 없는 경우 AIA 티어로 알아서 전환해 줍니다. (이 글에서는 Intelligent-Tiering Archive configurations
에서 설정할 수 있는 Deep Archive Access 티어는 다루지 않겠습니다.)
-
IA- Infrequent Access
- 자주 접근하지 않는 티어
- 밀리초 단위의 액세스
- $0.0138 per GB (in Seoul)
- Standard 대비 44.8% 저렴
-
AIA- Archive Instant Access
- 즉시 접근 가능한 아카이빙 티어
- 밀리초 단위의 액세스
- $0.005 per GB (in Seoul)
- Standard 대비 80% 저렴
IA, AIA 티어는 Standard 클래스 대비 보관비용이 각각 44.8%, 80%나 저렴합니다! 그럼에도 불구하고 이어서 소개해 드릴 전환 비용이나 객체 개수에 따른 관리 비용도 부과되니 잘 계산해야 합니다.
Lifecycle rule로 직접 스토리지 클래스를 티어링하는 경우와 비교해 볼까요? 자주 사용하는 객체도 동일한 정책으로 클래스가 다운그레이드 되어서 조회 및 복구 비용이 만만치 않게 발생하게 됩니다. 랜덤 액세스 요구사항이 있다면 적용하기 어렵습니다.
그렇다면 우리같은 고객에게 Intelligent-Tiering 같은 기능을 제공하면 Amazon은 뭐가 남을까요? 그래서인지 객체 모니터링과 티어 관리 자동화를 명목으로 1000개의 객체 당 $0.0025 요금을 받습니다. 이를 지불한다고 해도 절약하는 스토리지 비용이 훨씬 큽니다.
스토리지 클래스는 객체 업로드(PutObject)시에 지정할 수도 있고, lifecycle rule 설정으로 변경할 수도 있습니다. Lifecycle rule로 지정하면 복잡한 절차 없이 버킷의 모든 객체를 한 번에 전환할 수 있어 편리합니다.
새로운 파일을 업로드한다면 다음과 같이 PutObject API를 실행하여 전환 비용을 절약할 수 있겠습니다. 미리 스토리지 클래스를 지정해두면 전환할 필요가 없으니까 말입니다.
import boto3
# 파일 업로드 시 Intelligent-Tiering 스토리지 클래스 지정
s3_client = boto3.client('s3')
s3_client.put_object(
Bucket='YOUR_BUCKET_NAME',
Key='video.mp4',
Body=open('/tmp/video.mp4', 'rb'),
StorageClass='INTELLIGENT_TIERING'
)그런데 만약 S3 버킷에 수십에서 수백만, 아니 수백억 개에 달하는 객체가 이미 있다면 어떻게 해야 할까요? 스토리지 클래스를 전환하는 데에 발생하는 비용은 부담되지 않을까요?
한 번에 모든 객체를 Intelligent-Tiering 클래스로 전환할 필요는 없습니다.
객체 크기가 크고 덜 접근하는 버킷부터 prefix나 객체 크기 조건으로 Lifecycle rule을 지정하면 됩니다. 업로드 즉시 조건에 맞는 객체는 Intelligent-Tiering 스토리지 클래스로 전환하도록 구성할 수 있습니다.
도입 비용
Intelligent-Tiering을 도입하게 되면 크게 두 가지 비용이 추가되게 됩니다.
-
객체 관리 비용
1000개의 객체 당 $0.0025 요금을 받습니다.
-
(Lifecycle 설정시) Lifecycle 전환 요청 비용 (Lifecycle transition requests)
1000개의 객체 당 $0.01 요금을 받습니다.
(Pricing 페이지의Requests & data retrievals항목에 기재되어 있습니다.)
Lifecycle 전환 요청 비용은 규칙으로 기존 객체를 전환할 때만 요청 비용이 발생하며, 이후 Intelligent Tiering 클래스 내 단계 이동에는 추가 비용이 없습니다.
만이해를 돕기 위해 같이 한 번 계산해 보겠습니다.
약 10만 개의 객체가 있는 버킷이 있고, 현재 모두 Standard 클래스로 보관하고 있다면, Intelligent-Tiering 클래스로 전환하는데 발생하는 비용은 다음과 같습니다.
(라이프사이클 전환 비용) = (객체 수) / 1000 * $0.01
(10만개 객체가 있는 버킷의 I.T. 전환 비용) = 100_000 / 1000 * $0.01 = $1전환 비용은 객체의 갯수에 따르기 때문에, 객체 수가 적으면서 파일 크기가 큰 경우 유리합니다.
위에서 여러분의 사용 패턴에 맞는지 확인해야 한다고 말씀드렸는데요, 그 이유이기도 합니다.
버킷의 종류에 따라서 도입 효과가 극명히 달라지기 때문입니다.
그래서 처음부터 모든 버킷에 적용하기보다는 조금씩 적용하면서 비용을 모니터링하는 것이 좋겠습니다.
CopyObject를 직접 호출하는 방법
여기서 Intelligent Tiering 도입시 라이프사이클 전환 비용을 절약하는 방법이 더 있습니다.
Lifecycle 규칙을 설정하면 S3가 내부적으로 객체를 새로운 스토리지 클래스로 이동시키는데, 이 과정에서 “전환 요청”이 발생합니다.
이 작업은 사실상 CopyObject API를 이용한 내부 복사와 유사한 동작으로 수행되며, 따라서 동일한 효과를 직접 CopyObject 방식으로 구현할 수도 있습니다.
이 경우 Lifecycle 전환 요청 대비 약 2.2배 저렴하게 처리됩니다.
- ListObjects: 1000개당 $0.005 (전체 목록 조회 시 몇 번만 호출)
- CopyObject: 1000개당 $0.0045 (객체 수만큼 호출)
또한 같은 버킷 내에서의 CopyObject는 데이터 전송 요금(Data Transfer Cost)이 발생하지 않습니다. S3 요금 구조상, 인터넷 혹은 리전 간 전송만 요금이 청구되고, 같은 버킷 또는 같은 리전 내 전송은 무료입니다.
즉, CopyObject 방식은 트래픽 요금이 0이고, Lifecycle 전환보다 요청 단가가 낮아 총비용이 더 저렴합니다.
따라서 객체 수가 많아 전환 비용이 부담되는 경우 ListObjects → CopyObject 방식으로 수동 전환을 수행하는 방법도 충분히 고려할 만합니다.
이 부분도 일종의 트레이드 오프이므로 관리하는 객체 수량을 한 번 잘 고려해보시길 바랍니다.
진행 및 성과
그래서 저희는 전환비용을 미리 객체 개수를 기반으로 계산해봤습니다. 앞으로 보관 비용을 획기적으로 줄일 수 있다고 예상하고 진행한 일이었습니다. 90일 접근 안 하는 객체가 과반수를 넘는 패턴이라, 최대 80%까지 보관 비용을 줄일 수 있으니까요.
아래 과정을 따라 대부분의 객체를 Intelligent Tiering으로 적용했습니다.
-
신규로 업로드 되는 객체부터 PutObject 호출시 Intelligent-tiering을 적용했습니다. 스토리지 클래스 전환 비용을 절약하기 위해서였습니다.
-
원본 영상을 저장하는 아카이브 성격의 버킷은 부담없이 일괄 적용할 수 있었기에 CopyObject로 전체 과정을 실행했습니다.
-
인코딩 결과물을 저장하는 버킷은 작은 파일이 많아 Lifecycle rule을 사용해 파일 크기별로 점진적으로 적용하였습니다. 스토리지 클래스 전환 API 호출 비용을 모니터링하면서 진행했습니다.
90일 연속으로 객체에 액세스하지 않은 객체는 가장 낮은 계층인 Archive Instant Access 계층으로 이동합니다. 따라서 적용 후 3~4달이 지나, 자주 접근되지 않은 객체의 비용이 감소하여 38%의 비용을 절감할 수 있었습니다.
S3 Inventory 활용
모든 경우가 저희와 같지는 않겠지요. 여러분의 사용 사례를 잘 파악하시어, 객체 개수가 엄청 많고 조회 빈도도 매우 높은지, 전환 비용은 얼마나 되는지, 객체 관리 비용은 어떻게 되는지 잘 계산해보셔야 하겠습니다.
대략적인 객체 수와 버킷 크기는 웹 콘솔에서도 쉽게 확인하실 수 있습니다. Metrics 탭에 Total bucket size, Total number of objects 지표를 보시면 됩니다.
그런데 만약 S3 버킷에 크고 작은 객체가 뒤섞여 있다면 어떻게 해야 할까요? 이 때에는 S3 Inventory 를 사용하면 됩니다.
S3 Inventory는 버킷 내 모든 객체의 메타데이터를 정기적으로 리포트 형태로 생성해주는 기능입니다. 객체 키, 크기, 마지막 수정일, 스토리지 클래스 등의 정보를 CSV, ORC, Parquet 형식으로 제공합니다.
버킷의 Management 탭에서 Inventory configurations 를 설정하면, 매일 또는 매주 단위로 리포트가 자동 생성됩니다. 생성된 리포트는 Athena를 이용해서 SQL 쿼리로 분석할 수 있습니다.
예를 들어 다음과 같은 쿼리로 128KB를 초과하는 파일의 개수를 확인할 수 있습니다.
SELECT
COUNT(*) as object_count,
SUM(size) / 1024 / 1024 / 1024 as total_size_gb
FROM s3_inventory_table
WHERE size > 128 * 1024;인프랩에서도 도입 전에 먼저 128kB를 초과하는 객체는 몇 개인지, 예상되는 전환 비용은 얼마인지를 사전에 산정하고 작업을 진행했습니다. 덕분에 예상되는 금액 안에서 매끄럽게 전환할 수 있었습니다.
만약 객체 보관 패턴을 잘 알기 어려운 경우라면 S3 Inventory 도 활용해보시기를 권해 드립니다.
이미지 CDN 트래픽 최적화
왜 비용이 많이 나오는가?
영상 보관 뿐만 아니라, 서비스 곳곳에서 사용되는 이미지의 CDN 응답 트래픽도 최적화의 대상이었습니다.
인프런의 메인 페이지에서는 수십 개의 강의 썸네일 이미지를 노출하고 있습니다. 강의 상세 페이지, 질문/답변 페이지, 검색 결과 페이지 등 서비스의 거의 모든 곳에서 강의 썸네일을 보여주고 있습니다.
이미지가 많을수록 페이지를 로드하는 데 시간이 오래 걸립니다. 사용자가 처음 접속했을 때 다운로드해야 하는 용량이 크면 페이지가 느리게 느껴지고, 이는 곧 사용자 경험 저하로 이어집니다.
비용 측면에서도 부담이었습니다. CDN 트래픽이 늘어날수록 비용도 함께 증가하는데, 이미지를 많이 사용하는 서비스 특성상 CDN 응답 트래픽 비용이 만만치 않았습니다.
이처럼 이미지를 다수 사용하는 서비스에선, 이미지 최적화를 통해 CDN 트래픽 비용도 줄이고 사용자 경험도 개선할 수 있습니다.
Lambda@Edge 이미지 리사이저
목록에서는 작은 이미지가 필요하고, 상세 페이지에서는 큰 이미지가 필요합니다. 원본 이미지를 그대로 응답하는 대신 용도에 맞는 크기, 최적화된 포맷으로 변환한다면 트래픽을 줄일 수 있을 것입니다. 이렇게 상황에 맞는 리사이징과 변환을 적용하기 위한 변환기가 필요합니다.
CloudFront를 사용하고 있다면, AWS에서 공유하는 샘플 코드를 기반으로 Lambda@Edge 기반의 on-the-fly 이미지 리사이저 & 포맷 변환기를 쉽게 구성할 수 있습니다.
Lambda@Edge
그렇다면 Lambda@Edge란 무엇일까요? Lambda@Edge는 CloudFront의 엣지 로케이션에서 Lambda 함수를 실행할 수 있게 해주는 서비스입니다. 사용자와 가까운 위치에서 코드를 실행하므로 지연 시간을 줄일 수 있습니다.
Lambda@Edge 함수는 CloudFront의 4가지 이벤트에 연결할 수 있는데요.
Viewer Request → Origin Request → Origin Response → Viewer Response
↑ ↓
사용자 사용자Origin Response를 사용하면 오리진(S3)에서 원본 이미지를 받은 직후 바로 처리하게 됩니다. 따라서 캐시 미스일 때만 Lambda 함수가 실행되고, 변환된 이미지가 CloudFront에 캐싱됩니다. 이후 동일한 요청은 캐시에서 즉시 제공되므로 Lambda 함수가 실행되지 않습니다.
이미지 리사이징에 Origin Response Lambda@Edge를 사용하면 S3에 모든 크기의 이미지를 미리 저장할 필요 없이 요청 시점에 필요한 크기로 쉽게 변환하여 제공할 수 있습니다.
Lambda@Edge는 요청 수와 실행 시간으로 과금됩니다.
- 요청 수: 백만 건당 $0.60 (서울 리전 기준)
- 실행 시간: 128MB 메모리 기준 1ms당 $0.00000625125
Lambda@Edge는 CloudFront 캐시와 결합되므로 캐시 히트율이 높을수록 실제 Lambda 실행 비용은 줄어듭니다. 예를 들어 캐시 히트율이 90%라면 Lambda 비용은 10%만 발생합니다. S3에 모든 크기의 이미지를 미리 생성해서 저장하는 것보다 효율적입니다.
AVIF 변환 추가
AWS 샘플 코드에서는 JPG와 WebP만 지원하지만, CDN 트래픽을 더 줄일 방법을 찾다가 AVIF 포맷에 주목하게 되었습니다.
AVIF(AV1 Image File Format)는 동영상 코덱 AV1을 기반으로 만들어진 이미지 포맷입니다. WebP보다 약 20~30% 더 작은 파일 크기로 동일한 품질을 제공할 수 있습니다.
실제로 인프런 강의 썸네일 이미지를 변환해본 결과는 아래와 같았습니다.
이미지 유형에 따라 변환 용량은 달라질 수 있습니다:
| 포맷 | 파일 크기 | 절감율 |
|---|---|---|
| 원본 JPG | 100 KB | - |
| WebP | 68 KB | 32% |
| AVIF | 55 KB | 45% |
테스트 결과에서도 AVIF 포맷의 압축률이 가장 좋은 것을 확인할 수 있었습니다.
다행히 이미지 리사이저가 사용하는 Sharp 라이브러리는 AVIF를 지원하고 있었습니다. Sharp는 내부적으로 libvips를 사용하는데, libvips v8.9.0부터 libheif를 통해 AVIF 인코딩을 지원합니다.
따라서 적은 코드 작업으로 최적화를 적용할 수 있었습니다. Lambda@Edge 환경에서도 문제없이 동작하는지 확인한 후 허용 포맷에 추가했습니다.
const allowedFormats = ['jpg', 'jpeg', 'png', 'webp', 'avif'];
...
sharp(imageBuffer)
.resize({width})
.toFormat(format, { quality: 80 });이제 ?w=960&f=avif 형태로 요청하면 AVIF 포맷으로 변환된 이미지가 반환됩니다.
다만 AVIF는 WebP보다 인코딩 시간이 1.5배 가량 오래 걸립니다. 이미지 특성과 크기에 따라 차이가 있지만, 일반적으로 더 많은 CPU 연산이 필요합니다. 하지만 CloudFront 캐싱을 통해 최초 1회만 변환하면 되므로 큰 문제가 되지 않았습니다.
브라우저 지원
AVIF는 Chrome 85+(2020년), Safari 16+(2022년)부터 지원되기 시작했습니다. 2025년 현재 글로벌 브라우저 점유율 기준 약 90% 이상이 AVIF를 소프트웨어적으로 지원하고 있습니다.
하지만 하드웨어 디코딩 지원은 제한적입니다. Apple M3+ 등 최신 칩셋에서만 하드웨어 가속을 지원하며, 그 외 기기에서는 CPU로 디코딩하므로 배터리 소모가 클 수 있습니다. 특히 저사양 모바일 기기에서는 디코딩 시간이 길어질 수 있습니다.
이런 이유로 프론트엔드에서 <picture> 태그로 폴백을 구성하는 것이 중요합니다.
<picture>
<source srcset="inflearn-logo.jpg?f=avif" type="image/avif">
<source srcset="inflearn-logo.jpg?f=webp" type="image/webp">
<img src="inflearn-logo.jpg" alt="Description" width="360" height="240">
</picture>브라우저는 위에서부터 지원 가능한 포맷을 선택합니다. AVIF를 지원하면 AVIF를, 지원하지 않으면 WebP를, 둘 다 지원하지 않으면 원본 JPEG를 요청하게 됩니다. 이렇게 모든 브라우저에서 호환성을 유지하면서도 최신 브라우저에서는 최적화된 포맷의 이점을 누릴 수 있습니다.
GIF 트래픽 최적화
한편, 강의 썸네일이나 커뮤니티 게시글에선 애니메이션 GIF가 업로드되기도 하는데요. GIF는 256색으로 제한되고 압축 효율이 낮아 파일 크기가 큰 편입니다. 특히 고화질 영상을 GIF로 변환하면 용량이 급격히 증가합니다.
움직이는 이미지를 최적화하기 위해 여러 포맷으로 변환을 테스트해봤습니다. FFmpeg를 사용해 실제 GIF 파일을 변환한 결과는 다음과 같았습니다:
| 포맷 | 파일 크기 | 절감율 |
|---|---|---|
| 원본 GIF | 1000 KB | - |
| WebP (animated) | 860 KB | 14% |
| AVIF (animated) | 281 KB | 72% |
| MP4 (H.264) | 322 KB | 68% |
이미지와 비슷하게 AVIF가 가장 압축률이 좋았고, MP4는 AVIF보다 약간 큰 수준이었습니다.
Sharp, Lambda@Edge 고려 사항
하지만 적용하려고 보니 몇 가지 어려움이 있었습니다.
Sharp에서 애니메이션 변환을 지원하는 포맷은 WebP 뿐입니다. animated AVIF 변환은 지원하지 않고 GIF의 첫 프레임만 추출할 수 있습니다. AVIF 애니메이션이 비교적 최근에 표준화되었고, Sharp가 의존하는 libheif 라이브러리의 지원이 제한적이어서 발생한 상황입니다. (문서, 관련 이슈)
게다가 Lambda@Edge는 엣지 로케이션에서 실행되는 환경이라 제약이 많습니다.
- 패키지 크기: 최대 50MB
- 실행 시간: Origin Response는 최대 30초
- 메모리: 최대 3008MB
- 응답 크기: Origin Response는 최대 1MB
MP4로 변환하려면 FFmpeg가 필요한데, 바이너리 크기가 60-80MB에 달해 패키지 크기 제한(50MB)을 초과합니다.
다른 포맷으로 변환하더라도 특히 문제가 된 건 응답 크기와 실행 시간 제한이었습니다. 인프런에 업로드되는 GIF 중 일부는 수 MB에 달했고, WebP로 변환하더라도 결과가 1MB를 초과하는 경우가 종종 발생했습니다. 애니메이션 프레임이 많거나 해상도가 높은 GIF는 처리 시간도 오래 걸려 30초 제한을 넘기기도 했습니다.
이런 이유로 모든 GIF를 Lambda@Edge에서 실시간 변환하기엔 무리가 있다고 판단했습니다.
MP4 변환 람다
Lambda@Edge는 실시간 변환에 적합하지 않았으므로, 업로드 시점에 미리 변환해두는 방식을 선택했습니다.
일반 Lambda는 Lambda@Edge와 달리 큰 GIF 파일을 처리하기에 충분한 환경입니다. FFmpeg 같은 큰 바이너리도 AWS에서 제공하는 Lambda Layer를 활용하면 쉽게 사용할 수 있습니다.
구성한 Lambda의 동작 방식은 다음과 같습니다.
- 사용자가 GIF 파일을 S3 버킷에 업로드
- S3가
s3:ObjectCreated:*이벤트 발생 - Lambda 함수가 자동으로 트리거
- Lambda가 GIF를 다운로드하여 FFmpeg로 MP4 변환
- 변환된 파일을
.mp4확장자로 S3에 저장
비동기로 처리되므로 변환이 완료되면 자동으로 최적화된 MP4를 보게 됩니다. 초기 업로드 시 약간의 지연이 있지만, 한 번 변환해두면 모든 사용자가 최적화된 파일을 받을 수 있어 장기적으로 CDN 트래픽을 절감할 수 있습니다.
프론트엔드에서는 MP4가 존재하는 경우 <video> 태그로 표시합니다.
<video muted loop autoPlay playsInline>
<source src={gifUrl} type="video/mp4" />
</video>이 방식은 초기 업로드 시 약간의 지연이 있지만, 한 번 변환해두면 모든 사용자가 최적화된 파일을 받을 수 있어 장기적으로 CDN 트래픽을 크게 절감할 수 있습니다.
단, 상황에 따라 GIF를 작은 썸네일로 보여주는 경우는 위의 Lambda@Edge 리사이저를 사용하고 있습니다. 애니메이션이 필요한지에 따라 적절하게 선택할 수 있겠습니다.
진행 및 성과
이미지 포맷 전환은 프론트엔드와의 긴밀한 협업이 필요한 작업이었습니다. 모든 페이지에 일괄 적용하기보다는, 트래픽이 높은 주요 페이지부터 단계적으로 적용하며 효과를 검증하는 방식으로 진행했습니다.
그중에서도 인프런의 강의 목록 페이지와 랠릿의 공고 목록 페이지처럼 다수의 썸네일 이미지를 동시에 노출하는 페이지에서 가장 큰 개선 효과를 확인했습니다.
이미지 CDN에서는 AVIF 포맷 적용 후 이미지 응답 크기(P90 기준)가 적용 이전 대비 평균적으로 약 15% 감소했으며, 이후에도 전체적으로 낮은 수준이 유지되었습니다. 특히 랠릿에서는 사용자 체감 성능을 나타내는 LCP(Largest Contentful Paint) 지표 또한 명확하게 개선되었습니다.
- 배포 전 2일 평균: LCP 1.16초
- 배포 후 2일 평균: LCP 0.87초
모니터링
최적화 작업을 완료한 후에도 지속적인 모니터링이 중요합니다.
이번 최적화 작업도 평소 페이지 로딩 속도와 CDN 트래픽 추이를 꾸준히 모니터링해온 덕분에 적용할 수 있었습니다. 트래픽이 증가하는 패턴을 발견하고, 어느 부분을 개선하면 좋을지 데이터 기반으로 판단할 수 있었습니다.
여러분들도 모니터링 도구를 적극적으로 활용해, 트래픽 변화나 리소스 병목을 점검하시는 것을 추천드립니다.
DataDog RUM 기능을 사용하고 있다면, 사용자의 RUM Session에서 불러온 리소스 중 오래걸리거나 사이즈가 큰 요청을 쿼리해 대시보드를 구성할 수 있습니다.
CloudFront Log를 Athena로 조회하고 있다면 아래처럼 Grafana Dashboard를 구성할 수 있습니다.
마치며
인프랩의 S3 보관 비용과 CDN 트래픽 최적화 경험을 공유해드렸습니다. 내용을 정리하자면 다음과 같습니다.
S3 Intelligent-Tiering은 복잡한 설정 없이 자동으로 스토리지 클래스를 관리해주는 기능입니다. 객체 수와 크기 패턴만 잘 파악하면 비용 절감 효과를 쉽게 예측할 수 있습니다. 저희는 전환 비용을 미리 계산하고 버킷별로 순차적으로 적용하면서 38%의 비용을 절감할 수 있었습니다.
이미지 CDN 트래픽 최적화는 Lambda@Edge 리사이저에 AVIF 포맷 지원을 추가하는 것만으로도 큰 효과를 볼 수 있었습니다. 프론트엔드와 협업으로 주요 페이지부터 적용했고, 이미지 요청당 응답 크기 P90이 15% 이상 개선되었습니다. GIF를 MP4로 변환하는 Lambda도 함께 적용해서 애니메이션 콘텐츠의 용량을 크게 줄일 수 있었습니다.
처음에는 복잡해 보이고 리스크가 클 것 같았지만, 작은 부분부터 시작해서 점진적으로 확대하니 부담 없이 진행할 수 있었습니다.
물론 이 글에서 소개한 방법이 모든 상황에 정답은 아닐 수 있습니다. 현재 규모와 사용 패턴에 맞다고 판단해서 적용한 사례였습니다. 여러분의 객체 크기 분포나 접근 패턴, 트래픽 규모에 따라서는 다른 방식이 더 적합할 수 있으니, 상황에 맞게 선택하시길 권해드립니다.
이 글이 여러분의 비용 최적화에 조금이나마 도움이 되었으면 좋겠습니다. 감사합니다.

