Kỹ năng sử dụng FIO

1. Tổng quan

Bài này tổng hợp kinh nghiệm “đo cho ra ngô ra khoai” với fio: cách dựng kịch bản test (random/sequential), chọn bs, iodepth (QD), numjobs, hiểu và điều khiển các lớp cache (OS/RAID/SSD), đọc – diễn giải kết quả (IOPS/BW/latency/percentiles), cùng các mẹo thực chiến cho môi trường server (RAID controller, HBA, NVMe, SSD SATA, HDD). Mục tiêu: bạn có thể tự tin thiết kế bài test đúng câu hỏi, chạy đúng cách, đọc đúng kết quả và tránh bẫy.

2. Nguyên tắc cốt lõi

Định luật Little (điểm tựa chọn QD):
Concurrency (QD tổng) ≈ Throughput (IOPS) × Latency (giây)
Trong fio: QD tổng = iodepth × numjobs.
→ Muốn đạt IOPS mục tiêu, QD phải đủ lớn; nhưng QD quá cao sẽ đội p99/p99.9.

Workload quyết định cấu hình:

  • Random nhỏ (4K/8K) → đo IOPS/latency.
  • Sequential lớn (128K/256K/1M) → đo throughput (MB/s).
  • Mixed (r/w) → mô phỏng OLTP, VM, file server.
  • Latency-first → test QD=1.
  • Path I/O phải sạch:
    • kiểm soát cache.
    • Chọn ioengine phù hợp, đảm bảo không có bottleneck ngoài ý muốn.

3. Các lớp cache & cách kiểm soát

3.1. OS Page Cache (Linux):

  • Bị bỏ qua khi --direct=1.
  • Nếu test trên filesystem và muốn cache: không khai báo --direct=1; nếu không muốn cache khai báo --direct=1.
  • Dọn cache (thận trọng): echo 3 > /proc/sys/vm/drop_caches (chỉ khi cần và hiểu rõ hệ đang chạy gì).
  • Nó phù hhợp test raw không dựa dẫm vào cache
  • Thông số này dù khai báo hay không khai báo đều không ảnh hưởng đến cache trên RAID controller hay cache của SSD.

3.2. RAID Controller Cache (ví dụ HPE Smart Array P440):

  • Read cache / Write-back cache do firmware điều khiển.
  • --direct=1 không bypass cache phần cứng này.
  • Bật/tắt bằng công cụ (vd ssacli).
  • Cache OFF → phản ánh “raw SSD”
  • Cache ON → phản ánh hiệu năng thực chiến của hệ lưu trữ có tính luôn cache của card RAID.

3.3. SSD Internal Cache (DRAM/SLC, FTL):

  • Không bypass được bằng fio; đó là bản chất SSD.
  • Cần precondition đối với SSD enterprise/NVMe khi so sánh nghiêm ngặt.

Sơ đồ luồng I/O (điển hình):

fio → [libaio/io_uring] → kernel block layer → driver → (RAID cache?) → SSD cache/FTL → NAND
                            ↑                                          
                           (OS page cache - bị bỏ qua nếu --direct=1)

4. Chọn ioengine

4.1. --ioengine=io_uring (kernel 5.x trở lên)

Cơ chế

  • User space và kernel chia sẻ 2 vòng tròn (ring buffer) trong memory:
    • SQ (Submission Queue): fio ghi thẳng request vào đây.
    • CQ (Completion Queue): kernel ghi kết quả vào đây.
  • Với nhiều request: fio chỉ cần ghi memory, không syscall.
  • Chỉ khi cần “kick” kernel (queue đầy, hoặc SQPOLL tắt) mới gọi một syscall io_uring_enter().
  • Hoàn thành: fio đọc kết quả trực tiếp từ CQ, không cần syscall nếu polling.
[User space: fio]
    |
    | ghi entry vào SQ (user->kernel shared memory)
    v
[Kernel I/O thread]
    |
    v
[Block layer -> driver -> device]

(Sau khi I/O hoàn tất)
    |
    | ghi entry vào CQ (kernel->user shared memory)
    v
[User space đọc CQ trực tiếp]

Cơ chế này giúp FIO có thể ghi 32, 64… entry liên tục vào SQ, kernel xử lý một mạch.

Syscall chỉ cần thiết khi muốn đẩy hàng loạt hoặc block chờ.

Có mode SQPOLL giúp kernel thread poll SQ liên tục, fio gần như không gọi syscall nữa → latency cực thấp.

Kernel sẽ tạo sẵn 1 thread để chủ động theo dõi Submission Queue, nhờ đó fio chỉ cần ghi vào memory là kernel xử lý ngay, không syscall. Cái giá phải trả là chỉ mất 1 CPU core cho thread poll liên tục. Nhưng nếu là test thì cũng không phải là vấn đề gì lớn.

Khác biệt chính ngoài cách gọi hàm là:

  • libaio: batch = mảng struct iocb trong mỗi syscall.
    io_uring: batch = nhiều entry push vào SQ buffer, kernel quét một lượt, syscall giảm hẳn.
  • Hoàn tất:
    • libaio → io_getevents() syscall để lấy kết quả.
    • io_uring → đọc CQ buffer trực tiếp (zero syscall).
  • Context switch:
    • libaio: 2 syscall/chu kỳ (submit, complete).
    • io_uring: 0–1 syscall/chu kỳ (tùy polling hay không).
  • Latency & CPU overhead:
    • io_uring giảm syscall → giảm context switch → latency thấp hơn, CPU nhàn hơn.

4.2. --ioengine=libaio (tương thích hầu hết kernel cũ)

Cơ chế

  • User space (fio) muốn gửi I/O phải gọi syscall (io_submit()).
  • Kernel nhận từng batch nhỏ (mảng struct iocb), đưa vào request queue.
  • Hoàn thành xong, user space gọi tiếp syscall io_getevents() để lấy kết quả.
  • Mỗi vòng submit/complete đều qua syscall → overhead lớn, context switch nhiều.
[User space: fio]
    |
    | io_submit()  (syscall)
    v
[Kernel AIO layer]
    |
    v
[Block layer -> driver -> device]

(Sau khi I/O hoàn tất)
    |
    ^ io_getevents() (syscall)
    |
[User space nhận kết quả]

Cơ chế này vẫn có batch submit (nhiều iocb trong một syscall), nhưng vẫn cần syscall mỗi lần.

Không có queue chia sẻ memory giữa user và kernel, tất cả qua syscall API.

5. Chọn bs, iodepth (QD) và numjobs

5.1. bs (block size):

  • 4K–8K: random IOPS/latency (DB/metadata).
  • 16K–32K: mixed OLTP.
  • 64K–256K–1M: throughput (backup/scan).
  • Nếu có RAID: cân nhắc khớp stripe (vd stripe 256K → test seq với bs=256k).

5.2. iodepth (QD mỗi job) & numjobs (số job):

Trong fio:

  • iodepth = số I/O đồng thời tối đa mỗi job giữ trong hàng đợi.
  • numjobs = số job song song chạy độc lập.
  • Ví dụ numjobs=4, iodepth=10 → tổng tối đa 40 I/O đồng thời.
  • Mỗi job luôn cố giữ đầy iodepth của mình: khi 1 request hoàn tất, nó lập tức bổ sung 1 request mới.
  • Không cần chờ xử lý hết 10 request rồi mới nạp thêm.
  • Vì vậy, iodepth thể hiện mức “song song” liên tục của từng job, còn tổng song song là iodepth × numjobs.
  • QD tổng = iodepth × numjobs.
  • SSD SATA: QD hữu ích per-disk thường 16–32 (giới hạn NCQ ~32).
  • NVMe: QD cao hơn nhiều (64–256+).
  • HDD: QD nhỏ (1–4) đủ bão hòa.
  • Quy tắc: tăng iodepth trước, sau đó tăng numjobs nếu 1 thread không đủ bơm I/O.

5.3. Công thức áp dụng:

  • Chạy QD1 lấy L (latency trung bình, giây).
  • Ước lượng IOPS mục tiêu I.
  • Tính QD cần ≈ I × L, rồi chọn numjobs × iodepth ≈ QD cần.
  • Sweep quanh điểm đó và theo dõi p99/p99.9.

6. Bộ kịch bản test mẫu (đủ “tất cả trường hợp” hay gặp)

6.1. Latency QD1 (4K random read – đo độ trễ “thật”)

fio --name=lat-randread --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=randread --bs=4k --numjobs=1 --iodepth=1 \
    --time_based --runtime=120 --ramp_time=30 --group_reporting

6.2. Max IOPS random 4K (read/write)

# Read
fio --name=iops-randread --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=randread --bs=4k --numjobs=4 --iodepth=32 \
    --time_based --runtime=120 --ramp_time=30 --group_reporting

# Write (không dùng --fdatasync=1 nếu đo “raw performance”)
fio --name=iops-randwrite --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \
    --time_based --runtime=120 --ramp_time=30 --group_reporting

6.3. Throughput sequential (khớp stripe, ví dụ 256K)

# Read
fio --name=seq-read --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=read --bs=256k --numjobs=1 --iodepth=32 \
    --time_based --runtime=120 --ramp_time=30 --group_reporting

# Write
fio --name=seq-write --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=write --bs=256k --numjobs=1 --iodepth=32 \
    --time_based --runtime=120 --ramp_time=30 --group_reporting

6.4. Random throughput lớn (64K/128K/256K)

# Random read 128K
fio --name=randread-128k --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=randread --bs=128k --numjobs=4 --iodepth=32 \
    --time_based --runtime=120 --ramp_time=30 --group_reporting
# Random write 128K (tương tự)

6.5. Mixed OLTP (ví dụ 70% read / 30% write, 4K)

fio --name=mixed-4k --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=randrw --rwmixread=70 --bs=4k --numjobs=4 --iodepth=32 \
    --time_based --runtime=180 --ramp_time=30 --group_reporting

6.6. Sweep QD để tìm plateau (IOPS tăng chậm lại)

# Ví dụ bash loop
for i in 1 2 4 8 16 32; do
  fio --name=rd4k-QD$i --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
      --rw=randread --bs=4k --numjobs=4 --iodepth=$i \
      --time_based --runtime=60 --ramp_time=20 --group_reporting \
      --output=rd4k-QD$i.json --output-format=json
done

6.7. Test “durability” (đo flush chi phí thực)

# Trên filesystem (không --direct) + fdatasync để mô phỏng WAL/log
fio --name=wal-like --filename=/mnt/fs/testfile --ioengine=io_uring \
    --rw=randwrite --bs=4k --numjobs=1 --iodepth=1 \
    --fdatasync=1 --size=10G --time_based --runtime=120 --ramp_time=30

6.8. Preconditioning SSD (khi cần so sánh nghiêm ngặt)

# Viết tuần tự full device trước khi test để ổ định trạng
fio --name=precond --filename=/dev/<dev> --ioengine=io_uring --direct=1 \
    --rw=write --bs=1M --iodepth=32 --numjobs=1 --size=100% --loops=1

7. Đọc & diễn giải kết quả fio

7.1. Trường quan trọng:

  • IOPS, BW (MB/s), clat (completion latency), slat (submit latency).
  • Percentiles: p50/p90/p95/p99/p99.9 (tail latency).
  • cpu (usr/sys), ctx (context switch) → overhead.
  • Disk stats/util=...%: thiết bị có bị “đầy” không.

7.2. Cách nhìn nhanh:

  • Random 4K: xem IOPS và p99/p99.9 (jitter/tail).
  • Sequential: xem BW; nếu ~trần giao tiếp (SATA ~550 MB/s), coi như OK.
  • Mixed: chú ý độ cân bằng r/w, p99 write thường xấu hơn read.

7.3. Khi so sánh A/B:

  • Giữ mọi thứ cố định trừ một biến (vd bật/tắt cache).
  • Chạy ≥2–3 lần, bỏ lượt đầu (warm-up).
  • Dùng --output-format=json + ghi log (--write_bw_log, --write_lat_log) để vẽ biểu đồ.

8. Tối ưu phần cứng/firmware cho test

8.1. RAID vs HBA:

  • Muốn “raw passthrough”: HBA/IT mode (nếu controller hỗ trợ).
  • Smart Array P440 (Gen9) không HBA thực sự → dùng RAID0 1-disk để OS nhìn thấy disk.
  • Bật/tắt cache trên logical drive tùy mục tiêu (raw vs thực chiến).

8.2. Stripe size & bs:

  • Sequential: đặt bs bằng hoặc bội số stripe (vd 256K) để đẹp.
  • Random nhỏ: stripe ít ảnh hưởng nếu 1 disk; với nhiều disk, mismatch có thể gây phân phối kém.

8.3. Kernel/block layer:

  • Scheduler: none cho NVMe, mq-deadline/none cho SATA; tránh cfq trên server mới.
  • read_ahead_kb: tăng khi test seq; giảm khi test random.
  • nr_requests/queue_depth: đừng vượt trần thiết bị (SATA NCQ ~32).

8.4. CPU/NUMA/IRQ:

Pin CPU cho fio, cân bằng IRQ NVMe/SAS, bật performance governor để giảm drift.

9. Bẫy thường gặp

  • Dùng --fdatasync=1 trên raw + cache OFF và mong IOPS giảm: sẽ không khác mấy (không có gì để flush).
  • Quên --direct=1 khi muốn đo raw: số đo cao giả do page cache.
  • Dùng nhiều job cho sequential: biến seq thành semi-random, throughput xấu đi.
  • Không đặt --ramp_time: lấy số khi hệ chưa ổn định.
  • randrepeat=1 (mặc định) khi bạn muốn dữ liệu ngẫu nhiên khác nhau giữa các lần → đặt
  • randrepeat=0.
  • Test trên thiết bị đang mount/đang dùng: có thể mất dữ liệu hoặc số đo nhiễu.
  • Không precondition SSD khi so sánh mẫu khác nhau → kết quả “đẹp” bất thường.

10. Lời khuyên

Xác định mục tiêu trước (IOPS? throughput? latency? durability?) rồi mới chọn bs/QD/job/cache.

Quy trình 5 bước:

  • Test latency QD1 lấy baseline.
  • Sweep QD để tìm điểm bão hòa (plateau).
  • Chạy random 4K (IOPS) và sequential 256K (BW).
  • Chạy random throughput 128K–256K (VM/file server).
  • Chạy mixed (70/30 hoặc theo app).

Test A/B cache OFF vs ON để biết bạn được lợi bao nhiêu trong thực tế.

Luôn lưu JSON/log, đặt tên rõ ràng, có ngữ cảnh (firmware, nhiệt độ, kernel, cache state).

12. Sơ đồ logic chọn tham số

[Hỏi mục tiêu?] --> [Latency?] --> QD=1, bs=4k, read/write
                \-> [Max IOPS?] --> bs=4k, tăng QD (iodepth×numjobs) đến plateau
                \-> [Max BW?]   --> bs=256k (hoặc 128k/1M), numjobs=1 (seq), iodepth=32
                \-> [Mixed?]    --> randrw rwmixread=..., bs theo app (4k–64k), QD vừa phải
                \-> [Durability?]--> trên FS, không --direct, dùng --fdatasync=1
[Cache mục tiêu?] --> Raw (cache OFF) | Thực chiến (cache ON)

13. Checklist trước khi chạy

  • Xác định: random/seq, bs, r/w mix, thời gian chạy, ramp.
  • Chọn ioengine (io_uring nếu có), --direct=1 nếu cần raw.
  • Kiểm tra cache RAID/SSD theo mục tiêu.
  • Đảm bảo thiết bị không dùng bởi dịch vụ khác; biết rõ rủi ro dữ liệu.
  • Cài đặt scheduler/CPU governor hợp lý.
  • Thiết lập log/JSON để lưu kết quả.

14. Kết luận

Thiết kế bài test fio đúng mục tiêu quan trọng hơn mọi thứ khác. Hãy dùng định luật Little để ước lượng QD, chọn bs theo workload, điều khiển cache đúng ý (OS/RAID/SSD) và đọc kỹ percentiles để hiểu tail latency. Với quy trình – kịch bản ở trên, bạn sẽ có số đo tin cậy, tái lập và hữu ích để ra quyết định (tối ưu, so sánh phần cứng, đặt kỳ vọng cho ứng dụng).

Bài viết gần đây

spot_img

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Đăng ký nhận thông tin bài viết qua email