AWS S3에서 Cloudflare R2 마이그레이션: 비용 99% 절감

대용량 미디어 파일, 비디오 스트리밍, 대규모 AI 데이터셋을 클라우드에 저장해 운영하는 기업들에게 AWS S3의 청구서는 매달 공포의 대상이다. 스토리지 보관 비용 자체보다 사용자가 파일을 다운로드하거나 CDN/외부 서버로 데이터를 전송할 때 발생하는 **이그레스 비용(Data Transfer Out, GB당 $0.09)**이 전체 클라우드 비용의 70~90%를 차지하기 때문이다.
Cloudflare의 오브젝트 스토리지 Cloudflare R2는 시장의 판도를 완전히 바꿨다. R2는 **이그레스 비용(대역폭 전송료)을 100% 무료($0/GB)**로 제공하며, 스토리지 저장 비용 역시 S3 Standard 대비 약 35% 저렴하다($0.015/GB-month).
더욱이 R2는 기존 S3 API와 100% 호환되므로, 코드 수정 없이 엔드포인트 URL 변경만으로 즉시 교체할 수 있다. 서비스 중단 없이 테라바이트(TB) 단위의 데이터를 이전할 수 있는 게으른 마이그레이션(Lazy Migration / Super Slurp) 파이프라인도 내장되어 있다.
이 글은 S3와 R2의 비용 구조 상세 비교, AWS SDK 환경에서 R2 엔드포인트 전환법, 무중단 게으른 마이그레이션 아키텍처, 그리고 Cloudflare Workers + R2를 결합한 글로벌 에지 이미지/미디어 최적화 파이프라인을 실전 세팅 코드로 다룬다.
핵심 요약
- 이그레스 비용 $0/GB: 인터넷이나 외부 클라우드로 데이터를 아무리 많이 전송해도 대역폭 과금이 0원이다. 미디어 delivery, AI 데이터셋 공유 시 최고의 선택이다.
- 스토리지 요금 35% 절감: S3 Standard $0.023/GB 대비 R2는 $0.015/GB로 기본 보관료부터 저렴하다.
- S3 API 100% 호환: AWS SDK(
@aws-sdk/client-s3)를 그대로 사용하며,endpoint설정만 R2 주소로 변경하면 즉시 작동한다.- 게으른 마이그레이션 (Lazy Migration): 서비스 다운타임 없이 사용자가 요청하는 시점에 S3에서 R2로 미싱 개체를 자동으로 동기화하여 수 초 만에 마이그레이션을 시작할 수 있다.
- Workers + R2 에지 바인딩: Cloudflare Workers와 네이티브 바인딩하면 네트워크 홉(Hop) 오버헤드 0ms로 스토리지 데이터를 에지에서 즉시 렌더링하고 이미지 변환을 수행할 수 있다.
1. AWS S3 vs Cloudflare R2 비용 비교: 실제 청구서 시뮬레이션
100TB 데이터 보관 및 월 500TB 이그레스(전송량)가 발생하는 미디어/AI 서비스의 2026년 8월 기준 월간 시뮬레이션 결과다. AWS S3 요금표와 Cloudflare R2 공식 문서를 기반으로 계산했다.
| 항목 | AWS S3 Standard | Cloudflare R2 | 비용 차이 |
|---|---|---|---|
| 스토리지 보관료 (100TB) | $2,300 ($0.023/GB) | $1,500 ($0.015/GB) | $800 절감 (35%) |
| 이그레스 전송료 (500TB) | $44,910 ($0.089/GB) | $0 ($0/GB) | $44,910 절감 (100%) |
| Class A 연산 (쓰기 1천만 회) | $50 ($5.00/100만 회) | $45 ($4.50/100만 회) | $5 절감 |
| Class B 연산 (읽기 1억 회) | $40 ($0.40/100만 회) | $36 ($0.36/100만 회) | $4 절감 |
| 총 월간 클라우드 청구액 | $47,300 | $1,581 | 월 $45,719 절감 (96.6% 절감!) |
이그레스 비중이 높은 서비스일수록 비용 절감률은 **95%~99%**에 육박한다. 이그레스 비용 때문에 AWS 네트워크 갇힘(Vendor Lock-in) 현상을 겪던 기업들이 R2로 이전하는 가장 강력한 이유다.
2. 코드 수정 0: AWS SDK에서 R2 엔드포인트 연결하기
R2는 S3 호환 API를 지원하므로, 기존 Node.js, Python, Go의 AWS SDK 코드를 재사용할 수 있다.
// 변경 전 (AWS S3)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
const s3Client = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
// 변경 후 (Cloudflare R2) — endpoint만 교체하면 100% 동작!
const r2Client = new S3Client({
region: 'auto', // R2는 글로벌 단일 리전으로 auto 지정
endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
주의할 점: R2는 AWS S3의 특정 리전 제한(us-east-1 등)이 없으며, Cloudflare의 300개 이상 에지 노드에서 요청과 가장 가까운 곳으로 자동 글로벌 최적 라우팅을 제공한다.
3. 서비스 무중단 ‘게으른 마이그레이션 (Lazy Migration)’ 패턴
테라바이트 단위의 S3 데이터를 한 번에 R2로 옮기는 “Big Bang” 마이그레이션은 시간이 오래 걸리고 서비스 다운타임 위험이 있다. 게으른 마이그레이션(Lazy Migration) 패턴을 쓰면 서비스 중단 없이 요청이 들어올 때 실시간으로 데이터를 R2로 채운다.
[클라이언트 요청] ──► [Cloudflare Workers]
│
├─► 1. R2 오브젝트 조회 (Cache Hit) ──► 파일 즉시 반환
│
└─► 2. 미싱 시 (Cache Miss)
│
├─► S3 오리진에서 파일 다운로드
├─► R2 버킷에 저장 (비동기 비동기 동기화)
└─► 클라이언트에 파일 반환
게으른 마이그레이션 Workers 실전 코드
export interface Env {
MY_R2_BUCKET: R2Bucket;
S3_ORIGIN_URL: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1);
// 1. R2 버킷에서 파일 조회
let object = await env.MY_R2_BUCKET.get(key);
if (object === null) {
// 2. R2에 파일이 없으면 기존 S3 오리진에서 읽어옴 (Lazy Fetch)
const s3Response = await fetch(`${env.S3_ORIGIN_URL}/${key}`);
if (!s3Response.ok) {
return new Response('File Not Found', { status: 404 });
}
const bodyBuffer = await s3Response.arrayBuffer();
// 3. S3에서 받아온 파일을 R2 버킷에 비동기 백그라운드 저장
env.MY_R2_BUCKET.put(key, bodyBuffer, {
httpMetadata: s3Response.headers,
});
// 4. 클라이언트에 즉시 응답 반환
return new Response(bodyBuffer, {
headers: s3Response.headers,
});
}
// R2 히트 시 즉시 응답 (이그레스 요금 $0)
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
return new Response(object.body, { headers });
},
};
이 패턴을 적용하면 사용자가 실제로 접근하는 활성 데이터부터 순차적으로 R2로 이전되며, 마이그레이션 당일부터 S3 이그레스 비용이 급감한다.
4. R2 + Cloudflare Image Resizing 동적 이미지 파이프라인
R2에 저장된 원본 고화질 이미지를 Cloudflare Workers와 결합하면 Next.js의 Image Optimization이나 Cloudinary 같은 비싼 외부 서비스를 대체할 수 있다. Cloudflare Images 요금 비교 가이드에서 다룬 실전 응용이다.
// Workers에서 R2 이미지 실시간 WebP 변환 및 리사이징
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const width = parseInt(url.searchParams.get('w') || '800');
const quality = parseInt(url.searchParams.get('q') || '80');
const key = url.pathname.slice(1);
// Cloudflare Image Resizing 옵션과 R2 오브젝트 연결
const options = {
cf: {
image: {
width,
quality,
format: 'webp',
},
},
};
const imageObject = await env.MY_R2_BUCKET.get(key);
if (!imageObject) return new Response('Not Found', { status: 404 });
return new Response(imageObject.body, {
headers: {
'content-type': 'image/webp',
'cache-control': 'public, max-age=31536000, immutable',
},
});
},
};
AWS Lambda에서 Cloudflare Workers로 이그레스 절감 마이그레이션 가이드와 함께 사용하면, 컴퓨팅과 스토리지 전 영역에서 클라우드 비용을 최소화할 수 있다.
5. R2 Presigned URL을 활용한 대용량 파일 다이렉트 업로드
클라이언트가 서버(Node.js/Next.js)를 거치지 않고 R2 버킷에 직접 비디오나 압축 파일 등 대용량 미디어를 업로드하게 하려면 Presigned URL(서명된 업로드 URL) 패턴을 적용한다.
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const r2Client = new S3Client({
region: 'auto',
endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
});
// 15분간 유효한 다이렉트 R2 업로드 서명 URL 생성
export async function generateUploadUrl(fileName: string, fileType: string) {
const command = new PutObjectCommand({
Bucket: 'my-app-uploads',
Key: `user-files/${fileName}`,
ContentType: fileType,
});
const uploadUrl = await getSignedUrl(r2Client, command, { expiresIn: 900 });
return uploadUrl;
}
이 패턴을 적용하면 서버 인프라 메모리 소모 없이 테라바이트 단위의 파일 업로드를 안전하게 처리할 수 있으며, 이 역시 AWS S3 SDK와 100% 동일한 코드로 작동한다.
6. R2 사용 시 주의사항 및 모범 사례
| 체크리스트 | 설명 및 모범 사례 |
|---|---|
| 커스텀 도메인 연결 | R2 버킷에 커스텀 도메인을 연결하면 Cloudflare CDN Caching이 자동 적용되어 Class B 읽기 연산 비용까지 절감된다. |
| 퍼블릭 버킷 노출 주의 | 버킷 퍼블릭 액세스는 무단 다운로드를 유발할 수 있으므로, Workers 바인딩이나 Signed URL(서명된 URL)을 통해 권한을 제어한다. |
| Class A / Class B 연산 조절 | R2는 이그레스가 무료지만 읽기/쓰기 연산 수에는 소액 과금된다. CDN 캐싱 헤더(max-age=31536000)를 설정해 읽기 연산수를 최소화한다. |
| S3 Object Lock 대체 | S3의 특수 규정 준수 기능(Object Lock 등)이 필요한 경우 법적 요구사항을 확인한다. |
자주 묻는 질문
R2는 정말 이그레스 비용이 완전 무료인가요?
네, 100% 무료입니다. 테라바이트나 페타바이트 단위의 데이터를 외부 인터넷으로 전송하더라도 Cloudflare는 대역폭 요금을 일절 청구하지 않습니다.
기존 S3 버킷의 데이터를 한 번에 대용량 이전하는 도구가 있나요?
Cloudflare 대시보드의 R2 Super Slurp 도구를 사용하면 S3 인증 정보 입력만으로 백그라운드에서 전체 S3 버킷 오브젝트를 R2로 자동 복사할 수 있습니다.
R2는 AWS S3보다 렌더링 레이턴시가 빠른가요?
사용자 위치에 따라 더 빠릅니다. AWS S3는 특정 단일 리전(예: us-east-1)에 데이터가 고정되어 해외 사용자의 레이턴시가 높지만, R2는 Cloudflare의 300개 이상 글로벌 에지 네트워크와 연동되어 사용자와 가장 가까운 에지에서 캐시 및 렌더링이 이루어집니다.
R2를 Next.js / React 프로젝트에서 사용할 수 있나요?
네, AWS SDK S3 커넥터를 사용할 수 있는 모든 프레임워크(Next.js, React, Remix, Astro, Express 등)에서 endpoint 교체만으로 즉시 사용할 수 있습니다.