Cloudflare R2: บริการจัดเก็บข้อมูลแบบ Object Storage ที่ไม่มีค่าดึงข้อมูลออก (Egress)

Cloudflare R2 Storage

Cloudflare R2 Storage — Photo by Growtika on Unsplash

Cloudflare R2: บริการจัดเก็บข้อมูลแบบ Object Storage ที่ไม่มีค่า Egress

ประหยัดต้นทุนการจัดเก็บข้อมูลด้วยบริการที่เข้ากันได้กับ S3 แต่ไม่มีค่าใช้จ่ายในการดึงข้อมูลออก


1. ทำความเข้าใจ Cloudflare R2

R2 คืออะไร?

  • นิยาม: R2 คือบริการ Object Storage ของ Cloudflare ที่เข้ากันได้กับ Amazon S3 API
  • จุดเด่น: ไม่มีค่า Egress Bandwidth (ค่าดึงข้อมูลออก) ซึ่งต่างจาก AWS S3 และผู้ให้บริการรายอื่น
  • ความสามารถหลัก:
    • จัดเก็บไฟล์ได้ทุกประเภท (รูปภาพ, วิดีโอ, เอกสาร, แบ็คอัพ)
    • เข้าถึงผ่าน API ที่เข้ากันได้กับ S3
    • ทำงานร่วมกับ Cloudflare Workers และบริการอื่นๆ ได้อย่างไร้รอยต่อ
  • Free Tier: 10GB storage และ 1 ล้าน Class A Operations ต่อเดือน

ทำไมต้องใช้ R2?

  • ประหยัดต้นทุน: ไม่มีค่า Egress Bandwidth ซึ่งเป็นค่าใช้จ่ายหลักของบริการ Object Storage อื่นๆ
  • ความเร็ว: กระจายข้อมูลผ่านเครือข่าย Cloudflare ทั่วโลก ทำให้เข้าถึงได้เร็ว
  • ความเข้ากันได้: ใช้ API เดียวกับ S3 ทำให้ย้ายจาก AWS มาใช้ได้ง่าย
  • ความปลอดภัย: ข้อมูลถูกเข้ารหัสทั้งขณะส่งและจัดเก็บ
  • ความง่าย: จัดการผ่าน Dashboard หรือ API ได้สะดวก

2. เปรียบเทียบ R2 กับบริการ Object Storage อื่นๆ

R2 vs Amazon S3

คุณสมบัติ Cloudflare R2 Amazon S3
ราคาเริ่มต้น $0.015/GB/เดือน $0.023/GB/เดือน
Egress Bandwidth ฟรี $0.09/GB
Free Tier 10GB storage 5GB storage
API S3-compatible S3
การกระจายข้อมูล ทั่วโลก ตาม Region
Storage Classes 1 ประเภท หลายประเภท
Lifecycle Rules มี มี

R2 vs Google Cloud Storage

คุณสมบัติ Cloudflare R2 Google Cloud Storage
ราคาเริ่มต้น $0.015/GB/เดือน $0.020/GB/เดือน
Egress Bandwidth ฟรี $0.08-$0.12/GB
Free Tier 10GB storage 5GB storage
API S3-compatible GCS และ S3-compatible
การกระจายข้อมูล ทั่วโลก ตาม Region
Storage Classes 1 ประเภท หลายประเภท

R2 vs Backblaze B2

คุณสมบัติ Cloudflare R2 Backblaze B2
ราคาเริ่มต้น $0.015/GB/เดือน $0.005/GB/เดือน
Egress Bandwidth ฟรี ฟรีผ่าน Cloudflare, $0.01/GB อื่นๆ
Free Tier 10GB storage 10GB storage
API S3-compatible B2 และ S3-compatible
การกระจายข้อมูล ทั่วโลก US region
การทำงานร่วมกับ Cloudflare ไร้รอยต่อ ต้องตั้งค่าเพิ่มเติม

3. การเริ่มต้นใช้งาน R2

ขั้นตอนการสร้าง R2 Bucket

  1. สร้างบัญชี Cloudflare

    • ไปที่ cloudflare.com และสมัครบัญชีฟรี
    • ยืนยันอีเมลของคุณ
  2. เปิดใช้งาน R2

    • ไปที่ Cloudflare Dashboard
    • เลือก “R2” จากเมนูด้านซ้าย
    • คลิก “Create bucket”
  3. ตั้งค่า Bucket

    • ตั้งชื่อ bucket (ต้องเป็นชื่อที่ไม่ซ้ำกันทั่วโลก)
    • เลือก Region (หรือใช้ค่าเริ่มต้น)
    • ตั้งค่าการเข้าถึง (Public หรือ Private)
  4. ตั้งค่า API Token

    • ไปที่ “R2” > “Manage R2 API Tokens”
    • สร้าง Token ใหม่สำหรับการเข้าถึง R2 API
    • บันทึก Access Key และ Secret Key ไว้ให้ดี

การอัปโหลดไฟล์ผ่าน Dashboard

  1. เข้าสู่ Bucket

    • ไปที่ R2 Dashboard
    • เลือก bucket ที่ต้องการ
  2. อัปโหลดไฟล์

    • คลิก “Upload” หรือลากไฟล์มาวาง
    • เลือกไฟล์ที่ต้องการอัปโหลด
    • ตั้งค่า metadata หรือ headers (ถ้าต้องการ)
  3. จัดการไฟล์

    • ดูรายการไฟล์ทั้งหมดใน bucket
    • ดาวน์โหลด, ลบ, หรือแก้ไขข้อมูลของไฟล์
    • คัดลอก URL สำหรับเข้าถึงไฟล์

การใช้งาน R2 ผ่าน S3 CLI

  1. ติดตั้ง AWS CLI

    pip install awscli
    
  2. ตั้งค่า Profile สำหรับ R2

    aws configure --profile r2
    
    • AWS Access Key ID: [R2 Access Key]
    • AWS Secret Access Key: [R2 Secret Key]
    • Default region name: auto
    • Default output format: json
  3. ใช้งานคำสั่งพื้นฐาน

    # ดูรายการ buckets
    aws s3 ls --endpoint-url https://[ACCOUNT_ID].r2.cloudflarestorage.com --profile r2
    
    # อัปโหลดไฟล์
    aws s3 cp myfile.jpg s3://my-bucket/ --endpoint-url https://[ACCOUNT_ID].r2.cloudflarestorage.com --profile r2
    
    # ดาวน์โหลดไฟล์
    aws s3 cp s3://my-bucket/myfile.jpg ./myfile.jpg --endpoint-url https://[ACCOUNT_ID].r2.cloudflarestorage.com --profile r2
    
    # ลบไฟล์
    aws s3 rm s3://my-bucket/myfile.jpg --endpoint-url https://[ACCOUNT_ID].r2.cloudflarestorage.com --profile r2
    

4. การใช้งาน R2 กับภาษาโปรแกรมต่างๆ

JavaScript/Node.js (AWS SDK)

// ติดตั้ง: npm install @aws-sdk/client-s3

const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/client-s3");

// ตั้งค่าการเชื่อมต่อ
const s3 = new S3Client({
  region: "auto",
  endpoint: `https://[ACCOUNT_ID].r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: "R2_ACCESS_KEY",
    secretAccessKey: "R2_SECRET_KEY"
  }
});

// อัปโหลดไฟล์
async function uploadFile(bucketName, key, body) {
  const command = new PutObjectCommand({
    Bucket: bucketName,
    Key: key,
    Body: body
  });
  
  return s3.send(command);
}

// ดาวน์โหลดไฟล์
async function downloadFile(bucketName, key) {
  const command = new GetObjectCommand({
    Bucket: bucketName,
    Key: key
  });
  
  const response = await s3.send(command);
  return response.Body;
}

Python (boto3)

# ติดตั้ง: pip install boto3

import boto3

# ตั้งค่าการเชื่อมต่อ
s3 = boto3.client('s3',
    endpoint_url = 'https://[ACCOUNT_ID].r2.cloudflarestorage.com',
    aws_access_key_id = 'R2_ACCESS_KEY',
    aws_secret_access_key = 'R2_SECRET_KEY'
)

# อัปโหลดไฟล์
def upload_file(bucket_name, key, file_path):
    s3.upload_file(file_path, bucket_name, key)

# ดาวน์โหลดไฟล์
def download_file(bucket_name, key, file_path):
    s3.download_file(bucket_name, key, file_path)

# ดูรายการไฟล์
def list_files(bucket_name):
    response = s3.list_objects_v2(Bucket=bucket_name)
    if 'Contents' in response:
        for obj in response['Contents']:
            print(obj['Key'])

PHP (AWS SDK)

// ติดตั้ง: composer require aws/aws-sdk-php

require 'vendor/autoload.php';

use Aws\S3\S3Client;

// ตั้งค่าการเชื่อมต่อ
$s3 = new S3Client([
    'endpoint' => 'https://[ACCOUNT_ID].r2.cloudflarestorage.com',
    'region' => 'auto',
    'version' => 'latest',
    'credentials' => [
        'key' => 'R2_ACCESS_KEY',
        'secret' => 'R2_SECRET_KEY',
    ]
]);

// อัปโหลดไฟล์
function uploadFile($bucketName, $key, $filePath) {
    global $s3;
    $result = $s3->putObject([
        'Bucket' => $bucketName,
        'Key' => $key,
        'SourceFile' => $filePath
    ]);
    return $result;
}

// ดาวน์โหลดไฟล์
function downloadFile($bucketName, $key, $savePath) {
    global $s3;
    $result = $s3->getObject([
        'Bucket' => $bucketName,
        'Key' => $key,
        'SaveAs' => $savePath
    ]);
    return $result;
}

5. การใช้งาน R2 กับ Cloudflare Workers

การเข้าถึง R2 จาก Workers

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

export default {
  async fetch(request, env, ctx) {
    // ดึงข้อมูลจาก URL
    const url = new URL(request.url);
    const key = url.pathname.slice(1); // ตัด / ออกจากด้านหน้า
    
    if (request.method === "GET") {
      // ดาวน์โหลดไฟล์
      try {
        const object = await env.MY_BUCKET.get(key);
        
        if (object === null) {
          return new Response("Object Not Found", { status: 404 });
        }
        
        const headers = new Headers();
        object.writeHttpMetadata(headers);
        headers.set("etag", object.httpEtag);
        
        return new Response(object.body, {
          headers
        });
      } catch (e) {
        return new Response("Error: " + e.message, { status: 500 });
      }
    } else if (request.method === "PUT") {
      // อัปโหลดไฟล์
      try {
        const object = await env.MY_BUCKET.put(key, request.body, {
          httpMetadata: request.headers
        });
        
        return new Response("Object Uploaded Successfully", { status: 200 });
      } catch (e) {
        return new Response("Error: " + e.message, { status: 500 });
      }
    } else if (request.method === "DELETE") {
      // ลบไฟล์
      try {
        await env.MY_BUCKET.delete(key);
        return new Response("Object Deleted Successfully", { status: 200 });
      } catch (e) {
        return new Response("Error: " + e.message, { status: 500 });
      }
    }
    
    return new Response("Method Not Allowed", { status: 405 });
  }
};

การสร้าง Image Resizing Service

// wrangler.toml
// [[r2_buckets]]
// binding = "IMAGES_BUCKET"
// bucket_name = "my-images"

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const path = url.pathname;
    
    // รูปแบบ URL: /resize/{width}x{height}/{image-key}
    const match = path.match(/^\/resize\/(\d+)x(\d+)\/(.+)$/);
    
    if (!match) {
      return new Response("Invalid URL format", { status: 400 });
    }
    
    const width = parseInt(match[1]);
    const height = parseInt(match[2]);
    const imageKey = match[3];
    
    // ดึงรูปภาพจาก R2
    const object = await env.IMAGES_BUCKET.get(imageKey);
    
    if (object === null) {
      return new Response("Image Not Found", { status: 404 });
    }
    
    // ปรับขนาดรูปภาพด้วย Cloudflare Image Resizing
    const imageURL = new URL(request.url);
    imageURL.pathname = imageKey;
    
    const resizedImage = await fetch(imageURL.toString(), {
      cf: {
        image: {
          width: width,
          height: height,
          fit: "cover"
        }
      }
    });
    
    // ส่งรูปภาพที่ปรับขนาดแล้วกลับไป
    return new Response(resizedImage.body, {
      headers: {
        "Content-Type": object.httpMetadata.contentType,
        "Cache-Control": "public, max-age=31536000"
      }
    });
  }
};

การสร้าง Public URL Generator

// wrangler.toml
// [[r2_buckets]]
// binding = "FILES_BUCKET"
// bucket_name = "my-files"

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    if (url.pathname === "/generate-url" && request.method === "POST") {
      try {
        const { key, expirationSeconds = 3600 } = await request.json();
        
        if (!key) {
          return new Response(JSON.stringify({ error: "Key is required" }), {
            status: 400,
            headers: { "Content-Type": "application/json" }
          });
        }
        
        // ตรวจสอบว่าไฟล์มีอยู่จริง
        const object = await env.FILES_BUCKET.head(key);
        
        if (object === null) {
          return new Response(JSON.stringify({ error: "File not found" }), {
            status: 404,
            headers: { "Content-Type": "application/json" }
          });
        }
        
        // สร้าง signed URL
        const signedUrl = await env.FILES_BUCKET.createSignedUrl(key, expirationSeconds);
        
        return new Response(JSON.stringify({ url: signedUrl }), {
          headers: { "Content-Type": "application/json" }
        });
      } catch (e) {
        return new Response(JSON.stringify({ error: e.message }), {
          status: 500,
          headers: { "Content-Type": "application/json" }
        });
      }
    }
    
    return new Response("Not Found", { status: 404 });
  }
};

6. การตั้งค่า Public Access และ Custom Domain

การตั้งค่า Public Access

  1. เปิดใช้งาน Public Access

    • ไปที่ R2 Dashboard > เลือก bucket
    • ไปที่ “Settings” > “Public Access”
    • เปิดใช้งาน “Public Access”
  2. การตั้งค่า CORS (Cross-Origin Resource Sharing)

    [
      {
        "AllowedOrigins": ["https://example.com"],
        "AllowedMethods": ["GET"],
        "AllowedHeaders": ["*"],
        "MaxAgeSeconds": 3600
      }
    ]
    
  3. การเข้าถึงไฟล์แบบ Public

    • URL รูปแบบ: https://pub-[HASH].r2.dev/[BUCKET_NAME]/[FILE_PATH]
    • ตัวอย่าง: https://pub-abc123.r2.dev/my-bucket/images/photo.jpg

การตั้งค่า Custom Domain

  1. เพิ่ม Custom Domain ใน Cloudflare

    • ต้องมีโดเมนที่ใช้ Cloudflare DNS
    • ไปที่ R2 Dashboard > “Custom Domains”
    • คลิก “Add Custom Domain”
    • ใส่โดเมนที่ต้องการ (เช่น assets.example.com)
  2. ตั้งค่า DNS Record

    • Cloudflare จะสร้าง CNAME record ให้อัตโนมัติ
    • ตรวจสอบว่า Proxy Status เป็น “Proxied”
  3. การเข้าถึงไฟล์ผ่าน Custom Domain

    • URL รูปแบบ: https://[CUSTOM_DOMAIN]/[FILE_PATH]
    • ตัวอย่าง: https://assets.example.com/images/photo.jpg
  4. การตั้งค่า Cache

    • ไปที่ Cloudflare Dashboard > Rules > Cache Rules
    • สร้าง Cache Rule สำหรับ Custom Domain
    • ตั้งค่า Edge TTL และ Browser TTL ตามต้องการ

7. การใช้งาน R2 กับเว็บไซต์และแอปพลิเคชัน

การใช้ R2 เก็บรูปภาพสำหรับเว็บไซต์

  1. การอัปโหลดรูปภาพ

    // ตัวอย่างการอัปโหลดรูปภาพจาก form
    const form = document.getElementById('upload-form');
    
    form.addEventListener('submit', async (e) => {
      e.preventDefault();
    
      const fileInput = document.getElementById('image-file');
      const file = fileInput.files[0];
    
      if (!file) {
        alert('Please select a file');
        return;
      }
    
      // ส่งไฟล์ไปยัง API ที่เชื่อมต่อกับ R2
      const formData = new FormData();
      formData.append('image', file);
    
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
      });
    
      const result = await response.json();
    
      if (result.success) {
        document.getElementById('image-url').textContent = result.url;
        document.getElementById('preview').src = result.url;
      } else {
        alert('Upload failed: ' + result.error);
      }
    });
    
  2. การแสดงรูปภาพบนเว็บไซต์

    <!-- การใช้ Public URL -->
    <img src="https://pub-abc123.r2.dev/my-bucket/images/photo.jpg" alt="My Photo">
    
    <!-- การใช้ Custom Domain -->
    <img src="https://assets.example.com/images/photo.jpg" alt="My Photo">
    
    <!-- การใช้ Image Resizing -->
    <img src="https://assets.example.com/resize/400x300/images/photo.jpg" alt="My Photo">
    

การใช้ R2 เก็บไฟล์สำหรับแอปพลิเคชัน

  1. การอัปโหลดไฟล์จากแอปพลิเคชัน

    // React/Next.js ตัวอย่าง
    import { useState } from 'react';
    
    function FileUploader() {
      const [file, setFile] = useState(null);
      const [uploading, setUploading] = useState(false);
      const [downloadUrl, setDownloadUrl] = useState('');
    
      const handleFileChange = (e) => {
        setFile(e.target.files[0]);
      };
    
      const handleUpload = async () => {
        if (!file) return;
    
        setUploading(true);
    
        const formData = new FormData();
        formData.append('file', file);
    
        try {
          const response = await fetch('/api/upload-file', {
            method: 'POST',
            body: formData
          });
    
          const data = await response.json();
    
          if (data.success) {
            setDownloadUrl(data.url);
            alert('File uploaded successfully!');
          } else {
            alert('Upload failed: ' + data.error);
          }
        } catch (error) {
          alert('Error: ' + error.message);
        } finally {
          setUploading(false);
        }
      };
    
      return (
        <div>
          <input type="file" onChange={handleFileChange} />
          <button onClick={handleUpload} disabled={!file || uploading}>
            {uploading ? 'Uploading...' : 'Upload'}
          </button>
    
          {downloadUrl && (
            <div>
              <p>File uploaded successfully!</p>
              <a href={downloadUrl} target="_blank" rel="noopener noreferrer">
                Download File
              </a>
            </div>
          )}
        </div>
      );
    }
    
  2. การสร้าง API สำหรับจัดการไฟล์

    // Next.js API Route
    import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
    import { v4 as uuidv4 } from 'uuid';
    import formidable from 'formidable';
    import fs from 'fs';
    
    export const config = {
      api: {
        bodyParser: false,
      },
    };
    
    const s3 = new S3Client({
      region: 'auto',
      endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
      credentials: {
        accessKeyId: process.env.R2_ACCESS_KEY,
        secretAccessKey: process.env.R2_SECRET_KEY,
      },
    });
    
    export default async function handler(req, res) {
      if (req.method !== 'POST') {
        return res.status(405).json({ error: 'Method not allowed' });
      }
    
      try {
        const form = new formidable.IncomingForm();
    
        form.parse(req, async (err, fields, files) => {
          if (err) {
            return res.status(500).json({ error: 'Error parsing form' });
          }
    
          const file = files.file;
    
          if (!file) {
            return res.status(400).json({ error: 'No file uploaded' });
          }
    
          const fileContent = fs.readFileSync(file.filepath);
          const fileKey = `uploads/${uuidv4()}-${file.originalFilename}`;
    
          const command = new PutObjectCommand({
            Bucket: process.env.R2_BUCKET_NAME,
            Key: fileKey,
            Body: fileContent,
            ContentType: file.mimetype,
          });
    
          await s3.send(command);
    
          const fileUrl = `https://${process.env.R2_CUSTOM_DOMAIN}/${fileKey}`;
    
          res.status(200).json({
            success: true,
            url: fileUrl,
          });
        });
      } catch (error) {
        console.error('Upload error:', error);
        res.status(500).json({ error: 'Upload failed' });
      }
    }
    

8. การใช้ R2 สำหรับ Backup และ Archive

การสร้างระบบ Backup อัตโนมัติ

  1. การสร้าง Backup Script ด้วย Python

    import boto3
    import os
    import datetime
    import tarfile
    
    # ตั้งค่าการเชื่อมต่อ R2
    s3 = boto3.client('s3',
        endpoint_url = 'https://[ACCOUNT_ID].r2.cloudflarestorage.com',
        aws_access_key_id = 'R2_ACCESS_KEY',
        aws_secret_access_key = 'R2_SECRET_KEY'
    )
    
    # ตั้งค่าพารามิเตอร์
    BUCKET_NAME = 'my-backups'
    BACKUP_DIR = '/path/to/backup'
    
    def create_backup():
        # สร้างชื่อไฟล์ด้วยวันที่และเวลาปัจจุบัน
        timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
        backup_file = f'backup_{timestamp}.tar.gz'
    
        # สร้างไฟล์ tar.gz
        with tarfile.open(backup_file, 'w:gz') as tar:
            tar.add(BACKUP_DIR, arcname=os.path.basename(BACKUP_DIR))
    
        # อัปโหลดไฟล์ไปยัง R2
        print(f'Uploading {backup_file} to R2...')
        s3.upload_file(backup_file, BUCKET_NAME, backup_file)
    
        # ลบไฟล์ชั่วคราว
        os.remove(backup_file)
    
        print(f'Backup completed: {backup_file}')
    
        # ลบไฟล์ backup เก่า (เก็บไว้แค่ 7 วันล่าสุด)
        delete_old_backups(7)
    
    def delete_old_backups(days_to_keep):
        # ดึงรายการไฟล์ทั้งหมด
        response = s3.list_objects_v2(Bucket=BUCKET_NAME)
    
        if 'Contents' not in response:
            return
    
        # คำนวณวันที่ที่เก่ากว่า X วัน
        cutoff_date = datetime.datetime.now() - datetime.timedelta(days=days_to_keep)
    
        for obj in response['Contents']:
            # ตรวจสอบว่าเป็นไฟล์ backup หรือไม่
            if obj['Key'].startswith('backup_'):
                # ดึงวันที่จากชื่อไฟล์ (format: backup_YYYYMMDD_HHMMSS.tar.gz)
                try:
                    date_str = obj['Key'].split('_')[1].split('.')[0]
                    file_date = datetime.datetime.strptime(date_str, '%Y%m%d_%H%M%S')
    
                    # ลบไฟล์ที่เก่ากว่า X วัน
                    if file_date < cutoff_date:
                        print(f"Deleting old backup: {obj['Key']}")
                        s3.delete_object(Bucket=BUCKET_NAME, Key=obj['Key'])
                except:
                    pass
    
    if __name__ == '__main__':
        create_backup()
    
  2. การตั้งค่า Cron Job

    # เปิด crontab editor
    crontab -e
    
    # เพิ่มบรรทัดนี้เพื่อรัน script ทุกวันเวลา 02:00 น.
    0 2 * * * /usr/bin/python3 /path/to/backup_script.py >> /path/to/backup.log 2>&1
    

การกู้คืนข้อมูลจาก Backup

import boto3
import tarfile
import os

# ตั้งค่าการเชื่อมต่อ R2
s3 = boto3.client('s3',
    endpoint_url = 'https://[ACCOUNT_ID].r2.cloudflarestorage.com',
    aws_access_key_id = 'R2_ACCESS_KEY',
    aws_secret_access_key = 'R2_SECRET_KEY'
)

# ตั้งค่าพารามิเตอร์
BUCKET_NAME = 'my-backups'
RESTORE_DIR = '/path/to/restore'

def list_backups():
    # ดึงรายการไฟล์ backup ทั้งหมด
    response = s3.list_objects_v2(Bucket=BUCKET_NAME)
    
    if 'Contents' not in response:
        print("No backups found")
        return []
    
    # กรองเฉพาะไฟล์ backup
    backups = [obj['Key'] for obj in response['Contents'] if obj['Key'].startswith('backup_')]
    backups.sort(reverse=True)  # เรียงจากใหม่ไปเก่า
    
    # แสดงรายการ
    for i, backup in enumerate(backups):
        print(f"{i+1}. {backup}")
    
    return backups

def restore_backup(backup_file):
    # ดาวน์โหลดไฟล์ backup
    print(f"Downloading {backup_file}...")
    s3.download_file(BUCKET_NAME, backup_file, backup_file)
    
    # สร้างโฟลเดอร์สำหรับกู้คืนข้อมูล (ถ้ายังไม่มี)
    os.makedirs(RESTORE_DIR, exist_ok=True)
    
    # แตกไฟล์ tar.gz
    print(f"Extracting {backup_file} to {RESTORE_DIR}...")
    with tarfile.open(backup_file, 'r:gz') as tar:
        tar.extractall(path=RESTORE_DIR)
    
    # ลบไฟล์ชั่วคราว
    os.remove(backup_file)
    
    print(f"Restore completed to {RESTORE_DIR}")

if __name__ == '__main__':
    backups = list_backups()
    
    if not backups:
        exit(0)
    
    choice = input("\nEnter the number of the backup to restore (or 'q' to quit): ")
    
    if choice.lower() == 'q':
        exit(0)
    
    try:
        index = int(choice) - 1
        if 0 <= index < len(backups):
            restore_backup(backups[index])
        else:
            print("Invalid selection")
    except ValueError:
        print("Invalid input")

9. การจัดการต้นทุนและการติดตามการใช้งาน

การติดตามการใช้งานและค่าใช้จ่าย

  1. การดูสถิติการใช้งาน

    • ไปที่ Cloudflare Dashboard > R2
    • ดูแท็บ “Usage” เพื่อดูข้อมูล:
      • Storage Used
      • Class A Operations (PUT, POST, LIST)
      • Class B Operations (GET)
      • Egress Bandwidth
  2. การตั้งค่าการแจ้งเตือน

    • ไปที่ Cloudflare Dashboard > R2 > “Usage Alerts”
    • ตั้งค่าการแจ้งเตือนเมื่อ:
      • Storage เกินกำหนด
      • Operations เกินกำหนด
    • เลือกช่องทางการแจ้งเตือน (Email, Webhook)

เทคนิคการประหยัดต้นทุน

  1. การใช้ Lifecycle Rules

    • ตั้งค่า Lifecycle Rules เพื่อลบไฟล์ที่ไม่ได้ใช้งานอัตโนมัติ
    • ไปที่ R2 Dashboard > เลือก bucket > “Lifecycle Rules”
    • ตัวอย่าง: ลบไฟล์ชั่วคราวหลังจาก 7 วัน
  2. การบีบอัดไฟล์ก่อนอัปโหลด

    // ตัวอย่างการบีบอัดรูปภาพก่อนอัปโหลด
    async function compressAndUploadImage(file) {
      // ใช้ browser-image-compression library
      const imageFile = await imageCompression(file, {
        maxSizeMB: 1,
        maxWidthOrHeight: 1920
      });
    
      // อัปโหลดไฟล์ที่บีบอัดแล้ว
      const formData = new FormData();
      formData.append('image', imageFile);
    
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
      });
    
      return response.json();
    }
    
  3. การใช้ Cache อย่างมีประสิทธิภาพ

    • ตั้งค่า Cache-Control headers ให้เหมาะสม
    • ตัวอย่าง: Cache-Control: public, max-age=31536000 สำหรับไฟล์สถิต
    • ใช้ Cloudflare Cache Rules เพื่อเพิ่มประสิทธิภาพ

10. กรณีศึกษา: การใช้งานจริงของ Startup

กรณีศึกษา 1: เว็บไซต์ Content

  • ความท้าทาย: Startup ด้านคอนเทนต์ต้องการพื้นที่เก็บรูปภาพและวิดีโอที่ประหยัดต้นทุน
  • การใช้ R2:
    • เก็บรูปภาพและวิดีโอทั้งหมดใน R2
    • ใช้ Cloudflare Workers เพื่อปรับขนาดรูปภาพตามอุปกรณ์
    • ตั้งค่า Custom Domain สำหรับเข้าถึงไฟล์มีเดีย
  • ผลลัพธ์:
    • ประหยัดค่า bandwidth ได้มากกว่า 90% เมื่อเทียบกับ AWS S3
    • เว็บไซต์โหลดเร็วขึ้นเนื่องจากไฟล์มีเดียอยู่บนเครือข่าย Cloudflare
    • ลดความซับซ้อนในการจัดการไฟล์มีเดีย

กรณีศึกษา 2: แอปพลิเคชันแชร์ไฟล์

  • ความท้าทาย: Startup ต้องการสร้างแพลตฟอร์มแชร์ไฟล์ที่มีต้นทุนต่ำ
  • การใช้ R2:
    • เก็บไฟล์ทั้งหมดใน R2
    • ใช้ Workers เพื่อสร้าง Signed URLs ที่หมดอายุ
    • สร้างระบบจัดการสิทธิ์การเข้าถึงไฟล์
  • ผลลัพธ์:
    • ประหยัดค่าใช้จ่ายได้มากกว่า 80% เมื่อเทียบกับผู้ให้บริการรายอื่น
    • ผู้ใช้สามารถดาวน์โหลดไฟล์ได้เร็วขึ้นทั่วโลก
    • สามารถขยายธุรกิจได้โดยไม่กังวลเรื่องค่า bandwidth

กรณีศึกษา 3: ระบบ Backup อัตโนมัติ

  • ความท้าทาย: Startup ด้าน SaaS ต้องการระบบ backup ที่เชื่อถือได้และประหยัด
  • การใช้ R2:
    • สร้างระบบ backup อัตโนมัติที่เก็บข้อมูลใน R2
    • ตั้งค่า lifecycle rules เพื่อลบ backup เก่า
    • สร้างระบบกู้คืนข้อมูลที่ใช้งานง่าย
  • ผลลัพธ์:
    • ลดค่าใช้จ่ายในการเก็บ backup ลง 70%
    • เพิ่มความเร็วในการกู้คืนข้อมูล
    • ระบบทำงานอัตโนมัติ ลดภาระของทีม DevOps

11. ข้อจำกัดและการอัพเกรด

ข้อจำกัดของ R2

  • Free Tier:

    • 10GB storage
    • 1 ล้าน Class A Operations ต่อเดือน
    • 10 ล้าน Class B Operations ต่อเดือน
    • ไม่จำกัด Egress Bandwidth
  • ข้อจำกัดทางเทคนิค:

    • ขนาดไฟล์สูงสุด: 5TB
    • จำนวน buckets สูงสุด: 1,000 ต่อบัญชี
    • ไม่รองรับ Server-Side Encryption ด้วย Customer-Provided Keys
    • ไม่รองรับ Multi-Region Replication แบบอัตโนมัติ

เมื่อไรควรพิจารณาแพ็กเกจ Paid

  1. ปริมาณการใช้งาน:

    • ใช้พื้นที่เกิน 10GB
    • มี Class A Operations เกิน 1 ล้านต่อเดือน
    • มี Class B Operations เกิน 10 ล้านต่อเดือน
  2. ความต้องการพิเศษ:

    • ต้องการ Support แบบ Priority
    • ต้องการการรับประกันด้าน SLA
    • ต้องการฟีเจอร์ Enterprise เพิ่มเติม
  3. การเติบโตของธุรกิจ:

    • มีแผนขยายการใช้งานในอนาคต
    • ต้องการความยืดหยุ่นในการใช้งาน
    • ต้องการการรองรับจากทีม Cloudflare

ราคาและแพ็กเกจ

  • Free Tier:

    • 10GB storage
    • 1 ล้าน Class A Operations ต่อเดือน
    • 10 ล้าน Class B Operations ต่อเดือน
    • ไม่จำกัด Egress Bandwidth
  • Paid Tier:

    • Storage: $0.015/GB/เดือน (หลังจาก 10GB แรก)
    • Class A Operations: $4.50/ล้าน (หลังจาก 1 ล้านแรก)
    • Class B Operations: $0.36/10 ล้าน (หลังจาก 10 ล้านแรก)
    • Egress Bandwidth: ฟรีทั้งหมด

12. สรุป: ทำไม Cloudflare R2 ถึงเหมาะกับ Startup

  1. ประหยัดต้นทุน
    ไม่มีค่า Egress Bandwidth ซึ่งเป็นค่าใช้จ่ายหลักของบริการ Object Storage อื่นๆ

  2. ความเข้ากันได้
    ใช้ API เดียวกับ S3 ทำให้ย้ายจาก AWS มาใช้ได้ง่าย ไม่ต้องเขียนโค้ดใหม่

  3. ประสิทธิภาพสูง
    กระจายข้อมูลผ่านเครือข่าย Cloudflare ทั่วโลก ทำให้เข้าถึงได้เร็ว

  4. ความปลอดภัย
    ข้อมูลถูกเข้ารหัสทั้งขณะส่งและจัดเก็บ พร้อมระบบป้องกันการโจมตี

  5. ทำงานร่วมกับบริการอื่น
    ทำงานร่วมกับ Cloudflare Workers, Pages และบริการอื่นๆ ได้อย่างไร้รอยต่อ

“Cloudflare R2 เป็นทางเลือกที่ชาญฉลาดสำหรับ Startup ที่ต้องการลดต้นทุนด้าน Object Storage โดยไม่ต้องกังวลเรื่องค่า bandwidth ที่ไม่คาดคิด ทำให้สามารถเติบโตได้อย่างยั่งยืน”


13. แหล่งเรียนรู้เพิ่มเติม

เอกสารและบทความ:

คอมมูนิตี้:

คอร์สและวิดีโอ:

เคล็ดลับ: Cloudflare มีการอัพเดทฟีเจอร์ใหม่ๆ สำหรับ R2 อยู่เสมอ ติดตาม Cloudflare Blog และ Twitter เพื่อไม่พลาดฟีเจอร์ล่าสุดที่อาจช่วยให้การใช้งาน Object Storage ของคุณดียิ่งขึ้น