Đổi bộ lập lịch I/O là một trong những lời khuyên tinh chỉnh phổ biến nhất. Bài này đo xem nó cho bao nhiêu.

So sánh ba bộ lập lịch và ảnh hưởng của read_ahead_kb

Bài kiểm tra

Đây đúng là kịch bản mà bộ lập lịch sinh ra để xử lý: một tiến trình ghi tuần tự liên tục (tác vụ nền), một tiến trình khác đọc khối nhỏ 4 KB ngẫu nhiên (dịch vụ nhạy cảm với độ trễ). Bộ lập lịch tốt phải bảo vệ được kẻ đọc.

Ba lần đo mỗi bộ lập lịch:

Lần none mq-deadline kyber
1 15.726 IOPS, p99 171 µs 15.702 IOPS, p99 169 µs 15.190 IOPS, p99 183 µs
2 15.430 IOPS, p99 167 µs 15.424 IOPS, p99 165 µs 15.900 IOPS, p99 169 µs
3 15.310 IOPS, p99 183 µs 15.591 IOPS, p99 171 µs 15.879 IOPS, p99 173 µs

Toàn bộ dải: 15.190 – 15.900 IOPS, p99 165 – 183 µs. Không bộ lập lịch nào tách ra khỏi nhiễu. Thứ hạng còn đổi giữa các lần chạy.

Vì sao chúng bằng nhau

Bộ lập lịch I/O sinh ra để giải hai bài toán của đĩa quay:

  • Sắp xếp lại yêu cầu theo vị trí trên đĩa để đầu đọc đi ít nhất — thuật toán "thang máy".
  • Chống bỏ đói: giữ một yêu cầu đọc nhỏ khỏi bị chôn dưới hàng nghìn yêu cầu ghi tuần tự.

Bài toán thứ nhất biến mất trên ổ thể rắn: phần 19 đã đo và tuần tự với ngẫu nhiên cho cùng một con số, nên sắp xếp lại chẳng để làm gì.

Bài toán thứ hai gần như biến mất theo: NVMe có hàng nghìn hàng đợi phần cứng và phục vụ chúng song song, nên yêu cầu nhỏ không phải xếp sau yêu cầu lớn.

Đó là lý do none là mặc định cho NVMe trên mọi bản phân phối hiện đại. Nó không phải là "chưa cấu hình" — nó là lựa chọn đúng.

Trên đĩa quay thì hoàn toàn khác, và những lời khuyên bạn đọc được thường ra đời từ thời đó. Nếu hệ thống của bạn còn chạy đĩa quay, bfq hoặc mq-deadline vẫn tạo khác biệt lớn. Nhân trên máy tôi đo không có bfq nên tôi không đo được nó.

Nút thật sự có tác dụng

Cùng thiết bị, cùng phép đo đọc tuần tự qua bộ đệm trang, chỉ đổi cửa sổ đọc trước:

read_ahead_kb Thông lượng
4 375,9 MB/s
32 1.398,6 MB/s
128 (mặc định) 2.510,5 MB/s
512 3.370,8 MB/s
2.048 4.026,8 MB/s

10,7 lần.

Nút được người ta chỉnh nhiều nhất cho 0%. Nút gần như không ai đụng tới cho 10,7 lần.

Không phải vì bộ lập lịch vô dụng, mà vì phần cứng đã đổi và nó không còn là chỗ tắc nghẽn. Chỗ tắc nghẽn bây giờ là số yêu cầu đang bay — đúng như phần 19 đã đo với iodepth. Đọc trước là cách nhân tự tạo ra độ sâu hàng đợi thay cho bạn, và cửa sổ đọc trước quyết định nó tạo được bao nhiêu.

Đọc trước lớn không miễn phí

Cửa sổ 2 MB nghĩa là mỗi lần chạm vào một chỗ mới, nhân kéo về 2 MB. Với truy cập tuần tự thì tuyệt vời. Với truy cập ngẫu nhiên thì:

  • Lãng phí băng thông cho dữ liệu không bao giờ dùng tới.
  • Đẩy dữ liệu đang nóng ra khỏi bộ đệm trang.

Nên quy tắc là chỉnh theo tải, không chỉnh theo thiết bị:

Tải read_ahead_kb
Quét bảng lớn, sao lưu, phân tích dữ liệu 1024 – 4096
Mặc định, tải hỗn hợp 128
Cơ sở dữ liệu OLTP, truy cập ngẫu nhiên 16 – 64
blockdev --setra 2048 /dev/nvme0n1              # don vi 512 byte -> 1 MB
echo 1024 > /sys/block/nvme0n1/queue/read_ahead_kb   # don vi KB

Hai lệnh này chỉnh cùng một thứ với hai đơn vị khác nhau — một nguồn nhầm lẫn quen thuộc.

Đặt bền qua khởi động lại bằng udev:

# /etc/udev/rules.d/60-readahead.rules
ACTION=="add|change", KERNEL=="nvme[0-9]n[0-9]", ATTR{queue/read_ahead_kb}="1024"

Hai nút khác đáng biết

cat /sys/block/nvme0n1/queue/nr_requests   # do sau hang doi cua tang khoi
cat /sys/block/nvme0n1/queue/nomerges      # 0 = cho gop yeu cau lien ke
cat /sys/block/nvme0n1/queue/rotational    # 0 = the ran, 1 = dia quay

rotational là cờ mà nhân dùng để chọn mặc định. Máy ảo đôi khi báo sai — nếu nó là 1 trên một ổ SSD, nhân sẽ chọn bộ lập lịch và cửa sổ đọc trước của đĩa quay, và đó là một lỗi cấu hình thật sự đáng sửa.

Giới hạn của phép đo này

Thiết bị tôi đo là tệp loop nằm trên overlay nằm trong máy ảo. Bộ lập lịch của nhân trong máy ảo điều phối một hàng đợi ảo, còn việc sắp xếp thật diễn ra ở tầng dưới mà nó không với tới.

Nghĩa là kết quả "ba bộ lập lịch bằng nhau" được củng cố bởi bố trí này chứ không bị nó phủ định — trong container, bộ lập lịch còn ít việc hơn nữa. Nếu bạn chạy container, đó chính là tình huống của bạn.

Trên kim loại trần với đĩa quay, hãy tự đo lại. Đó cũng là điểm chung của cả loạt bài này: mọi con số đều thuộc về một máy cụ thể, và cái đáng mang đi là cách đo.

Thử ba mươi giây

Xem thiết bị của bạn đang dùng gì và thử đổi cửa sổ đọc trước:

for d in /sys/block/*/queue; do
  dev=$(echo $d | cut -d/ -f4)
  case $dev in loop*|ram*) continue;; esac
  printf "%-10s rotational=%s  ra=%-6s sched=%s\n" "$dev" \
    "$(cat $d/rotational)" "$(cat $d/read_ahead_kb)" "$(cat $d/scheduler)"
done

# thu tang cua so doc truoc roi do lai (can root, doi ten thiet bi)
d=nvme0n1
cu=$(cat /sys/block/$d/queue/read_ahead_kb)
for ra in 128 1024; do
  echo $ra > /sys/block/$d/queue/read_ahead_kb
  sync; echo 3 > /proc/sys/vm/drop_caches
  printf "ra=%-6s " $ra
  dd if=/duong/dan/tep-lon of=/dev/null bs=4k count=200000 2>&1 | tail -1
done
echo $cu > /sys/block/$d/queue/read_ahead_kb

Nếu dòng ra=1024 nhanh hơn đáng kể và tải của bạn là quét tuần tự, bạn vừa tìm được một cải thiện lớn hơn mọi thứ mà việc đổi bộ lập lịch có thể cho.

Phần sau: theo dõi I/O trong thực tế — iostat, iotop và cách đọc chúng cho đúng.