Thay thế Supabase Storage bằng Cloudflare R2: giảm chi phí lưu trữ file
Ứng dụng tăng trưởng thường kéo theo chi phí lưu trữ tăng âm thầm. Ban đầu vài GB ảnh, PDF, avatar, file đính kèm không đáng kể. Sau vài tháng, dữ liệu phình to. Người dùng upload nhiều hơn. Backup nhiều hơn. Băng thông download tăng. Hóa đơn bắt đầu đau.
Supabase Storage rất tiện nếu stack đang dùng Supabase: auth, database, policy, SDK đồng bộ. Nhưng khi file nhiều, truy cập nhiều, hoặc cần tối ưu chi phí dài hạn, Cloudflare R2 thành lựa chọn đáng cân nhắc. R2 dùng API tương thích S3, không tính phí egress ra internet qua Cloudflare, phù hợp lưu ảnh, video ngắn, tài liệu, asset tĩnh, log export.
Bài này phân tích khi nào nên thay Supabase Storage bằng Cloudflare R2, cách thiết kế kiến trúc, migration, bảo mật, chi phí, và lỗi thường gặp.
Vì sao muốn rời Supabase Storage?
Supabase Storage mạnh ở trải nghiệm tích hợp. Tạo bucket nhanh, dùng policy theo Row Level Security, SDK dễ dùng. Với MVP, startup nhỏ, dashboard nội bộ, Supabase Storage rất ổn.
Nhưng vài tình huống khiến chi phí và giới hạn thành vấn đề:
– Dung lượng tăng nhanh: user-generated content như ảnh sản phẩm, file học tập, hóa đơn, tài liệu.
– Download nhiều: ảnh hiển thị ở frontend, file public, media được share rộng.
– Cần CDN tốt hơn: Cloudflare có mạng biên mạnh, cache tốt, rule linh hoạt.
– Muốn chuẩn S3: dễ đổi provider, dễ dùng tool như AWS CLI, rclone, presigned URL.
– Muốn tách database và object storage: giảm phụ thuộc vào một nền tảng.
Điểm lớn nhất: R2 không thu phí egress theo cách nhiều object storage truyền thống thu. Với app có nhiều lượt tải file, đây là khác biệt lớn.
Cloudflare R2 là gì?
Cloudflare R2 là dịch vụ object storage tương thích S3. Object storage khác filesystem truyền thống: file được lưu dưới dạng object trong bucket, truy cập qua key.
– Ảnh upload từ user.
– File PDF, CSV, DOCX.
– Asset tĩnh cho web/app.
– Video ngắn, audio nhỏ đến vừa.
– Export dữ liệu, backup nhẹ.
– File cần signed URL.
Không nên dùng R2 như database. Không query object theo field phức tạp. Metadata quan trọng vẫn nên nằm trong PostgreSQL/Supabase.
So sánh Supabase Storage và Cloudflare R2
Trải nghiệm tích hợp
Supabase Storage thắng nếu app đã dùng Supabase Auth và Postgres. Policy theo user dễ viết. Ví dụ user chỉ đọc file thuộc chính họ.
R2 không có RLS kiểu Supabase. Bạn cần tự quản quyền ở backend: kiểm tra user, tạo presigned URL, hoặc proxy download/upload qua server.
Kết luận: Supabase tiện hơn. R2 linh hoạt hơn nhưng cần tự xây lớp quyền.
Chi phí băng thông
Đây là nơi R2 nổi bật. Với file public, ảnh sản phẩm, tài liệu tải thường xuyên, egress thường chiếm nhiều chi phí hơn dung lượng. R2 kết hợp Cloudflare CDN giúp giảm áp lực ngân sách.
Supabase Storage vẫn hợp lý ở quy mô nhỏ. Nhưng khi traffic tăng, cần đọc kỹ bảng giá hiện tại, đặc biệt phần storage, bandwidth, image transformation nếu dùng.
Kết luận: App download nhiều nên cân nhắc R2 sớm.
CDN và cache
Cloudflare có lợi thế mạng biên toàn cầu. Dùng custom domain như:
https://files.example.com/products/abc.jpg
Bạn có thể cấu hình cache rule, TTL, transform ảnh qua Cloudflare Images hoặc Workers nếu cần.
Supabase Storage cũng có public URL, nhưng mức kiểm soát CDN thường không mạnh bằng Cloudflare.
Kết luận: R2 hợp với file public, asset cần tải nhanh toàn cầu.
API và portability
R2 tương thích S3. Nghĩa là nhiều thư viện hỗ trợ sẵn:
create table files (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null,
bucket text not null,
object_key text not null,
filename text not null,
content_type text,
size_bytes bigint,
visibility text not null default 'private',
created_at timestamptz not null default now()
);
object_key nên có cấu trúc ổn định:
users/{user_id}/files/{file_id}/{safe_filename}
Không nên tin filename người dùng nhập. Nên sanitize, giới hạn ký tự, hoặc dùng UUID.
Upload file lên R2: nên qua server hay presigned URL?
Có hai hướng chính.
Upload qua backend
Frontend gửi file lên backend. Backend kiểm tra quyền, upload lên R2.
– Backend chịu băng thông.
– File lớn làm server nặng.
Phù hợp dashboard, file nhỏ, nghiệp vụ cần kiểm soát mạnh.
Upload bằng presigned URL
Backend tạo URL upload tạm thời. Frontend upload trực tiếp lên R2.
Luồng:
1. User đăng nhập.
2. Frontend gọi API /files/presign-upload.
3. Backend kiểm tra user, tạo object key, tạo presigned URL.
4. Frontend PUT file lên R2.
5. Frontend gọi API xác nhận upload.
6. Backend ghi metadata vào Supabase.
– Cần xử lý file upload dở.
– Cần validate size/type trước và sau upload.
– Cần lifecycle cleanup object mồ côi.
Phù hợp app nhiều upload, ảnh/video/tài liệu lớn.
Bảo mật: điểm dễ sai khi chuyển sang R2
R2 không tự hiểu user trong Supabase. Vì vậy, quyền phải nằm trong backend.
Checklist bảo mật:
– Không lộ Access Key/Secret Key ở frontend.
– Không dùng bucket public cho file private.
– Presigned URL phải có TTL ngắn, ví dụ 1-10 phút.
– Object key không đoán được nếu file nhạy cảm.
– Kiểm tra owner trước khi tạo signed download URL.
– Giới hạn MIME type và dung lượng.
– Quét malware nếu lưu file nguy hiểm như .exe, .docm, archive.
– Không tin Content-Type từ client.
– Ghi log download/upload quan trọng.
File public như ảnh sản phẩm có thể dùng public bucket hoặc custom domain. File private nên dùng signed URL hoặc backend proxy.
Migration từ Supabase Storage sang R2
Migration cần làm chậm, có kiểm soát. Đừng xóa file cũ ngay.
Quy trình thực tế:
1. Inventory
– Liệt kê bucket, path, dung lượng, file public/private.
– Xác định file nào còn được tham chiếu trong database.
2. Thiết kế mapping
– Supabase path cũ chuyển sang R2 key mới.
– Ví dụ:
avatars/user-123.png -> users/123/avatar.png
3. Copy dữ liệu
– Dùng script Node.js, Python, hoặc rclone nếu endpoint hỗ trợ.
– Tải từ Supabase Storage, upload sang R2.
– Ghi log file thành công/thất bại.
4. Dual-read tạm thời
– App đọc R2 trước.
– Nếu không thấy, fallback Supabase.
– Giúp tránh downtime.
5. Dual-write ngắn hạn
– Upload mới ghi vào R2.
– Có thể vẫn ghi Supabase vài ngày nếu cần rollback.
6. Cập nhật URL/metadata
– Chuyển record trong database sang object_key R2.
– Tránh hardcode full URL. Lưu key tốt hơn.
7. Verify
– So sánh size, checksum nếu có.
– Test file private, public, cache, signed URL.
8. Tắt Supabase Storage cũ
– Chỉ sau khi backup đủ.
– Giữ bản cũ thêm một chu kỳ billing nếu rủi ro cao.
Tối ưu chi phí sau khi dùng R2
R2 giúp giảm egress, nhưng vẫn cần quản lý tốt.
– Nén ảnh trước upload: dùng WebP/AVIF nếu phù hợp.
– Tạo thumbnail: đừng tải ảnh 5MB cho avatar 80px.
– Cache mạnh file public:
– Dùng object key có hash/version để cache dài mà không sợ file cũ.
– Xóa file mồ côi: object tồn tại nhưng database không tham chiếu.
– Lifecycle policy cho file tạm, export cũ, upload chưa xác nhận.
– Theo dõi request count: storage rẻ nhưng request quá nhiều vẫn thành chi phí.
Khi nào không nên chuyển?
Không phải app nào cũng cần R2.
Giữ Supabase Storage nếu:
– File ít, traffic thấp.
– Cần RLS tích hợp nhanh.
– Team nhỏ, không muốn vận hành thêm backend logic.
– App đang MVP, chưa rõ nhu cầu.
– Chi phí hiện tại chưa đáng kể.
Cloudflare R2 không phải “bản thay thế 1-1” cho Supabase Storage. R2 là object storage mạnh, rẻ cho băng thông, hợp với file lớn và traffic cao. Supabase Storage là phần tích hợp tiện, hợp MVP và app cần policy nhanh.
Kiến trúc đáng dùng nhất: Supabase quản user + metadata, R2 lưu file thật. Backend đứng giữa để kiểm tra quyền và tạo signed URL. Cách này giữ lợi thế Supabase, đồng thời giảm chi phí lưu trữ và download.
Nếu app còn nhỏ, chưa cần vội. Nếu hóa đơn storage/bandwidth bắt đầu tăng, hãy thiết kế migration sớm: lưu object key thay vì URL cứng, tách metadata khỏi file, dùng presigned URL, và bật cache đúng cách. Làm vậy, chuyển từ Supabase Storage sang Cloudflare R2 sẽ ít đau, ít downtime, và tiết kiệm rõ hơn khi sản phẩm lớn lên.