Upload File

Hệ thống cần xử lý upload file tầm 500MB - 1GB, bạn sẽ thiết kế thế nào để đảm bảo tính ổn định ?

==> Giải pháp tốt nhất là Chunked Upload + Pre-signed URL.

🎯 Mục tiêu chính:

  1. Xử lý upload file lớn (500MB - 1GB) một cách ổn định.

  2. Không gây time-out ở phía client/backend.

  3. Không gây overload hệ thống.

  4. Hỗ trợ resume upload nếu bị ngắt giữa chừng.

  5. Tối ưu trải nghiệm người dùng (UX) và tài nguyên hệ thống.

🧱 Tổng quan kiến trúc đề xuất:

Client (browser/app)

Reverse Proxy (Nginx / CDN)

Upload API (Spring Boot)

File Storage (S3, GCS, MinIO, NAS...)
    +
Database (metadata)

🧠 Chi tiết kỹ thuật từng phần:

1. Upload kiểu trực tiếp (Direct Upload to Storage) 🔄

Đối với file lớn, không nên truyền hết qua backend → dễ gây overload. Thay vào đó:

  • Backend chỉ tạo pre-signed URL (S3, GCS, MinIO...)

  • Client upload trực tiếp lên storage thông qua URL đó (giảm tải cho backend).

  • Sau khi upload xong, client gửi callback để thông báo hoàn tất.

✅ Ưu điểm: Giảm tải backend, tăng tốc độ upload, không timeout.


2. Hỗ trợ Multipart / Chunked Upload 📦

Để xử lý file lớn & đảm bảo resume khi mạng bị gián đoạn:

  • Client chia file ra từng chunk nhỏ (5-10MB)

  • Gửi từng chunk lên (song song hoặc tuần tự)

  • Backend/storages sẽ merge lại hoặc lưu metadata từng phần.

🔧 Ví dụ: S3 hỗ trợ Multipart Upload API; Google Drive cũng hoạt động tương tự.


3. Tạm lưu file ở Local nếu cần (Optional)

Trong trường hợp cần xử lý file (scan virus, convert, nén...), có thể lưu tạm ở local hoặc mounted volume:

  • /tmp/uploads/sessionId/part1.tmp

  • Sử dụng Streaming API để xử lý file mà không cần load toàn bộ vào RAM.

Dùng InputStream / StreamingResponseBody trong Spring Boot để stream.

4. Quản lý trạng thái upload (Resume, Fail-safe)

  • Tạo bảng upload_session trong DB:

sqlCopyEditid | user_id | file_name | total_chunks | received_chunks | status | created_at
  • Mỗi lần client gửi chunk, cập nhật trạng thái.

  • Nếu bị mất kết nối, client có thể resume từ chunk chưa gửi.


5. Timeout & Stability

  • Reverse proxy như Nginx phải cấu hình:

    • client_max_body_size

    • proxy_read_timeout / proxy_send_timeout

  • Spring Boot cấu hình:

propertiesCopyEditspring.servlet.multipart.max-file-size=2GB
spring.servlet.multipart.max-request-size=2GB
server.tomcat.max-swallow-size=-1

🛡️ Một số điểm cần lưu ý:

Yếu tố
Cách xử lý

Quá tải RAM

Không lưu toàn bộ file vào memory (streaming)

Mất kết nối mạng

Hỗ trợ resume upload

Timeout HTTP

Dùng direct upload hoặc websocket/message queue

Quét virus

Scan sau khi upload xong

Kiểm tra loại file

Dựa vào header & magic bytes, không chỉ nhìn file extension

Tối ưu UX

Progress bar, tốc độ, resume, retry


📌 Công nghệ gợi ý:

Tầng
Công nghệ

Client

HTML5 File API + JavaScript (chunked upload, retry logic)

API

Spring Boot (REST + streaming), hoặc WebSocket nếu cần push

Storage

Amazon S3 / MinIO / GCS

CDN (optional)

CloudFront / Nginx cache

Background job

Redis queue + worker (convert, scan...)


🔄 Flow Tổng Thể Upload File Lớn (Chunked Upload + Backend-Storage Architecture)


📦 1. Client khởi tạo phiên upload

  • API: POST /api/files/init-upload

  • Payload: { filename, size, contentType }

  • Backend xử lý:

    • Sinh ra uploadId, lưu metadata (filename, user, size, status = INITIATED)

    • Trả về uploadId + chunkSize + thông tin định dạng

    • (Tùy chọn): trả về pre-signed URLs (nếu dùng S3/MinIO)

Tác dụng: quản lý session, theo dõi tiến độ, kiểm tra lỗi.


📤 2. Client chia file thành các chunk và upload từng phần

  • File sẽ được chia thành từng chunk (VD: 5MB/chunk)

  • Client gửi tuần tự hoặc song song:

POST /api/files/upload-chunk
Headers:
  Content-Range: bytes 0-5242879/1048576000
Body:
  binary data (chunk)
Query params:
  uploadId, chunkIndex
  • Backend lưu chunk vào:

    • Tạm thời: local/temp folder (disk, Redis if meta)

    • Hoặc trực tiếp đẩy từng chunk vào object storage (S3/MinIO/GCS)


📥 3. Client gọi hoàn tất upload

  • Sau khi upload xong toàn bộ chunk:

POST /api/files/complete-upload
Payload: { uploadId }
  • Backend sẽ:

    • Validate đủ chunk

    • Gộp các chunk lại (nếu local storage)

    • Hoặc trigger storage (nếu S3 multipart upload complete)

    • Tạo metadata: path, size, MIME, checksum...

    • Cập nhật trạng thái uploadId → COMPLETED


⚠️ 4. Exception & Resilience Handling

Trường hợp lỗi
Cách xử lý ở backend

⏱️ Timeout / mạng yếu

Cho phép client retry chunk upload từng phần

💔 Upload bị gián đoạn

Dựa vào uploadId, resume từ missing chunks

❌ Thiếu chunk khi complete

Trả lỗi 409 Conflict, kèm danh sách chunk thiếu

💾 Disk full (local temp chunk)

Cảnh báo sớm bằng monitoring + chuyển qua S3

🔒 Token expire trong pre-signed URL

Re-request pre-signed URL theo chunkIndex

💣 File quá giới hạn

Validate tại init-upload, trả về 413 Payload Too Large

📎 Duplicate upload

Check hash (MD5/SHA-256), hỗ trợ dedup nếu cần


5. Tối ưu & bảo trì

Mục tiêu
Giải pháp

Hiệu năng

Sử dụng parallel chunk upload (ở client + backend thread pool)

Memory tiết kiệm

Stream trực tiếp, không giữ cả file trong RAM

Tăng độ ổn định

Quản lý trạng thái từng chunk (upload progress, retry logic)

Lưu trữ tạm

Xoá chunk sau 24h nếu không hoàn thành (sử dụng scheduler)

File integrity

Dùng checksum (MD5/SHA256) để xác nhận sau khi merge


🗃️ Công nghệ khuyến nghị

Thành phần
Giải pháp phù hợp

Storage

Amazon S3, MinIO, GCP Storage, NFS

Chunk tracking

Redis (tạm thời), PostgreSQL (metadata)

Streaming

InputStream, FileChannel, MultipartFile.transferTo()

Large request

spring.servlet.multipart.max-file-size=-1 (tắt limit)

Retry logic

Client-side + Backend retry handler


Khuyến khích sử dụng S3 + Kafka để scale tốt nhất.

Last updated