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/miniotrả 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/miniotrả"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:
- Metadata của object nằm ngay cạnh dữ liệu, trong file
xl.metatrên chính các ổ giữ object đó. - Muốn biết object nằm ở đâu thì tính, không phải tra: hash tên object ra erasure set (nhóm ổ). Mọi node đều tính ra cùng kết quả, nên client gọi vào node nào cũng được.
- Hệ quả tốt: không có single point of failure kiểu NameNode, không có bước tra cứu thêm.
- Hệ quả xấu: không đổi được số ổ trong một erasure set sau khi đã format, vì công thức hash phụ thuộc vào nó. Muốn thêm dung lượng thì thêm pool mới (mục 1.6).
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
- Drive: một ổ đĩa (khuyến nghị XFS, JBOD — cắm thẳng, không RAID).
- Erasure set: nhóm 4–16 ổ. Đây là đơn vị chịu lỗi thật sự. Một object nằm gọn trong một set.
- Pool: tập các erasure set được format cùng lúc bằng một lệnh
minio server. - Cluster: một hoặc nhiều pool, trình bày ra ngoài như một endpoint S3 duy nhất.
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
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
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:
- Bucket = thư mục, object = thư mục (không phải file). Trong đó có một thư mục
UUID cho version của object, và
xl.meta. - Mỗi ổ giữ đúng 1 shard. 8 MiB chia 2 data shard = 4 MiB mỗi shard.
- 4.194.560 ≠ 4.194.304. Dư 256 byte: MinIO đọc/ghi theo block 1 MiB; mỗi block sinh 1 shard 512 KiB, và mỗi shard-block được gắn một hash 32 byte (HighwayHash) để phát hiện bitrot (dữ liệu tự mục trên đĩa, bit lật âm thầm). 8 block × 32 byte = 256 byte. Số khớp chính xác.
- xl.meta có mặt trên cả 4 ổ — metadata cũng được bảo vệ như dữ liệu.
- Dung lượng thật = 2× dữ liệu vì parity 2/4. Công thức:
chi phí = N / Kvới N = số ổ trong set, K = số shard dữ liệu.
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 object | Có part.1? | Shard trên 1 ổ | xl.meta trên 1 ổ | Tổng 4 ổ |
|---|---|---|---|---|
| 16 KiB | không — inline | — | 8.631 B | 34.524 B (2,1×) |
| 64 KiB | không — inline | — | 33.213 B | 132.852 B |
| 128 KiB | không — inline | — | 65.983 B | 263.932 B |
| 256 KiB | không — inline | — | 131.519 B | 526.076 B |
| 384 KiB | có | 196.640 B | 368 B | — |
| 1 MiB | có | 524.320 B | 368 B | 2.098.752 B |
| 8 MiB | có | 4.194.560 B | 368 B | 16.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):
- Read quorum = số shard dữ liệu. Với set 4 ổ
EC:2: cần ít nhất 2 ổ lành. Mất tới 2 ổ vẫn đọc được. - Write quorum cũng theo parity; khi parity đúng bằng nửa số ổ thì ghi cần thêm 1 ổ (để không có hai "nửa" cùng nhận ghi).
- Ghi vào set đang thiếu ổ: MinIO tự nâng parity cho object mới để nó vẫn đạt cùng mức an toàn.
- Vượt quá parity: object mất, và mất theo từng erasure set, không phải cả cluster.
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ế:
- Chữa khi đọc: mỗi lần GET, nếu shard nào sai checksum hoặc thiếu, MinIO dựng lại từ parity và ghi lại xuống ổ.
- Scanner chạy nền: quét toàn bộ object theo chu kỳ, so sánh với parity, sửa cái hỏng,
đồng thời cập nhật số liệu dung lượng cho
mc admin info.
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ụcdisk13đã được MinIO tạo lại thìmvsẽ 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ônlslạ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à MinIO | Khi 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ản | Khi 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 log | Khi 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ế
- Không phải filesystem. Không sửa tại chỗ (muốn đổi 1 byte phải PUT lại cả object), không rename thật (rename = copy + delete), không có quyền POSIX, không mount làm thư mục dùng nghiêm túc được. Chế độ "filesystem/gateway" đã bị gỡ từ 2022.
- Set không co giãn. Đã format 12 ổ/set thì vĩnh viễn 12 ổ/set. Thêm dung lượng
= thêm cả một pool mới (thêm máy), rồi
mc admin rebalance— không phải cắm thêm một ổ. - Chi phí parity là cố định. Cụm nhỏ 4 ổ = tốn gấp đôi dung lượng. Muốn tỉ lệ đẹp (1,33–1,5×) phải có ít nhất 12–16 ổ.
- File nhỏ tốn đắt. Bảng ở mục 1.6: object 16 KiB chiếm 34,5 KB trên đĩa (2,1×), cộng thêm mỗi object là 1 thư mục + ít nhất 1 file metadata trên mỗi ổ. Vài triệu ảnh thumbnail sẽ ăn inode và làm chậm các thao tác quét.
- LIST đắt. Không có index; liệt kê prefix lớn phải đi quét thư mục trên nhiều ổ. Thiết kế tên key cho phân tán đều, đừng nhét 5 triệu object vào một prefix phẳng.
- Mất quorum = mất im lặng. Như đã đo: object trả về "does not exist", không phải lỗi rõ ràng. App dễ hiểu nhầm là "user chưa upload".
- Erasure coding không thay backup. Nó chống ổ hỏng, không chống lệnh xoá nhầm, không chống bug app, không chống ransomware (trừ khi bật object lock).
- Nhiều ổ trên cùng một máy = rủi ro. Chính MinIO cảnh báo lúc khởi động lab:
WARNING: Host local has more than 2 drives of set. A host failure will result in data becoming unavailable.Một máy chết là mất nhiều shard cùng lúc. - License và tình trạng dự án. AGPLv3 (nhiều công ty cấm dùng trong sản phẩm bán ra), và từ 2025–2026 bản community đã archive — không còn bản vá bảo mật. Đây là rủi ro vận hành, không chỉ rủi ro pháp lý.
- Bẫy khi thử nghiệm: MinIO từ chối ổ nằm trên root disk, và thông báo lỗi lại là
drive not found, will be retried— rất dễ hiểu nhầm là sai đường dẫn. Lab này phải đặtMINIO_CI_CD=1mới chạy được trên thư mục thường.
4. Trade-off
| Phương án | Chọn khi | Trả 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ự host | Dữ 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 RGW | Cần cả object + block + file; quy mô nhiều PB; có đội chuyên | Phứ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 / NFS | Một server, ít file, không cần scale | Khô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 transaction | DB 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ế
- Tính dung lượng phải nhân hệ số. Cần chứa 20 TB dữ liệu với set 12 ổ EC:4 → phải mua ~30 TB ổ (1,5×), chưa kể chỗ trống để heal. Đừng chạy quá 80% dung lượng.
- Heal ăn băng thông và IOPS. Dựng lại một ổ 12 TB phải đọc dữ liệu tương ứng từ 11 ổ còn lại. Trong lúc đó độ trễ đọc tăng. Lên lịch thay ổ vào giờ thấp điểm nếu còn đệm parity.
- Giám sát phải nhìn đúng chỉ số: số ổ offline theo từng set (không phải tổng
cluster), tiến độ heal, dung lượng còn trống, tỉ lệ lỗi 5xx. MinIO xuất sẵn metrics kiểu Prometheus
và có endpoint
/minio/health/cluster(503 khi mất write quorum) và/minio/health/cluster?maintenance=true(412 nếu tắt node này sẽ mất quorum) — dùng cái thứ hai trước khi reboot máy để bảo trì. - Cảnh báo phải bắt "còn 1 ổ nữa là mất", không phải "đã mất". Ngưỡng đúng: số ổ hỏng trong một set ≥ parity − 1.
- Nâng cấp. Cả cụm nên cùng một version; nâng cấp là restart lần lượt. Với bản đã archive thì còn thêm việc: tự theo dõi CVE và tự quyết định pin version nào.
- Bảo mật. Đổi credential root, tạo service account riêng cho từng ứng dụng với policy hẹp, bật TLS, không để console (cổng 9001) ra Internet. Với bản không còn bản vá, phải đặt MinIO sau proxy và giới hạn mạng.
- Backup thật sự. Replication (nhân bản sang cụm khác) chống mất cụm, không chống xoá nhầm — vì lệnh xoá cũng được nhân bản. Cần thêm versioning + object lock, hoặc bản sao lạnh ở nơi khác.
- Đừng để nhiều ổ của cùng một set nằm trên một máy ở môi trường thật — như cảnh báo MinIO in ra lúc khởi động lab.
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
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.- 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. - Xác định nguyên nhân: ổ hỏng thật (
dmesg, SMART), hay chỉ mất mount (kiểm tramount | grep disk). Mất mount thì mount lại là xong — và nhớ bẫymvlồng thư mục ở mục 1.8. - Ổ 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ờ).
- 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.
- Đối chiếu:
mc stat ALIAS/bucket/keyvàmc ls --versions. Không có gì cả → không phải do app. mc admin heal --recursive --force ALIAS/bucket→ nếu object hiện raRedvà không heal được, tức đã mất nhiều shard hơn parity.- 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ũ.
- 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.
- 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
- 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 ổ.
- 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.
- 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ồ. - Đặ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:
- 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. - Dọn rác trước: version cũ + delete marker thường chiếm rất nhiều — đặt lifecycle
rule
NoncurrentVersionExpiration. - 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-livewirehiện chưa càileague/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ằngmc 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ỏi | Trả 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.