Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 50 — File Storage — S3 và Alternatives

Architecture Decision-Making Bài 50/60

Mở đầu — vì sao bài này quan trọng

Trong gần như mọi hệ thống bạn từng xây, sẽ đến lúc phải trả lời một câu hỏi tưởng chừng đơn giản: "Lưu cái file này ở đâu?" Ảnh đại diện người dùng, hóa đơn PDF, video khóa học, file backup database, model machine learning nặng vài GB — tất cả đều là dữ liệu, nhưng chúng khác hẳn dữ liệu bạn đặt trong bảng SQL. Chúng lớn, chúng bất biến (một khi ghi xong thường không sửa nữa), và chúng cần được truy cập từ nhiều nơi cùng lúc.

Nhiều kỹ sư trẻ mặc định lưu file ngay trên đĩa của server ứng dụng, hoặc nhét cả file vào cột BLOB trong MySQL. Cách này chạy được — cho đến khi bạn scale ra nhiều server, cho đến khi ổ đĩa đầy lúc 2 giờ sáng, cho đến khi database backup phình lên 200 GB chỉ vì ảnh sản phẩm. Đây chính là lúc quyết định kiến trúc về file storage / object storage trở thành một trade-off thực sự, chứ không phải chuyện "cứ lưu đại đi".

Bài này giúp bạn nhìn file storage như một quyết định kiến trúc có ý thức: hiểu bản chất object storage, phân biệt nó với block storage và file storage, biết khi nào chọn S3, khi nào chọn giải pháp thay thế, và biết đưa ra quyết định dựa trên use case — upload của người dùng, static asset, backup/archive, hay artifact cho ML pipeline. Đây là kỹ năng nền tảng: hầu hết các bài sau (CDN, search, data pipeline) đều dựa trên một tầng lưu trữ file được chọn đúng ngay từ đầu.

Khái niệm cốt lõi

Ba kiểu lưu trữ: Block, File, Object

Trước khi nói về S3, bạn cần phân biệt ba mô hình lưu trữ, vì chọn nhầm mô hình là sai lầm kiến trúc phổ biến nhất.

Block storage chia đĩa thành các block có kích thước cố định, không hiểu gì về "file". Đây là thứ nằm dưới ổ cứng ảo của một máy chủ (AWS EBS, Google Persistent Disk). Nó nhanh, độ trễ thấp, phù hợp cho database và OS. Nhưng nó gắn với một máy — bạn không thể cho 50 server cùng đọc ghi một EBS volume một cách tự nhiên.

File storage tổ chức dữ liệu theo cây thư mục quen thuộc (folder, file, đường dẫn), truy cập qua giao thức như NFS hoặc SMB (AWS EFS, Azure Files). Nhiều máy có thể mount cùng lúc. Phù hợp khi ứng dụng legacy cần một "ổ đĩa chia sẻ". Nhược điểm: đắt, khó scale đến hàng tỷ file, và độ trễ tăng khi số file lớn.

Object storage là mô hình mà S3 phổ biến hóa. Mỗi file là một "object" gồm: dữ liệu, một khóa (key, giống đường dẫn nhưng thực chất là chuỗi phẳng), và metadata. Bạn tương tác qua HTTP API (PUT, GET, DELETE) chứ không mount như ổ đĩa. Object storage được thiết kế để chứa dung lượng gần như vô hạn, độ bền cực cao (S3 quảng cáo 99.999999999% — "11 số 9"), và truy cập từ bất kỳ đâu qua URL. Đổi lại, bạn không thể sửa một phần file (muốn đổi là ghi đè cả object), và độ trễ cao hơn block storage.

Quy tắc ngón tay cái: database → block storage; ổ chia sẻ cho app cũ → file storage; còn lại (upload, asset, backup, artifact) → object storage.

S3 và mô hình object storage

Amazon S3 (Simple Storage Service) là chuẩn de facto. Dữ liệu tổ chức thành bucket (như một namespace toàn cục), bên trong là các object định danh bằng key. Vài khái niệm quyết định kiến trúc bạn phải nắm:

  • Storage class: S3 có nhiều lớp lưu trữ với trade-off giữa giá lưu và giá truy cập. Standard cho dữ liệu nóng, truy cập thường xuyên. Standard-IA (Infrequent Access) rẻ hơn khi lưu nhưng tính phí mỗi lần đọc. Glacier / Glacier Deep Archive cực rẻ để archive nhưng lấy dữ liệu ra mất từ vài phút đến vài giờ. Chọn sai lớp là nguyên nhân số một khiến hóa đơn phình to.
  • Lifecycle policy: tự động chuyển object từ Standard sang IA sau 30 ngày, sang Glacier sau 90 ngày, hoặc xóa sau 365 ngày. Đây là công cụ tiết kiệm chi phí quan trọng nhất.
  • Presigned URL: thay vì cho client gọi API server rồi server tải file lên S3, bạn tạo một URL có chữ ký tạm thời để client upload/download trực tiếp lên S3. Điều này giảm tải cực lớn cho app server.
  • Versioning và immutability (Object Lock): giữ nhiều phiên bản, hoặc khóa object không cho xóa/sửa — quan trọng cho backup tuân thủ (compliance) hoặc chống ransomware.

Các lựa chọn thay thế S3

S3 không phải lựa chọn duy nhất, và với nhiều team Việt Nam, không phải lựa chọn tối ưu về chi phí hay độ trễ.

  • Google Cloud Storage (GCS)Azure Blob Storage: tương đương S3, chọn theo hệ sinh thái cloud bạn đang dùng.
  • Cloudflare R2Backblaze B2: điểm bán hàng lớn nhất là không tính phí egress (phí băng thông khi tải dữ liệu ra). Nếu bạn phục vụ nhiều video/ảnh cho người dùng cuối, phí egress của S3 có thể vượt cả phí lưu trữ. R2 tương thích S3 API nên gần như thay thế trong suốt.
  • MinIO: object storage mã nguồn mở, tương thích S3 API, tự host trên server của bạn. Phù hợp khi cần chủ quyền dữ liệu (dữ liệu phải nằm ở Việt Nam theo Nghị định 53), on-prem, hoặc air-gapped.
  • Object storage của nhà cung cấp trong nước (VNG Cloud vStorage, Viettel IDC, FPT Cloud): thường tương thích S3 API, độ trễ thấp cho người dùng Việt Nam, và xuất hóa đơn VND — điều mà bộ phận kế toán và tuân thủ dữ liệu nội địa rất thích.
Điểm mấu chốt về mặt kiến trúc: hãy code với S3 SDK (hoặc thư viện tương thích S3), và cấu hình endpoint qua biến môi trường. Vì hầu hết nhà cung cấp đều nói "ngôn ngữ S3", bạn giữ được khả năng đổi backend mà gần như không sửa code — một quyết định giảm lock-in rất rẻ.

Tình huống thực tế

Tình huống 1: Nền tảng thương mại điện tử scale ra nhiều server

Một startup TMĐT ở TP.HCM ban đầu chạy một server duy nhất. Ảnh sản phẩm do người bán upload được lưu vào thư mục /var/www/uploads trên chính server đó. Mọi thứ ổn cho tới khi traffic tăng, họ đưa lên hai server sau một load balancer.

Ngay lập tức lỗi xuất hiện: người bán upload ảnh, request rơi vào server A và ảnh lưu trên A. Nhưng khi khách xem sản phẩm, request rơi vào server B — không có ảnh, hiện lỗi 404. Họ vá tạm bằng cách rsync ảnh giữa hai server mỗi phút, nhưng đây là quả bom hẹn giờ: dữ liệu không nhất quán, và không thể scale lên server thứ ba.

Giải pháp kiến trúc đúng: chuyển toàn bộ upload sang object storage. Họ chọn Cloudflare R2 vì phí egress bằng 0 (họ phục vụ hàng triệu lượt xem ảnh mỗi ngày). Client upload qua presigned URL trực tiếp lên R2, app server chỉ lưu key vào database. Mọi server đọc cùng một nguồn, thêm bao nhiêu server cũng không sao.

Bài học: Ngay khi bạn có nhiều hơn một server ứng dụng, lưu file trên đĩa local là một anti-pattern. Object storage tách trạng thái file ra khỏi server, biến app server thành stateless — điều kiện tiên quyết để scale ngang.

Tình huống 2: Hóa đơn hết đắt vì chọn nhầm storage class

Một công ty fintech ở Singapore lưu tất cả log giao dịch và bản sao chứng từ (KYC document) trên S3 Standard. Sau 18 tháng, họ tích lũy 80 TB. Hóa đơn S3 chạm khoảng 1.900 USD/tháng chỉ riêng tiền lưu trữ, dù 95% dữ liệu cũ hơn 90 ngày gần như không bao giờ được đọc lại — trừ khi có kiểm toán.

Đội SRE thiết lập lifecycle policy: object mới giữ ở Standard 30 ngày, chuyển sang Standard-IA đến 90 ngày, rồi sang Glacier Deep Archive. Với dữ liệu KYC bắt buộc giữ 7 năm theo luật, Glacier Deep Archive giảm chi phí lưu xuống còn khoảng 1/23 so với Standard. Kết quả: hóa đơn lưu trữ tụt xuống dưới 250 USD/tháng. Họ chấp nhận trade-off rằng khi cần lấy chứng từ cho kiểm toán, phải chờ vài giờ để restore — hoàn toàn chấp nhận được cho dữ liệu archive.

Bài học: Object storage rẻ ở lớp đúng, đắt ở lớp sai. Phân loại dữ liệu theo tần suất truy cập (nóng / ấm / lạnh) và để lifecycle policy tự động di chuyển. Đừng để dữ liệu lạnh nằm ở lớp nóng.

Tình huống 3: Chủ quyền dữ liệu và ML artifact với MinIO

Một ngân hàng số tại Việt Nam xây pipeline machine learning để chấm điểm tín dụng. Pipeline sinh ra hàng loạt artifact: dataset đã xử lý (vài trăm GB), model đã train (mỗi phiên bản 2–5 GB), và feature store. Theo quy định về an toàn dữ liệu cá nhân và Nghị định 53, dữ liệu khách hàng phải lưu trong lãnh thổ Việt Nam, và ngân hàng muốn kiểm soát hoàn toàn hạ tầng.

Đưa lên S3 ở region nước ngoài là không được phép. Họ triển khai MinIO trên cụm server trong data center riêng ở Hà Nội. MinIO tương thích S3 API, nên toàn bộ code pipeline (viết bằng boto3) chỉ cần đổi endpoint là chạy. Data scientist versioning model bằng cách ghi vào các key có prefix theo thời gian, và pipeline đọc lại đúng artifact cho mỗi lần train/serve.

Bài học: Object storage không đồng nghĩa với "phải dùng cloud công khai". Khi yêu cầu là chủ quyền dữ liệu, tuân thủ pháp lý, hoặc on-prem, một giải pháp tương thích S3 như MinIO cho bạn cùng mô hình lập trình mà vẫn giữ dữ liệu trong tầm kiểm soát. Việc chọn S3-compatible API ngay từ đầu chính là thứ giúp họ chuyển đổi trong suốt.

Hướng dẫn từng bước

Đây là quy trình ra quyết định file storage cho một hệ thống mới:

  • Liệt kê các loại dữ liệu file và use case. Với mỗi loại, ghi rõ: kích thước điển hình, tần suất ghi, tần suất đọc, ai đọc (người dùng cuối hay hệ thống nội bộ), và yêu cầu tuân thủ. Bốn nhóm phổ biến: upload của user (avatar, document), static asset (ảnh, video), backup/archive, và artifact của ML/data pipeline.
  • Chọn mô hình lưu trữ. Nếu là file cho ứng dụng, câu trả lời gần như luôn là object storage. Chỉ dùng file storage (EFS) khi có app legacy đòi hỏi filesystem POSIX; chỉ dùng block storage cho database và OS.
  • Xác định yêu cầu chủ quyền dữ liệu. Có dữ liệu cá nhân của người Việt Nam không? Nếu có, cân nhắc nhà cung cấp trong nước hoặc MinIO tự host để tuân thủ Nghị định 53. Điều này thu hẹp lựa chọn ngay từ đầu.
  • Ước tính pattern chi phí. Dữ liệu chủ yếu là lưu hay là phục vụ ra ngoài? Nếu egress lớn (video, ảnh cho hàng triệu người dùng), ưu tiên R2/B2 (egress miễn phí) hoặc CDN đứng trước. Nếu chủ yếu lưu trữ ít đọc, tối ưu bằng storage class rẻ và lifecycle.
  • Thiết kế đường upload. Ưu tiên presigned URL để client upload/download trực tiếp, tránh dồn băng thông qua app server. Server chỉ cấp phép và lưu metadata (key, size, content-type, owner) vào database.
  • Thiết lập lifecycle và bảo mật ngay từ đầu. Cấu hình bucket private mặc định, bật versioning nếu cần khôi phục, thêm lifecycle policy chuyển lớp/xóa tự động, và bật encryption at rest.
  • Trừu tượng hóa qua S3 API và biến môi trường. Code với S3 SDK, đặt endpoint/credential trong config. Điều này giữ cửa mở để đổi nhà cung cấp mà không viết lại.

Lỗi thường gặp & mẹo

Lỗi: Lưu file vào database dưới dạng BLOB. Database phình to, backup chậm và đắt, cache kém hiệu quả. Database nên lưu metadata và key trỏ tới object storage, không lưu bytes của file.

Lỗi: Bucket để public toàn bộ. Đây là nguyên nhân kinh điển của rò rỉ dữ liệu. Mặc định private, dùng presigned URL cho truy cập tạm thời, hoặc đặt CDN với token trước bucket.

Lỗi: Bỏ qua phí egress khi ước tính chi phí. Nhiều team chỉ nhìn giá lưu trữ mỗi GB. Với ứng dụng phục vụ nhiều media, phí băng thông ra thường mới là khoản đắt nhất. Luôn tính cả egress vào bài toán.

Lỗi: Dùng key tuần tự dạng 0001, 0002... cho lượng object khổng lồ. Một số hệ thống phân vùng theo prefix key; key quá đều có thể tạo hotspot. Prefix có tính phân tán (ví dụ thêm hash ở đầu) giúp phân phối tải tốt hơn ở quy mô rất lớn.

Mẹo: Đặt CDN trước object storage cho static asset. Object storage tối ưu cho độ bền, không phải độ trễ thấp toàn cầu; CDN cache ở edge sẽ nhanh hơn nhiều và còn giảm egress từ bucket.

Mẹo: Lưu metadata content-type và content-disposition đúng khi upload, để trình duyệt hiển thị/tải file đúng cách thay vì tải nhầm định dạng.

Mẹo: Với ML artifact, versioning bằng prefix theo thời gian hoặc theo commit hash, kèm một file manifest ghi rõ phiên bản nào đang được serve. Điều này giúp reproduce và rollback model.

Bài tập thực hành

  • Phân loại dữ liệu: Lấy một hệ thống bạn đang làm (hoặc một nền tảng học trực tuyến như Vietnamcos). Liệt kê tất cả loại file, phân vào bốn nhóm (upload, static asset, backup, ML artifact), và với mỗi nhóm ghi: kích thước, tần suất đọc/ghi, yêu cầu tuân thủ.
  • Chọn storage và biện luận: Với mỗi nhóm ở bài 1, chọn một storage class và (nếu cần) nhà cung cấp. Viết một đoạn ngắn kiểu ADR giải thích quyết định — đặc biệt là trade-off chi phí lưu so với chi phí truy cập.
  • Thiết kế luồng upload: Vẽ sơ đồ luồng upload avatar bằng presigned URL: client xin URL, server cấp phép và trả URL, client PUT trực tiếp lên object storage, server nhận callback/xác nhận và lưu key vào DB. Chỉ ra dữ liệu nào đi qua server và dữ liệu nào không.
  • Tính chi phí: Giả sử 10 TB dữ liệu, trong đó 1 TB nóng (đọc hằng ngày) và 9 TB lạnh (gần như không đọc), phục vụ 5 TB egress/tháng. So sánh sơ bộ chi phí giữa (a) toàn bộ S3 Standard, (b) S3 Standard + Glacier với lifecycle, (c) R2. Rút ra kết luận nào rẻ nhất và vì sao.

Tóm tắt

File storage là một quyết định kiến trúc thật sự, không phải chuyện vặt. Hãy nhớ những điểm cốt lõi:

  • Phân biệt block / file / object storage; với hầu hết file của ứng dụng, câu trả lời là object storage.
  • S3 là chuẩn de facto, nhưng có nhiều lựa chọn thay thế: GCS/Azure (theo hệ sinh thái), R2/B2 (egress miễn phí), MinIO và nhà cung cấp nội địa (chủ quyền dữ liệu, tuân thủ Nghị định 53, hóa đơn VND).
  • Quyết định dựa trên use case: upload người dùng, static asset, backup/archive, ML artifact — mỗi loại có pattern truy cập và yêu cầu chi phí khác nhau.
  • Tối ưu chi phí bằng storage class đúng + lifecycle policy, và luôn tính cả phí egress, không chỉ phí lưu trữ.
  • Dùng presigned URL để client làm việc trực tiếp với storage, giữ app server stateless và scale ngang được.
  • Code với S3-compatible API để giảm lock-in — đây là quyết định rẻ mà giữ được nhiều lựa chọn về sau.
Chọn đúng tầng lưu file ngay từ đầu sẽ giúp bạn tiết kiệm rất nhiều đau đớn khi hệ thống lớn lên — và là nền móng cho các quyết định về CDN, search và data pipeline ở những bài tiếp theo.