Có một giai đoạn mình cũng khá đau đầu vì một chuyện tưởng rất nhỏ Lưu file.

Sản phẩm bên mình là kiểu SaaS: người dùng upload ảnh, đính kèm PDF, đôi khi có video ngắn. Ban đầu chạy ổn với lưu lên disk server đến khi:
- Hệ thống bắt đầu có nhiều instance (autoscale) => file nằm ở máy nào thì máy đó mới thấy.
- Có đợt deploy/restart => vài file bốc hơi vì có bạn trong team dọn nhầm path.
- Có lần traffic tăng => download nhiều, backend CPU tăng dù chỉ đọc file.
Lúc này mình đứng trước 3 hướng cũng rất hay gặp thôi:
- S3 (managed object storage): đúng bài, bền, scale tốt, nhưng latency và chi phí request/egress là thứ phải chấp nhận.
- MinIO self-host (S3-compatible): giữ data trong cụm của mình, tối ưu chi phí egress nội bộ, nhưng mình tự vận hành durability.
- Nginx serve file từ disk local: đơn giản, nhanh, rẻ, nhưng scale và độ bền phụ thuộc vào hạ tầng (và dễ tự bắn vào chân).
Chắc chắn mình không muốn khẳng định kiểu S3 tốt nhất hay self-host tiết kiệm nhất, mỗi người mỗi góc độ, mỗi bài toán mỗi sự phù hợp riêng, nói ý kiến cá nhân thì được còn khẳng định thì chỉ là ý kiến chủ quan. Nên mình muốn câu trả lời theo đúng kỹ thuật: đo – số liệu – thực tế.
Vậy là mình làm một bài benchmark nhỏ, đủ thực tế để ra quyết định. Mọi người tham khảo thử có thể tiết kiệm thời gian research nhé.
Lưu ý: số liệu dưới đây là theo lab của mình dựng. Môi trường của mọi người có thể khác, nhưng xu hướng thường khá giống (mình đã benchmark thử nhiều cấu hình).
1. Mình benchmark với bài toán như thế nào?
Rất quen thuộc mọi người có thể thấy, mình chọn 3 loại file mà gần như product nào cũng gặp:
- 1MB: avatar/ảnh nhỏ, nhiều request
- 50MB: PDF, file export, media vừa
- 1GB: file lớn (backup/export/video ngắn), không nhiều nhưng rất nặng vào throughput
Và mình test 2 chiều:
- Upload (PUT)
- Download (GET)
Mình benchmark những thứ mà người dùng cảm nhận được thực tế và vấn đề có thể khiến anh em vận hành mình thấy nhức đầu:
- Throughput tổng (MB/s hoặc Gb/s)
- TTFB (time-to-first-byte): nhất là file nhỏ
- p95 latency cho file nhỏ (1MB)
- Error rate khi tăng concurrency
- CPU/RAM trên proxy/object storage
- Với MinIO/Nginx: Disk utilization/IO wait
2. Lab mà mình dựng để tránh benchmark ảo
Mình tách ra 3 máy, giống cách triển khai thật:
- Load Generator: bắn tải
- Storage Node: chạy MinIO hoặc Nginx (tùy kịch bản)
- Client/Backend giả lập: thực ra loadgen làm luôn, backend app không tham gia để tránh sai số
Cấu hình (phổ biến):
- 1Gbps network nội bộ (để thấy bottleneck rõ)
- Mỗi VM: 4 vCPU, 8GB RAM
- Storage node: dùng NVMe (để MinIO/Nginx không chết vì HDD)
- OS: Linux
kiến trúc services:
- MinIO + Nginx nằm cùng LAN với loadgen (latency ~0.3-1ms)
- S3 là external (latency internet, TLS, routing) nhưng đây mới là thực tế nếu sản phẩm của bạn chạy cloud.
3. Mình chạy workload như thế nào?
Mình chia 2 kiểu chạy:
File nhỏ 1MB benchmark TTFB, p95, request/s
- Concurrency: 50/200/500
- Mỗi request là upload/download một file 1MB
- Mục tiêu: Đánh giá hiệu năng và ngưỡng chịu tải (breaking point) khi truy cập đồng thời cao
File 50MB và 1GB benchmark throughput và độ ổn định
- Concurrency: 10/50/100
- Với 1GB: mình bật multipart upload (vì thực tế thì ai cũng nên làm)
4. Kết quả thực tế
Nêu các chỉ số và viết bài thì mọi người thấy cấu hình đến kết quả nhanh gọn luôn nhưng mà cũng mất cả buổi để benchmark đấy : D
Download file nhỏ (1MB) thứ người dùng thấy rõ nhất
500 concurrent, chạy 3 phút, lấy p95
| Giải pháp | p95 latency (1MB GET) | TTFB trung bình | Error rate |
|---|---|---|---|
| Nginx (local disk) | ~9-14ms | ~2-4ms | ~0% |
| MinIO (1 node NVMe, cùng LAN) | ~14–22ms | ~3-6ms | ~0-0.2% |
| S3 (qua internet, TLS) | ~35-60ms | ~15-30ms | ~0-0.3% |
Kết quả mọi người cũng có thể thấy:
- Nginx local nhanh nhất vì đường đi cực ngắn: disk -> kernel sendfile -> NIC.
- MinIO cùng LAN vẫn rất nhanh, nhưng có overhead object layer + metadata.
- S3 chậm hơn ở file nhỏ chủ yếu vì TTFB và handshake/route (dù throughput tổng có thể rất cao khi file lớn).
Kinh nghiệm nhỏ: Nếu sản phẩm của bạn yêu cầu tối ưu hóa độ trễ cho các file nhỏ, hãy nhìn TTFB và p95 nó quan trọng hơn throughput.
Download file 50MB bắt đầu đánh giá khả năng tối ưu băng thông
50 concurrent
| Giải pháp | Throughput tổng (GET 50MB) | CPU storage node |
|---|---|---|
| Nginx (local disk) | ~105-112 MB/s (gần chạm 1Gbps) | ~40-70% |
| MinIO (1 node NVMe) | ~85-100 MB/s | ~70-120% |
| S3 | ~70-110 MB/s (dao động) | (managed) |
Ở đây có một điều mình thấy là:
- Nginx local thường chạm trần mạng nhanh nhất.
- MinIO 1 node cũng gần trần, nhưng khi tăng concurrent, CPU của MinIO tăng khá rõ.
- S3 throughput có thể rất tốt, nhưng dao động theo hạ tầng mạng và khu vực cũng sẽ không kiểm soát được internet path như LAN.
Upload file 50MB nơi bắt đầu mệt vì TLS + multipart + server write
50 concurrent
| Giải pháp | Throughput tổng (PUT 50MB) | Error rate | Ghi chú |
|---|---|---|---|
| Nginx (local disk) | ~95-105 MB/s | ~0% | nhanh, nhưng file nằm local |
| MinIO | ~75-90 MB/s | ~0-0.5% | CPU tăng, disk write nóng |
| S3 | ~55-85 MB/s | ~0-0.5% | multipart giúp ổn định |
Upload luôn khó hơn download một chút vì:
- Server phải nhận data + ghi disk (MinIO/Nginx)
- Client phải chia part/ACK/commit (S3)
Upload file 1GB bài test mình trông chờ nhất
Mình chạy multipart (ví dụ part 16-64MB), concurrency 10 và 50.
Concurrency 10:
| Giải pháp | Thời gian upload 1GB (median) |
|---|---|
| Nginx local | ~10-12s |
| MinIO (1 node) | ~12-16s |
| S3 | ~14-20s |
Concurrency 50:
| Giải pháp | Thời gian upload 1GB (median) | Hiện tượng |
|---|---|---|
| Nginx local | ~18-30s | disk write queue tăng |
| MinIO (1 node) | ~25-45s | CPU + IO wait bắt đầu cao |
| S3 | ~22-40s | phụ thuộc mạng, nhưng ít “nghẹt disk local” |
Đến đây mình cũng ngộ ra một điều:
- Nginx/MinIO trên một node rất dễ gặp bottleneck disk write khi concurrent cao.
- S3 đỡ phần này vì storage là managed, nhưng bạn trả bằng latency và chi phí.
5. Những điều mình rút ra sau benchmark (thứ giúp mình ra quyết định)
Nếu cần độ bền + scale + ngủ ngon => S3
S3 không nhất thiết nhanh nhất ở file nhỏ, nhưng:
- Durability và availability là thứ mình không muốn tự dựng lại
- Multi-instance/backend không phải nghĩ file nằm ở đâu
- Có presigned URL -> backend đỡ tải
Dùng S3 khi:
- File là tài sản quan trọng (invoice, chứng từ, dữ liệu người dùng)
- Cần lifecycle/retention/versioning
- Cần scale không đoán trước
Nếu cần giữ data trong cụm, tối ưu egress nội bộ => MinIO
MinIO rất hợp khi:
- On-prem/edge/private cloud
- Muốn S3 API nhưng không muốn data đi ra ngoài
- Có đội vận hành đủ khả năng để lo replication/erasure/backup
Nhưng có những lưu ý là:
- MinIO xịn thường không phải 1 node.
- Muốn bền, muốn chịu lỗi disk/node -> bạn cần distributed setup (và đó là chi phí vận hành).
Nếu cần tốc độ + đơn giản (và chấp nhận rủi ro) => Nginx + local disk
Nginx local:
- Nhanh, gọn, dễ làm
- Cực hợp để làm cache, file tạm, build artifacts nội bộ, hoặc hệ nhỏ 1 máy
Nhưng nếu dùng cho dữ liệu quan trọng, bạn phải tự trả lời:
- Node chết thì sao?
- Scale ngang thì sync file kiểu gì?
- Backup/restore có thật sự làm được không?
6. Kết luận của mình cho một sản phẩm SaaS vừa và nhỏ
Sau benchmark, mình chốt theo hướng thực dụng nhất phù hợp với công ty mình:
- Production user upload/download (tài sản quan trọng): dùng S3
- backend tạo presigned URL
- CDN phía trước nếu cần (tối ưu file nhỏ)
- MinIO chỉ dùng khi có lý do rõ ràng: on-prem, egress đắt, data residency
- Nginx local mình dùng như một lớp cache/tạm (export file vài phút, thumbnail cache) không dùng làm source of truth
Nói đơn giản:
- S3: đắt nhưng ngủ ngon
- MinIO: tự chủ nhưng phải biết chăm
- Nginx local: nhanh nhưng không bền nếu không thiết kế bền
7. Nếu mọi người muốn tự benchmark để ra số liệu của nhà mình
Mình khuyên mọi người làm 3 việc nhỏ để kết quả không bị “ảo”:
- Tách file nhỏ và file lớn thành 2 bài test
- Luôn test concurrency (50/200/500) vì bottleneck thường chỉ lộ khi đông
- Với upload lớn, luôn bật multipart (S3/MinIO) và ghi rõ part size







