effidevFlutter · Cloudflare 엣지 · 클라우드 비용 최적화
한국어

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

AWS S3에서 Cloudflare R2로 마이그레이션하여 이그레스 비용 절감하기

대용량 미디어 파일, 비디오 스트리밍, 대규모 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 교체만으로 즉시 사용할 수 있습니다.