MinIO — kiến trúc và cơ chế hoạt động

MinIO là một object storage (kho lưu object — file kèm metadata, đọc/ghi qua HTTP) nói đúng giao thức S3 của AWS. Bạn tự cài trên máy của mình, ứng dụng dùng SDK S3 bình thường, không cần biết đằng sau là AWS hay MinIO.

Bài này trả lời được câu "giải thích kiến trúc MinIO" ở mức đi sâu: object nằm ở đâu trên đĩa, tại sao mất 2 ổ vẫn đọc được, khi nào thì mất dữ liệu thật. Toàn bộ số đo bên dưới lấy từ lab chạy thật trên máy này — MinIO RELEASE.2025-09-07T16-13-09Z (Go 1.24.6, Linux amd64), client mc RELEASE.2025-08-13T08-35-41Z, hai cụm: một cụm 4 ổ và một cụm 24 ổ.

Phải nói ngay khi phỏng vấn (tình hình 2026): bản open-source của MinIO đã bị archive (đóng băng, không bảo trì nữa). Kiểm chứng ngày 25/09/2026:

  • https://dl.min.io/server/minio/release/linux-amd64/minio trả về HTTP 410 Gone kèm dòng "The open-source MinIO Server, MinIO Client (mc) and MinIO KES projects are archived and no longer maintained… does not provide security updates".
  • GitHub API repos/minio/minio trả "archived": true, license vẫn AGPLv3.
  • docker pull minio/minio → pull access denied; image cũng bị rút khỏi Docker Hub.
  • Bản còn tải được là binary đính kèm release cũ trên GitHub (bản tôi dùng cho lab này).
  • Laravel 13 docs đã đổi ví dụ S3 local trong Sail từ MinIO sang RustFS.

Sản phẩm thương mại MinIO AIStor vẫn phát triển. Nên câu trả lời chuẩn khi được hỏi "có nên dùng MinIO không" trong năm 2026 là: kiến trúc vẫn là bài học hay và cụm đang chạy vẫn chạy, nhưng dự án mới thì phải cân nhắc AIStor (trả tiền), hoặc S3/R2, hoặc bản thay thế như RustFS/Garage/SeaweedFS — xem phần trade-off bên dưới.

1. Nó là gì / chạy thế nào

1.1. Một tiến trình, không có node quản lý metadata

Điểm kiến trúc quan trọng nhất, và cũng là điểm interviewer hay đào: MinIO không có metadata server. Không có node "master" giữ bảng tra cứu object nằm ở đâu, không có database phụ, không có etcd cho dữ liệu. So với Ceph (có MON + OSD map) hay HDFS (có NameNode), đây là khác biệt lớn:

1.2. Bốn tầng: drive → erasure set → pool → cluster

flowchart TB
  subgraph CL["Cluster (một tên miền, một endpoint S3)"]
    subgraph P1["Pool 1 — 24 ổ, không đổi sau khi format"]
      subgraph ES1["Erasure set 1 — 12 ổ"]
        D1["drive 1..12"]
      end
      subgraph ES2["Erasure set 2 — 12 ổ"]
        D2["drive 13..24"]
      end
    end
    subgraph P2["Pool 2 — thêm sau khi hết chỗ"]
      D3["drive 25..48"]
    end
  end
  OBJ["PUT /demo/file-c.txt"] -->|"hash tên object"| ES2

Lab thật — 24 thư mục ổ, MinIO tự chia:

INFO: Formatting 1st pool, 2 set(s), 12 drives per set.

┌──────┬────────────────────────┬─────────────────────┬──────────────┐
│ Pool │ Drives Usage           │ Erasure stripe size │ Erasure sets │
│ 1st  │ 48.0% (total: 2.3 TiB) │ 12                  │ 2            │
└──────┴────────────────────────┴─────────────────────┴──────────────┘

24 drives online, 0 drives offline, EC:4

MinIO tự chọn kích thước set (ở đây 12) và parity mặc định (EC:4 — 4 shard dự phòng). Với cụm 4 ổ, nó chọn set 4 ổ và EC:2. Không cần cấu hình gì.

1.3. Object rơi vào set nào — do hash tên, không do bảng tra

Hash tên object chọn erasure set Bốn object lần lượt rơi xuống một hàm hash ở giữa, rồi bị đẩy sang trái hoặc sang phải vào một trong hai erasure set. Tên object quyết định set, không có bảng tra cứu nào. PUT /demo/file-X.txt a c f h hash(tên object) % số set Erasure set 1 — ổ 1..12 file-a.txt, file-b.txt, file-f.txt 12 shard / object Erasure set 2 — ổ 13..24 file-c, file-d, file-e, file-g, file-h 12 shard / object
Không có bảng tra cứu: mỗi node tự tính hash tên object ra set. Đây là lý do mọi node đều trả lời được mọi request, và cũng là lý do không thể thêm ổ vào một set đã tồn tại.

Lab thật, 8 object tên khác nhau trên cụm 24 ổ (2 set), xem shard hạ cánh ở ổ nào:

file-a.txt → drive: 1,2,3,4,5,6,7,8,9,10,11,12
file-b.txt → drive: 1,2,3,4,5,6,7,8,9,10,11,12
file-c.txt → drive: 13,14,15,16,17,18,19,20,21,22,23,24
file-d.txt → drive: 13,...,24
file-e.txt → drive: 13,...,24
file-f.txt → drive: 1,...,12
file-g.txt → drive: 13,...,24
file-h.txt → drive: 13,...,24

Mỗi object dùng đúng 12 ổ của một set, không trải ra cả 24 ổ. Nhớ điểm này — nó quyết định toàn bộ phần "khi nào mất dữ liệu" ở mục 1.7.

1.4. Một PUT chạy qua những bước nào

Đường đi của một PUT trong MinIO Object đi từ client vào MinIO, được hash tên để chọn erasure set, cắt thành block 1 MiB, mỗi block mã hoá Reed-Solomon thành 2 shard dữ liệu và 2 shard parity, rồi ghi song song xuống 4 ổ cùng với file metadata xl.meta trên cả 4 ổ. Client PUT big.bin 8 MiB 1. hash tên object → chọn erasure set → khoá phân tán tên này 2. cắt thành block 1 MiB · 3. Reed-Solomon mỗi block → 2 data + 2 parity drive 1 part.1 shard 4 MiB + hash chống bitrot drive 2 part.1 shard 4 MiB drive 3 part.1 parity 4 MiB drive 4 part.1 parity 4 MiB 4. ghi xl.meta lên CẢ 4 ổ (368 byte) — metadata nằm cạnh dữ liệu, không có metadata server 5. trả 200 OK khi đủ write quorum. Ghi cùng lúc, không tuần tự.
Số trong hình là số đo thật từ lab 4 ổ (EC:2, object 8 MiB). Reed-Solomon (mã sửa lỗi: từ 2 mảnh dữ liệu sinh thêm 2 mảnh dự phòng, mất mảnh nào cũng dựng lại được).

1.5. Object nằm thế nào trên đĩa — xem tận mắt

Lab 4 ổ, EC:2, đẩy lên một file 8 MiB:

$ find lab/disk*/demo/big.bin -printf '%p  %s bytes\n'
disk1/demo/big.bin/3e332383-d413-48a8-a267-900f121165cd/part.1   4194560 bytes
disk1/demo/big.bin/xl.meta                                           368 bytes
disk2/demo/big.bin/3e332383-.../part.1                           4194560 bytes
disk2/demo/big.bin/xl.meta                                           368 bytes
disk3/... (giống hệt)     disk4/... (giống hệt)

$ du -cb lab/disk*/demo/big.bin | tail -1
16779712  total          ← file gốc 8388608 byte → tốn gấp 2.0 lần

Đọc được gì từ output này:

16 byte đầu của xl.meta:

$ head -c 16 disk1/demo/big.bin/xl.meta | xxd
00000000: 584c 3220 0100 0300 c600 0001 5e03 0301  XL2 ........^...

Magic XL2 + version. Phần thân là msgpack (định dạng nhị phân giống JSON nhưng gọn hơn) chứa: parity bao nhiêu, shard nằm ở ổ nào theo thứ tự nào, checksum, ETag, user metadata, và danh sách tất cả các version của object.

1.6. Object nhỏ không có file dữ liệu riêng (inline)

Nếu object đủ nhỏ, MinIO nhét luôn nội dung vào trong xl.meta thay vì tạo part.1 — bớt một lần mở file khi đọc. Lab thật, cụm 4 ổ (2 data shard):

Kích thước objectCó part.1?Shard trên 1 ổxl.meta trên 1 ổTổng 4 ổ
16 KiBkhông — inline—8.631 B34.524 B (2,1×)
64 KiBkhông — inline—33.213 B132.852 B
128 KiBkhông — inline—65.983 B263.932 B
256 KiBkhông — inline—131.519 B526.076 B
384 KiBcó196.640 B368 B—
1 MiBcó524.320 B368 B2.098.752 B
8 MiBcó4.194.560 B368 B16.779.712 B

Ngưỡng nằm ở shard, không phải object: inline khi mỗi shard ≤ 128 KiB. Ở cụm 2 data shard thì tương đương object ≤ 256 KiB; ở cụm 12 ổ (8 data shard) ngưỡng object sẽ cao hơn nhiều. Đây là chi tiết đáng nhớ khi bị hỏi "MinIO có hợp với hàng triệu file nhỏ không" (mục 3).

1.7. Read quorum và write quorum — lúc nào còn đọc được

Luật (kiểm chứng bằng lab bên dưới):

Mất ổ dần trong một erasure set 4 ổ, parity EC:2 Bốn ổ lần lượt hỏng. Mất một ổ và mất hai ổ thì object vẫn đọc ra đúng nhờ shard parity. Mất ổ thứ ba thì vượt parity, MinIO báo object không tồn tại và không cách nào dựng lại. drive 1 data shard drive 2 data shard drive 3 parity shard drive 4 parity shard 4/4 ổ lành — đọc thẳng 2 data shard, ghép lại md5 khớp file gốc 3/4 ổ — mất 1 shard, dựng lại từ parity ĐỌC OK, md5 vẫn khớp. Client không thấy gì bất thường. 2/4 ổ — đúng bằng read quorum ĐỌC OK, md5 vẫn khớp. Nhưng hết đệm — hỏng thêm 1 ổ là mất. 1/4 ổ — vượt parity ERROR: Object does not exist — mất hẳn, heal cũng không cứu được
Bốn khung này là 4 lần đo thật trong lab, không phải minh hoạ. Điểm cần nhớ: khi vượt parity, MinIO không báo "hỏng", nó báo "object does not exist" — mất dữ liệu trông giống như chưa từng upload.

Output thật:

md5 gốc: ec8992765b30db99bccb53498667b6cd

[1] Bitrot: ghi đè 64 byte ngẫu nhiên vào giữa shard trên disk1
    → ĐỌC OK, md5 khớp          (checksum phát hiện, MinIO bỏ shard hỏng, dùng parity)
[2] Xoá hẳn dữ liệu object trên disk1   → còn 3/4 → ĐỌC OK, md5 khớp
[3] Xoá tiếp trên disk2 (= parity EC:2) → còn 2/4 → ĐỌC OK, md5 khớp
[4] Xoá tiếp trên disk3                 → còn 1/4 →
    mc: <ERROR> Unable to read from `lab/demo/big.bin`. Object does not exist.

Trên cụm 24 ổ (2 set, EC:4), hỏng lan theo set chứ không lan ra cluster:

4 ổ của set 2 offline (đúng bằng parity):
  20 drives online, 4 drives offline, EC:4
  đọc file-c (set 2) → "hello"      ✔ vẫn đọc
  ghi 5 object mới  → PUT OK ×5     ✔ vẫn ghi (MinIO tự nâng parity cho object mới)

Ổ thứ 5 của set 2 offline (5 > parity 4):
  đọc file-a (set 1) → "hello"      ✔ set 1 không liên quan, vẫn sống
  đọc file-c (set 2) → Object does not exist   ✘ mất toàn bộ object của set 2

1.8. Tự chữa (healing): scanner và thay ổ

Hai cơ chế:

Lab — xoá 2/4 shard của một object rồi gọi heal:

trước khi phá:            disk1 disk2 disk3 disk4
sau khi xoá disk1+disk3:  disk2 disk4
$ mc admin heal --recursive --force lab/demo/heal-test.bin
[Red    ->  Green] demo/heal-test.bin
Healed: 1/1 objects; 8 MiB in 1s
sau heal:                 disk1 disk2 disk3 disk4
md5 gốc:    ec8992765b30db99bccb53498667b6cd
md5 đọc ra: ec8992765b30db99bccb53498667b6cd

Kịch bản thay ổ mới (xoá trắng một ổ, kể cả thư mục .minio.sys chứa format.json):

disk4 trước: 12.618.125 byte → xoá trắng → 0 byte
mc admin info: "Drives: 3/4 OK"  (MinIO coi ổ chưa format là offline)
đọc object khi disk4 rỗng → md5 vẫn khớp      ✔ đọc không bị ảnh hưởng

$ mc admin heal lab      → "No active healing is detected for new disks"   ← heal thủ công KHÔNG nhận ổ mới
khởi động lại tiến trình MinIO → nó tự format lại disk4 và tự heal nền:
  t=10s  disk4 = 8.422.130 byte
  t=20s  disk4 = 8.424.179 byte
  t=60s  disk4 = 8.429.328 byte   (đã dựng lại xong các shard)

Bẫy vận hành thật gặp trong lab: khi mang ổ cũ về đúng chỗ bằng mv disk13.off disk13, nếu thư mục disk13 đã được MinIO tạo lại thì mv sẽ nhét ổ cũ vào bên trong (disk13/disk13.off/…) chứ không thay thế. Dữ liệu trông như "đã trả về" nhưng MinIO không thấy, và object vẫn báo không tồn tại. Khi thao tác mount point lúc 3h sáng, luôn ls lại thư mục sau khi mount.

1.9. Versioning nằm trong cùng một xl.meta

$ mc version enable lab/ver && ghi đè 3 lần rồi xoá
[..] 0B  6bdaeb05-… v4 DEL file.txt       ← delete marker
[..] 12B d4c210a3-… v3 PUT file.txt
[..] 12B ae1e365a-… v2 PUT file.txt
[..] 12B 8aa198d8-… v1 PUT file.txt

$ find lab/disk1/ver/file.txt
disk1/ver/file.txt/xl.meta   1325 byte     ← CẢ 4 version nằm trong 1 file metadata

Vì object nhỏ nên nội dung cũng inline luôn. Với object lớn, mỗi version có thư mục UUID riêng cho phần dữ liệu, nhưng danh sách version vẫn chung một xl.meta. Xoá object khi bật versioning chỉ tạo delete marker: đọc bản mới nhất ra lỗi "does not exist", nhưng dữ liệu cũ vẫn còn và vẫn tốn chỗ — nhớ đặt lifecycle rule dọn version cũ, nếu không dung lượng phình âm thầm.

1.10. Xác thực: chữ ký AWS SigV4, không phải session

Mọi request đều ký bằng AWS Signature V4. Presigned URL (link tạm có chữ ký, hết hạn theo thời gian) là cách chuẩn để cho trình duyệt tải/đẩy file thẳng, không qua app:

$ mc share download --expire 5m lab/demo/small.bin
http://127.0.0.1:19000/demo/small.bin?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=…

curl có chữ ký      → 200
curl không chữ ký   → 403

Quyền dùng policy viết y hệt IAM của AWS (s3:GetObject, s3:PutObject…), gắn vào user hoặc service account. Bucket mặc định private.

2. Lợi ích

Được gìThật sự có giá trị khi nào
API S3 chuẩn — code Laravel/SDK không đổi một dòng khi chuyển giữa S3 và MinIOKhi muốn dev/staging chạy offline, hoặc muốn giữ đường lui khỏi AWS
Một binary, không phụ thuộc gì — không cần database, không ZooKeeper/etcdĐội nhỏ, không có team hạ tầng riêng. Dựng xong trong một buổi.
Dữ liệu ở trong nhà — chi phí cố định, không tính tiền egress (băng thông ra)Hệ thống đẩy/tải nhiều: video, ảnh gốc, backup. Egress AWS mới là thứ đắt.
Erasure coding tốt hơn nhân bản: chịu lỗi 4 ổ chỉ tốn 1,5× dung lượng (12 ổ, EC:4), thay vì 3× như replica 3 bảnKhi dữ liệu lớn tới hàng chục TB — chênh lệch dung lượng thành tiền thật
Chịu bitrot — hash từng shard-block, phát hiện và sửa âm thầmỔ cứng nhiều, chạy lâu năm. Đã đo ở mục 1.7: phá 64 byte vẫn đọc đúng.
Tuân thủ: versioning, object lock (WORM — ghi một lần, không sửa được), mã hoá phía server, audit logKhi có yêu cầu lưu trữ chứng từ, hoặc chống ransomware xoá backup

3. Tác hại / hạn chế

4. Trade-off

Phương ánChọn khiTrả giá gì
S3 / Cloudflare R2 / Spaces (dịch vụ)Không có người trực hạ tầng; lưu lượng vừa; muốn 11 số 9 độ bền mà không phải nghĩTiền egress (R2 miễn phí egress), dữ liệu nằm ngoài, phụ thuộc nhà cung cấp
MinIO / bản thay thế tự hostDữ liệu phải ở trong nước/trong nhà; egress lớn; đã có máy vật lýBạn tự trực ổ hỏng, tự backup, tự vá bảo mật; và với MinIO community là dự án đã archive
Ceph RGWCần cả object + block + file; quy mô nhiều PB; có đội chuyênPhức tạp hơn hẳn: MON, OSD, CRUSH map. Không phải hạ tầng cho đội 5 người.
Ổ đĩa local / NFSMột server, ít file, không cần scaleKhông scale ngang, khó backup, mở rộng là đau; không dùng được với nhiều app server
Lưu file trong PostgreSQL (bytea / large object)File rất ít và nhỏ, cần đúng một nguồn sự thật với transactionDB phình, backup chậm, WAL nặng, cache DB bị file chiếm chỗ. Gần như luôn là lựa chọn sai cho file người dùng.

Tiêu chí chọn, nói gọn khi phỏng vấn:

flowchart TB
  A["Cần lưu file người dùng"] --> B{"Dữ liệu bắt buộc ở trong nhà?"}
  B -- "Không" --> C{"Egress lớn?"}
  C -- "Có" --> R2["R2 / dịch vụ miễn phí egress"]
  C -- "Không" --> S3["S3 hoặc tương đương"]
  B -- "Có" --> D{"Có người trực hạ tầng 24/7?"}
  D -- "Không" --> E["Tự host là nợ vận hành — cân nhắc lại, hoặc thuê managed"]
  D -- "Có" --> F{"Cần nhiều PB + block/file storage?"}
  F -- "Có" --> CEPH["Ceph"]
  F -- "Không" --> G{"Chấp nhận AGPL + bản community đã archive?"}
  G -- "Không" --> ALT["AIStor trả phí, hoặc RustFS/Garage/SeaweedFS"]
  G -- "Có" --> MIN["MinIO bản đang có, pin version, tự chịu rủi ro CVE"]

5. Khó khăn khi vận hành thực tế

6. Case thực tế và cách xử lý

Case 1 — 3h sáng: cảnh báo "2 drives offline" trên cụm 12 ổ EC:4

  1. mc admin info ALIAS → xem 2 ổ đó thuộc cùng một set hay khác set. Cùng set mới đáng lo (còn đệm 2), khác set thì mỗi set chỉ mất 1.
  2. Kiểm tra dịch vụ: curl -I /minio/health/cluster → 200 nghĩa là vẫn còn write quorum, chưa cần đánh thức ai khác.
  3. Xác định nguyên nhân: ổ hỏng thật (dmesg, SMART), hay chỉ mất mount (kiểm tra mount | grep disk). Mất mount thì mount lại là xong — và nhớ bẫy mv lồng thư mục ở mục 1.8.
  4. Ổ hỏng thật → thay ổ, format XFS, mount đúng chỗ cũ, restart tiến trình MinIO node đó. MinIO tự format và heal nền (lab đo: ổ rỗng đầy lại sau ~10 giây với 8 MB dữ liệu; thực tế với ổ nhiều TB tính bằng giờ).
  5. Theo dõi mc admin heal --recursive ALIAS. Chỉ khi số ổ hỏng trong một set = parity − 1 mới cần dừng ghi và làm gấp.

Case 2 — App báo "ảnh không tồn tại" nhưng DB vẫn có record

Đây đúng là biểu hiện của mất quorum: MinIO trả Object does not exist, không trả lỗi hạ tầng, nên app tưởng chưa upload.

  1. Đối chiếu: mc stat ALIAS/bucket/key và mc ls --versions. Không có gì cả → không phải do app.
  2. mc admin heal --recursive --force ALIAS/bucket → nếu object hiện ra Red và không heal được, tức đã mất nhiều shard hơn parity.
  3. Xem có phải xoá nhầm không: nếu bật versioning thì tìm delete marker và khôi phục version cũ.
  4. Không cứu được thì lấy từ backup/cụm replication; lấy danh sách object hỏng, để app đánh dấu record thay vì trả lỗi 500 cho người dùng.
  5. Sau sự cố: bật versioning nếu chưa có, đặt cảnh báo ở mức parity − 1, xem lại tại sao nhiều ổ cùng set hỏng cùng lúc (thường vì chúng nằm cùng một máy hoặc cùng lô sản xuất).

Case 3 — Bucket 4 triệu ảnh thumbnail 20 KB, cụm chậm dần và đầy sớm

  1. Chẩn đoán bằng số ở mục 1.6: object 16 KiB tốn ~2,1× dung lượng, và mỗi object là một thư mục + metadata trên mỗi ổ.
  2. Xử lý gốc: đừng lưu thumbnail thành object. Sinh ảnh theo kích thước lúc đọc qua CDN/image proxy từ một ảnh gốc, hoặc gộp nhiều ảnh nhỏ thành một object lớn.
  3. Nếu buộc phải lưu: đặt tên key phân tán theo prefix (ví dụ yyyy/mm/dd/ hoặc 2 ký tự băm đầu) để LIST không phải quét một prefix khổng lồ.
  4. Đặt lifecycle rule xoá bản cũ, và đo lại dung lượng thật trên đĩa bằng mc admin info, không tin con số tổng kích thước object.

Case 4 — Sắp hết dung lượng, sếp hỏi "cắm thêm ổ được không"

Không. Erasure set đã format là cố định. Ba lối đi:

  1. Thêm pool: chuẩn bị nhóm máy/ổ mới, khởi động MinIO với cả pool cũ và pool mới trong cùng câu lệnh, rồi mc admin rebalance start để san dữ liệu. Object mới sẽ ưu tiên vào pool trống hơn.
  2. Dọn rác trước: version cũ + delete marker thường chiếm rất nhiều — đặt lifecycle rule NoncurrentVersionExpiration.
  3. Chuyển tầng (tiering): đẩy object cũ sang tier rẻ hơn (cụm khác hoặc S3) bằng lifecycle transition, cục bộ chỉ giữ metadata.

Phụ lục — nối MinIO vào Laravel

Driver s3 của Laravel dùng thẳng được, chỉ cần đổi endpoint và bật path-style (MinIO dùng http://host/bucket/key, không dùng tên bucket làm subdomain):

# .env
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=app-service-account
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=uploads
AWS_ENDPOINT=http://minio:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
// Trả link tạm thay vì cho file đi qua PHP — giống presigned URL đã test ở mục 1.10
$url = Storage::disk('s3')->temporaryUrl('invoices/2026/09/inv-1.pdf', now()->addMinutes(5));

Chưa verify trong repo này: project laravel-livewire hiện chưa cài league/flysystem-aws-s3-v3, nên phần code Laravel ở trên lấy theo tài liệu Laravel (fetch qua Context7), chưa chạy thật. Phần presigned URL thì đã test thật bằng mc share download + curl: có chữ ký → 200, không chữ ký → 403.

7. Câu hỏi phụ interviewer hay hỏi tiếp

Câu hỏiTrả lời ngắn
MinIO lưu metadata ở đâu?Trong file xl.meta nằm ngay cạnh dữ liệu, trên mọi ổ của erasure set. Không có metadata server, không có database.
Làm sao biết object nằm ở ổ nào?Hash tên object → erasure set. Mọi node tính ra cùng kết quả nên gọi vào node nào cũng được.
Erasure coding khác replication chỗ nào?Replication giữ N bản nguyên vẹn (3 bản = 3× dung lượng). Erasure coding cắt thành K data + M parity: chịu mất M mảnh mà chỉ tốn (K+M)/K lần. Bù lại, đọc khi thiếu mảnh phải tính toán để dựng lại.
Mất bao nhiêu ổ thì mất dữ liệu?Hơn số parity, tính trong một erasure set. Set 12 ổ EC:4 chịu được 4 ổ; ổ thứ 5 là mất object của set đó — các set khác vẫn sống.
Parity mặc định?EC:4 ở cụm đủ lớn; cụm 4 ổ thì EC:2. Đổi được qua storage class (standard, rrs).
Bitrot là gì, MinIO chống thế nào?Dữ liệu mục âm thầm trên đĩa. MinIO gắn hash HighwayHash 32 byte cho mỗi shard-block 512 KiB; đọc thấy sai thì bỏ shard đó, dựng lại từ parity. Đã đo: phá 64 byte, đọc ra vẫn đúng md5.
Thêm ổ vào cụm đang chạy được không?Không thêm vào set cũ được. Phải thêm cả một pool mới rồi rebalance.
Hai request PUT cùng key cùng lúc thì sao?MinIO khoá theo tên object (lock phân tán giữa các node), ghi lần lượt; S3 đảm bảo một object luôn là bản nguyên vẹn của một lần PUT, không có chuyện lẫn nửa này nửa kia.
Bật versioning có tốn chỗ không?Có, và tốn âm thầm — delete cũng chỉ tạo delete marker. Phải đặt lifecycle rule xoá version cũ.
Có dùng MinIO làm ổ chia sẻ (NFS) được không?Không. Object storage không sửa tại chỗ, không rename, không có quyền POSIX. Chế độ filesystem đã bị gỡ từ 2022.
Replication có thay được backup không?Không. Lệnh xoá cũng được nhân bản. Cần versioning + object lock, hoặc bản sao lạnh.
Năm 2026 còn nên chọn MinIO không?Bản community đã archive (dl.min.io trả 410, repo GitHub archived, image bị rút khỏi Docker Hub) nên không còn bản vá bảo mật. Dự án mới: AIStor trả phí, hoặc S3/R2, hoặc bản thay thế. Cụm đang chạy thì pin version, cô lập mạng, và lên kế hoạch rời đi.

8. Cách tự dựng lại lab này

Tất cả số đo ở trên chạy trên máy thường, không cần Docker, không cần nhiều ổ thật:

# binary lấy từ GitHub release (dl.min.io đã trả 410 Gone)
curl -L -o minio https://github.com/minio/minio/releases/download/RELEASE.2025-09-07T16-13-09Z/minio.linux-amd64.RELEASE.2025-09-07T16-13-09Z
curl -L -o mc    https://github.com/minio/mc/releases/download/RELEASE.2025-08-13T08-35-41Z/mc.linux-amd64.RELEASE.2025-08-13T08-35-41Z
chmod +x minio mc

mkdir -p /srv/lab/disk{1,2,3,4}
# MINIO_CI_CD=1 để MinIO chấp nhận thư mục nằm trên root disk;
# thiếu biến này nó báo "drive not found" rất khó đoán.
MINIO_CI_CD=1 MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin123 \
  ./minio server /srv/lab/disk{1...4} --address :19000 --console-address :19001

./mc alias set lab http://127.0.0.1:19000 minioadmin minioadmin123
./mc admin info lab            # xem số set, stripe size, EC:x
./mc mb lab/demo && ./mc cp file.bin lab/demo/

# rồi phá: xoá thư mục object trên 1, 2, 3 ổ và đọc lại sau mỗi lần
rm -rf /srv/lab/disk1/demo/file.bin && ./mc cat lab/demo/file.bin | md5sum

Đổi disk{1...4} thành disk{1...24} để thấy MinIO tự chia 2 erasure set 12 ổ và object phân về set theo hash tên.