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円です。メディア配信や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の転送量(Egress)が発生するメディア/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,000万回) | $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利用時の注意事項とベストプラクティス
| チェックリスト | 説明およびベストプラクティス |
|---|---|
| カスタムドメインの接続 | 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を差し替えるだけで即座に使用できます。