Mô hình tải tệp truyền thống thường đi theo đường vòng: Client đẩy file 50MB–200MB lên web server (Node.js/Python), server lưu tạm vào bộ nhớ RAM hoặc ổ cứng đệm, rồi mới đẩy tiếp lên Object Storage (S3, Cloudflare R2).
Khi chỉ cần 10 người dùng cùng upload video hoặc file nén dung lượng lớn cùng lúc, RAM của máy chủ sẽ chạm trần ngay lập tức (OOM Crash), trong khi toàn bộ băng thông mạng bị giữ cứng khiến các request API thông thường bị treo.
Dưới đây là kỹ thuật Direct-to-Storage (Tải thẳng từ trình duyệt lên Storage) qua Presigned URL, biến web server thành “người cấp vé” thay vì làm “phu khuân vác”.
Luồng xử lý: Truyền thống vs. Direct Upload
Thay vì để dữ liệu nhị phân (binary) chạy qua ứng dụng, hãy để trình duyệt giao tiếp trực tiếp với Object Storage:
[Mô hình truyền thống (Server nghẽn nặng)]
Client ──► [File 100MB qua Web Server] ──► [Object Storage] (Server tốn gấp đôi băng thông)
[Mô hình Direct Upload (Server thảnh thơi)]
1. Client ──► Xin Presigned URL (< 20ms) ──► Web Server (Chỉ ký vé, không nhận file)
2. Client ──► Đẩy thẳng File 100MB ──────────► S3 / Cloudflare R2 (Băng thông do Storage gánh)
3. Client ──► Báo hoàn tất (Metadata) ─────► Web Server ghi DB
Bước 1: Backend sinh Presigned URL an toàn (Dưới 30ms)
Web server chỉ làm một nhiệm vụ duy nhất: kiểm tra quyền người dùng, sinh ra một đường dẫn có chữ ký số tạm thời (giới hạn thời gian sống khoảng 5–15 phút), và chỉ định rõ loại file được phép tải lên.
Ví dụ sử dụng AWS SDK v3 (tương thích cả S3 và Cloudflare R2):
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const s3 = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT, // Cloudflare R2 hoặc S3 endpoint
credentials: {
accessKeyId: process.env.STORAGE_ACCESS_KEY!,
secretAccessKey: process.env.STORAGE_SECRET_KEY!,
},
});
export async function createUploadUrl(userId: string, filename: string, mimeType: string) {
// Tạo path ngẫu nhiên tránh ghi đè và ẩn danh hóa tên file
const key = `uploads/${userId}/${Date.now()}-${filename}`;
const command = new PutObjectCommand({
Bucket: process.env.STORAGE_BUCKET_NAME,
Key: key,
ContentType: mimeType, // Khóa cứng MIME type, ngăn client tải sai định dạng
});
// URL chỉ có hiệu lực trong 300 giây (5 phút)
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
return { uploadUrl, key };
}
Bước 2: Client tải trực tiếp dữ liệu lên Storage
Ở phía trình duyệt (hoặc Mobile App), dùng lệnh fetch hoặc XMLHttpRequest thông thường để đẩy trực tiếp file lên link vừa xin được bằng phương thức PUT:
async function uploadFileDirectly(file) {
// 1. Xin URL cấp phép từ Server
const res = await fetch("/api/upload-ticket", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename: file.name, mimeType: file.type })
});
const { uploadUrl, key } = await res.json();
// 2. Bắn trực tiếp lên Cloudflare R2 / S3
const uploadRes = await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file
});
if (!uploadRes.ok) throw new Error("Upload lên storage thất bại");
// 3. Thông báo cho Server lưu key file vào cơ sở dữ liệu
await fetch("/api/save-file-metadata", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ fileKey: key, size: file.size })
});
}
Ba chốt chặn bảo mật không thể bỏ qua
- Cấu hình CORS nghiêm ngặt trên Bucket: Chỉ cho phép domain của bạn gửi request
PUTlên bucket. Tuyệt đối không đểAllowedOrigins: ["*"]khiến hacker có thể dùng endpoint storage của bạn để làm máy chủ chứa file lậu. - Khóa Content-Length ở phía Storage Policy: Presigned URL cơ bản không tự giới hạn dung lượng file nếu không cấu hình kỹ. Với S3/R2, bạn nên dùng
createPresignedPost(POST Policy) để khóa cứng kích thước file tối đa (ví dụ:["content-length-range", 1048576, 52428800]– từ 1MB đến 50MB). - Quét mã độc ngầm (Async Antivirus Scan): Sau khi file lên storage, hãy dùng Event Notification kích hoạt một Worker ngầm kiểm tra định dạng nhị phân thực tế và quét virus (ClamAV) trước khi gắn trạng thái “File an toàn” trong cơ sở dữ liệu.
Đẩy việc vận chuyển file nặng sang đúng hạ tầng chuyên dụng là một trong những quyết định tái cấu trúc mang lại tỷ suất lợi ích cao nhất. Web server của bạn sẽ luôn nhẹ tênh, giữ vững tốc độ phản hồi sub-100ms ngay cả khi người dùng đang đồng loạt tải lên hàng chục gigabyte dữ liệu.


