Cloudflare R2: object storage S3-compatible, không egress fee - lý do team chúng tôi rời AWS S3

AWS S3 tốt nhưng có một điểm đau kinh điển: **egress fee** - bạn phải trả tiền mỗi khi data rời khỏi S3. Upload miễn phí, nhưng download thì tính tiền. Cloudflare R2 là S3-compatible object storage **không có egress fee**, 10GB free/tháng, và tích hợp native với Workers. Nếu bạn đang lưu file và serve cho user, R2 đáng xem xét nghiêm túc. ---

Tình huống mở đầu

Một dự án chúng tôi handle có S3 bucket lưu ảnh sản phẩm - vài chục nghìn file, tổng ~50GB. Chi phí storage không đáng kể, nhưng data transfer out (egress) mỗi tháng lên đến vài trăm GB vì ảnh được load trực tiếp từ S3 URL. Hóa đơn AWS tháng nào cũng có line item "Data Transfer" khó chịu.

Sau khi migrate sang R2: egress fee = $0. Chi phí giảm đáng kể, API hoàn toàn tương thích S3 nên code gần như không cần sửa.


Bối cảnh: tại sao egress fee tồn tại

AWS, GCP, Azure đều charge egress fee (data transfer out). Lý do kỹ thuật thực sự là chi phí bandwidth của data center - nhưng mức phí (~$0.09/GB với AWS) cao hơn nhiều so với cost thực. Đây là một trong những nguồn revenue lớn và cũng là một dạng vendor lock-in: nếu bạn có nhiều data trong S3, chi phí migrate ra ngoài rất lớn.

Cloudflare R2 không charge egress fee - Cloudflare có thể làm vậy vì họ sở hữu network infrastructure toàn cầu, bandwidth cost thấp hơn và đây là chiến lược cạnh tranh có chủ đích.


R2 free tier và pricing

Free tier Sau free
Storage 10 GB/tháng $0.015/GB/tháng
Class A operations (PUT, POST, LIST) 1M/tháng $4.50/triệu
Class B operations (GET) 10M/tháng $0.36/triệu
Egress (data transfer out) Unlimited $0 - miễn phí

So sánh với S3 Standard:

  • Storage: $0.023/GB (R2 rẻ hơn 35%)
  • GET: $0.0004/1000 ops (tương đương)
  • Egress: $0.09/GB (R2: $0 - đây là khác biệt lớn nhất)

Giải pháp: setup R2 bucket

Bước 1: Tạo R2 bucket

Vào Cloudflare dashboard → R2 Object Storage → Create bucket.

Bucket name: my-app-assets
Location: Automatic (hoặc chọn region gần user: ENAM, WNAM, WEUR, APAC)
Default storage class: Standard

Bước 2: Tạo API credentials

Vào R2 → Manage R2 API tokens → Create API token.

Token name: my-app-r2-access
Permissions: Object Read & Write
Bucket: my-app-assets (hoặc All buckets)

Lưu lại:

  • Access Key ID
  • Secret Access Key
  • Endpoint: https://.r2.cloudflarestorage.com

Bước 3: Setup public access (nếu cần serve file public)

Mặc định bucket là private. Để serve file public qua URL:

Vào R2 → [bucket] → Settings → Public Access → Allow Access.

Cloudflare cấp URL dạng: https://pub-.r2.dev/

Hoặc dùng custom domain: trỏ subdomain (ví dụ assets.yourdomain.com) về bucket qua R2 → [bucket] → Settings → Custom Domains.


Dùng R2 với AWS SDK (S3-compatible)

R2 tương thích S3 API - code đang dùng AWS SDK gần như không cần sửa, chỉ đổi endpoint và credentials.

C# (.NET) với AWSSDK.S3:

using Amazon.S3;
using Amazon.S3.Model;

// Cấu hình R2 endpoint
var config = new AmazonS3Config
{
    ServiceURL = "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    ForcePathStyle = true, // Bắt buộc với R2
    SignatureVersion = "4",
};

var client = new AmazonS3Client(
    awsAccessKeyId: "R2_ACCESS_KEY_ID",
    awsSecretAccessKey: "R2_SECRET_ACCESS_KEY",
    clientConfig: config
);

// Upload file
var putRequest = new PutObjectRequest
{
    BucketName = "my-app-assets",
    Key = "uploads/image.jpg",
    FilePath = "/path/to/image.jpg",
    ContentType = "image/jpeg",
};
await client.PutObjectAsync(putRequest);

// Download file
var getRequest = new GetObjectRequest
{
    BucketName = "my-app-assets",
    Key = "uploads/image.jpg",
};
using var response = await client.GetObjectAsync(getRequest);

JavaScript/TypeScript với AWS SDK v3:

import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";

const r2 = new S3Client({
  region: "auto",
  endpoint: `https://${process.env.ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID!,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
  },
});

// Upload
await r2.send(new PutObjectCommand({
  Bucket: "my-app-assets",
  Key: `uploads/${filename}`,
  Body: fileBuffer,
  ContentType: mimeType,
}));

// Tạo presigned URL (cho upload trực tiếp từ browser)
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const url = await getSignedUrl(r2, new PutObjectCommand({
  Bucket: "my-app-assets",
  Key: `uploads/${filename}`,
}), { expiresIn: 3600 });

Tích hợp native với Cloudflare Workers

Khi dùng R2 trong Workers, không cần credentials - binding trực tiếp:

// wrangler.toml
// [[r2_buckets]]
// binding = "MY_BUCKET"
// bucket_name = "my-app-assets"

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const key = url.pathname.slice(1); // "/image.jpg" → "image.jpg"

    if (request.method === "GET") {
      const object = await env.MY_BUCKET.get(key);
      if (!object) return new Response("Not found", { status: 404 });
      
      return new Response(object.body, {
        headers: {
          "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
          "Cache-Control": "public, max-age=31536000",
          "ETag": object.etag,
        },
      });
    }

    if (request.method === "PUT") {
      await env.MY_BUCKET.put(key, request.body, {
        httpMetadata: {
          contentType: request.headers.get("Content-Type") ?? undefined,
        },
      });
      return new Response("Uploaded", { status: 200 });
    }

    return new Response("Method not allowed", { status: 405 });
  },
};

Migrate từ S3 sang R2

Cloudflare cung cấp Super Slurper - tool migrate data từ S3 (và các S3-compatible providers) sang R2:

Vào R2 → Data Migration → Migrate from S3.

Nhập:

  • Source: S3 bucket URL, Access Key, Secret Key
  • Destination: R2 bucket

Cloudflare tự copy object theo batch, track tiến độ. Trong thời gian migrate, bạn có thể chạy song song - giữ S3 như source of truth, R2 nhận traffic dần dần.

Sau khi migrate xong: đổi DNS/CDN trỏ về R2 public URL, verify, rồi tắt S3.


Khi nào KHÔNG dùng R2

  • Cần tích hợp sâu AWS ecosystem: Lambda triggers, EventBridge, S3 Select - R2 chưa hỗ trợ
  • Region-specific compliance: R2 không cho phép pin data ở một region cụ thể (dùng Jurisdiction hint, nhưng không granular như S3)
  • Object versioning phức tạp: R2 hỗ trợ versioning nhưng ít mature hơn S3

Bài học rút ra

Sau khi migrate, kết quả:

  • Egress fee: $0 - line item khó chịu biến mất hoàn toàn
  • Code changes: minimal - chỉ đổi endpoint và credentials trong config
  • Performance: tương đương hoặc tốt hơn khi serve file qua Cloudflare CDN
  • DX tốt hơn: R2 trong Workers không cần credentials, binding trực tiếp

Nếu bạn đang dùng S3 chủ yếu để lưu và serve file (upload, download), R2 là drop-in replacement xứng đáng xem xét - đặc biệt khi egress volume lớn.


Tham khảo


BKGlobal Tech Team

Blog Công nghệ

Xem tất cả