요약
CloudFront에서 Cloudflare Workers로 CDN을 이전해 월 $1,200에서 $90으로 92%의 비용을 절감했습니다. 연간 약 $13,000의 절감 효과입니다.
개발에 3일, 실제 마이그레이션은 30분이면 충분했습니다. 문제가 생기면 DNS만 끌면 5분 내로 롤백할 수 있도록 설계했습니다.
배포 초기에는 7%의 에러율이 발생했지만, 픽셀 수 기반 분기를 도입해 0.04%까지 낮추는 데 성공했습니다.
배경: 6년간의 CDN 여정
2019년: GCP에서 CloudFront로
데이트팝은 2014년 처음 Google Cloud Platform의 App Engine 서비스를 사용하면서 getServingUrl() API를 적극 사용했습니다. App Engine Images API의 일부로, URL 파라미터만으로 이미지 리사이징이 가능한 편리한 서비스였습니다. 심지어 해당 서비스는 무료로 제공해왔던 서비스로 Google Cloud Platform을 떠나지 못하게 했던 강력한 기능 중 하나였습니다.
# 원본
https://lh3.googleusercontent.com/xxx
# 리사이징 (width=400)
https://lh3.googleusercontent.com/xxx=s400하지만 시간이 흘러 Google은 이 Legacy API의 지원 중단을 예고했고, 새로운 CDN 솔루션이 필요해졌습니다
당시 선택지는 AWS CloudFront, Cloudflare, 그리고 Cloudinary 같은 이미지 전문 서비스가 있었습니다.
많은 고민 끝에 안정성과 당시 Google Cloud Platform에서 AWS로 이전을 하고 있었기에 AWS 생태계와의 통합을 고려해 CloudFront를 선택했습니다.
2025년: CloudFront의 비용 문제
2019년부터 6년간 CloudFront는 안정적으로 동작했지만, 트래픽 증가와 함께 비용도 증가했습니다.
월간 비용 구성 (약 $1,200)
| 항목 | 비용 | 비고 |
|---|---|---|
| CloudFront 대역폭 | ~$1,150 | ~10TB/월 |
| CloudFront 요청 | ~$40 | ~5천만 요청 |
| ECS (이미지 처리) | 기존 EC2 활용 | 추가 비용 없음 |
대역폭 비용이 전체의 95%를 차지했습니다.
Cloudflare의 매력: 왜 이렇게 저렴한가?
Cloudflare를 검토하면서 가장 궁금했던 점은 어떻게 대역폭을 무료로 제공할 수 있는가?였습니다.
Cloudflare의 비즈니스 모델
Cloudflare는 전 세계 12,500개 이상의 네트워크와 직접 피어링을 맺고 있습니다. ISP 입장에서도 인기 콘텐츠를 가까이에 캐싱해두면 자신들의 네트워크 부하가 줄어들기 때문에, 이 피어링은 양쪽 모두에게 이득입니다. Bandwidth Alliance를 통해 AWS, GCP 등 클라우드 제공자와의 대역폭 비용도 상호 면제합니다.
무료 플랜으로 사용자를 확보한 후, Pro/Business/Enterprise 플랜과 Workers, R2, D1 같은 부가 서비스로 수익을 내는 구조입니다. 네트워크 장비도 직접 설계하고 제조해서 비용을 낮추고, 전 세계 인터넷 트래픽의 상당 부분을 처리하는 규모의 경제로 단위 비용을 최소화합니다.
결국 Cloudflare는 대역폭을 여러 상품으로 활용해 생태계를 확장하는 전략을 취하고 있습니다.
리스크 평가: Cloudflare 장애 이력
마이그레이션을 결정하기 직전인 2025년 12월 5일, Cloudflare에서 대규모 장애가 발생했습니다. 분산 시스템 동기화 문제로 API와 DNS 전파가 약 1시간 동안 지연되었습니다.
솔직히 타이밍이 좋지 않았습니다. 하필 마이그레이션 직전에 장애라니. 하지만 오히려 이 장애를 보면서 마이그레이션 결정에 확신이 생겼습니다. Dashboard와 API에 문제가 생겼지만, 엣지 캐싱은 정상 동작했기 때문입니다. CDN의 핵심 기능인 콘텐츠 전송은 영향을 받지 않았다는 점이 중요했습니다.
그럼에도 마이그레이션을 결정한 이유
결국 마이그레이션을 결정한 건 간단한 계산이었습니다. 연간 $13,000 절감 vs 연간 몇 시간의 장애 가능성. 그리고 무엇보다 DNS Proxy만 끄면 5분 내로 CloudFront로 돌아갈 수 있다는 점이 큰 안심이 되었습니다.
또한 98%의 캐시 히트율을 보이는 CDN 특성상, 오리진 서버나 API에 문제가 생기더라도 대부분의 요청은 엣지 캐시에서 처리됩니다. 실제로 12월 5일 장애 때도 캐싱된 콘텐츠는 정상적으로 제공되었습니다.
기술적 구현
아키텍처 설계
핵심 코드: 크기 기반 처리 전략
// image.ts
const MAX_WASM_FILE_SIZE = 3 * 1024 * 1024; // 3MB
export async function processImageWithWasm(
imageBytes: Uint8Array,
params: ImageParams
): Promise<ProcessedImage> {
const quality = params.quality || 85;
let image = PhotonImage.new_from_byteslice(imageBytes);
try {
if (params.width && params.width > 0) {
const targetHeight = Math.round(
(params.width / image.get_width()) * image.get_height()
);
const resized = resize(
image, params.width, targetHeight, SamplingFilter.Lanczos3
);
image.free();
image = resized;
}
if (params.blur && params.blur > 0 && image.get_width() <= 1024) {
gaussian_blur(image, params.blur);
}
return {
data: image.get_bytes_jpeg(quality),
contentType: 'image/jpeg'
};
} finally {
image.free();
}
}폴백 메커니즘
WASM 처리가 메모리/CPU 한계로 실패할 경우, Cloudflare Image Resizing으로 자동 폴백:
// index.ts
if (hasImageParams(params)) {
try {
const processed = await processImageWithWasm(imageBytes, params);
// ... WASM 처리 성공
} catch {
// WASM 실패 시 Cloudflare Image Resizing으로 폴백
const cfResponse = await fetchWithCfImageResizing(originUrl, params);
// ...
}
}왜 WASM인가?
Cloudflare Workers 환경에서 이미지 처리 방법을 찾는 게 생각보다 쉬웠습니다. 일반적으로 많이 사용하는 Sharp는 Native 바이너리라 Workers에서 실행이 불가능하고, Jimp도 Node.js API에 의존해서 사용할 수 없었습니다.
결국 선택지는 두 가지였습니다. Cloudflare가 제공하는 Image Resizing 서비스를 쓰거나, WASM으로 컴파일된 이미지 처리 라이브러리를 사용하는 것입니다.
WASM을 선택한 이유는 명확했습니다. Cloudflare Image Resizing은 요청당 $0.0005가 과금되지만, WASM은 무료입니다. 또한 오리진으로 이미지를 보낼 필요 없이 엣지에서 바로 처리할 수 있고, 리사이징 알고리즘이나 품질 설정도 원하는 대로 조정할 수 있습니다.
그럼에도 Cloudflare Image Resizing을 함께 사용하는 이유
문제는 WASM의 한계입니다. Workers는 128MB 메모리 제한이 있어서 대용량 이미지는 처리할 수 없습니다. 그래서 WASM으로 처리 가능한 이미지는 WASM으로, 나머지는 Cloudflare Image Resizing으로 처리하는 하이브리드 방식을 선택했습니다.
어차피 98%는 캐시에서 처리되고, 캐시 미스 중에서도 대용량 이미지는 소수입니다. 결과적으로 Cloudflare Image Resizing 비용은 월 $50 수준에 불과합니다.
마이그레이션 실행
Zero-Downtime 전환
실제 전환 과정은 간단했습니다. Worker는 이미 배포해둔 상태에서, Cloudflare Dashboard에서 DNS Proxy만 활성화하면 끝입니다.
DNS 기반 전환으로 다운타임 없이 마이그레이션:
# 1. Worker 배포 (사전 완료)
wrangler deploy --env production
# 2. DNS Proxy 활성화 (Cloudflare Dashboard)
# cdn.datepop.co.kr → Proxied (주황색 구름)
# 3. 즉시 확인
curl -sI "https://cdn.datepop.co.kr/image/test.jpg" | grep cf-
# cf-cache-status: HIT
# cf-ray: xxx-ICN모니터링 및 안정화
배포 직후, 예상치 못한 문제가 발생했습니다.
첫 번째 문제: 7% 에러율
마이그레이션 직후 대시보드를 확인하니 exceededMemory 5%, exceededCpu 2%로 총 7%의 에러가 발생하고 있었습니다. 원인은 간단했습니다. 5MB가 넘는 대용량 이미지를 WASM으로 처리하려고 했던 것입니다.
MAX_WASM_FILE_SIZE를 5MB에서 3MB로 낮추자 에러율이 7%에서 0.43%로 떨어졌습니다.
두 번째 문제: 파일 크기의 함정
하지만 0.43%의 exceededMemory가 계속 발생했습니다. 로그를 확인해보니 3MB 이하의 이미지에서도 메모리 초과가 발생하고 있었습니다.
문제의 이미지를 확인해보니 3MB짜리 JPEG였지만 해상도가 8021×5350이었습니다. JPEG는 압축 포맷이라 파일 크기는 작지만, 디코딩하면 픽셀당 4바이트(RGBA)가 필요합니다. 계산해보면 8021 × 5350 × 4 = 약 170MB. Workers의 128MB 메모리 제한을 훌쩍 넘어버립니다.
결국 파일 크기가 아니라 픽셀 수가 중요하다는 걸 깨달았습니다. 이미지 헤더를 파싱해서 픽셀 수를 먼저 확인하고, 4M 픽셀(2000×2000)을 초과하면 WASM 대신 Cloudflare Image Resizing을 사용하도록 수정했습니다.
// 이미지 헤더에서 dimensions 파싱 (JPEG/PNG/WebP)
export function getImageDimensions(bytes: Uint8Array): ImageDimensions | null;
// 4M 픽셀 초과 시 Cloudflare Image Resizing 사용
const MAX_PIXELS_FOR_WASM = 4_000_000; // 2000×2000이 수정 후 에러율이 0.43%에서 0.04%로 떨어졌습니다. 91% 감소입니다.
비용 분석
Before (CloudFront)
| 항목 | 월 비용 |
|---|---|
| CloudFront 대역폭 (~10TB) | ~$1,150 |
| CloudFront 요청 (~50M) | ~$40 |
| 합계 | ~$1,200 |
After (Cloudflare)
| 항목 | 월 비용 |
|---|---|
| Workers (무료 포함) | ~$5 |
| Cloudflare Image Resizing | ~$50 |
| 대역폭 | $0 |
| S3 → Cloudflare Egress | ~$18 |
| 합계 | ~$90 |
절감 효과
- 월간: ~$1,110 (92% 절감)
- 연간: ~$13,000
추후 고려사항: R2 마이그레이션
현재 이미지는 S3에 저장되어 있어서 캐시 미스 시 S3 → Cloudflare로 데이터가 나갈 때 Egress 비용이 발생합니다. 현재 월 약 $18 수준입니다.
Cloudflare R2로 이전하면 이 비용이 $0이 됩니다. 게다가 Workers와 동일한 네트워크에 있어서 레이턴시도 개선될 것으로 예상됩니다. S3 호환 API를 제공해서 마이그레이션도 비교적 쉬울 것 같습니다.
다만 S3에는 CDN 외에도 다른 서비스들이 의존하고 있어서, 전체 아키텍처를 검토한 후에 진행할 계획입니다.
교훈
잘한 점
점진적 전환 전략이 효과적이었습니다. 스테이징에서 충분히 테스트하고, 프로덕션에 배포한 후, DNS만 전환하는 방식으로 리스크를 최소화했습니다. CloudFront를 그대로 유지해두었기 때문에 문제 발생 시 5분 이내 롤백이 가능했습니다.
실시간 로그 모니터링도 큰 도움이 되었습니다. wrangler tail로 에러를 즉시 감지할 수 있었고, 당일에 픽셀 수 기반 분기까지 구현할 수 있었습니다.
개선할 점
대용량/고해상도 이미지에 대한 부하 테스트가 부족했습니다. 스테이징에서는 문제없었지만 프로덕션의 다양한 이미지를 만나면서 에러가 발생했습니다. 또한 초기 비용 예측에서 S3 Egress 비용을 누락했던 것도 아쉬웠습니다.
다른 팀에 권장하는가?
조건부로 Yes입니다.
캐시 히트율이 높은 정적 콘텐츠를 서빙한다면 강력히 추천합니다. 다만 동적 콘텐츠나 실시간 처리가 많다면 추가 검토가 필요합니다. 그리고 Cloudflare의 간헐적인 장애 리스크를 감수할 수 있어야 합니다. 저희처럼 즉시 롤백 전략을 마련해두면 이 리스크는 충분히 관리 가능합니다.
