Sự cố nghẽn đĩa lúc 2 giờ sáng
Đã 2 giờ sáng, và cơ sở dữ liệu PostgreSQL của bạn vừa gặp sự cố. Một script sao lưu định kỳ bắt đầu chạy, và đột nhiên, độ trễ truy vấn tăng vọt từ 15ms lên 450ms. Người dùng chỉ thấy biểu tượng tải xoay vòng, và bảng điều khiển giám sát đang báo động đỏ với thời gian chờ I/O cao.
Script sao lưu của bạn không hề chiếm dụng CPU hay RAM. Nó chỉ đơn giản là đang “ngốn” đĩa. Đây là trường hợp điển hình của việc tranh chấp tài nguyên đĩa (disk contention). Theo mặc định, Linux cố gắng công bằng với mọi tiến trình, nhưng trên một máy chủ thực tế, “sự công bằng” thường có nghĩa là mọi ứng dụng đều bị ảnh hưởng như nhau. Bạn cần database có quyền ưu tiên tuyệt đối trong khi việc sao lưu phải lùi lại phía sau. Đây chính là lúc lập lịch I/O và ionice phát huy tác dụng.
Cách Linux quản lý luồng dữ liệu khổng lồ
Trước khi đi sâu vào terminal, bạn cần hiểu cách nhân (kernel) xử lý việc di chuyển dữ liệu. Khi năm ứng dụng khác nhau yêu cầu đọc hoặc ghi đồng thời, I/O Scheduler đóng vai trò như một cảnh sát giao thông, quyết định yêu cầu nào được ưu tiên truy cập đĩa trước.
Kiến trúc đa hàng đợi (Multi-Queue) hiện đại
Các phiên bản kernel cũ dựa vào các bộ lập lịch như CFQ (Completely Fair Queuing). Các kernel hiện đại (4.12+) hiện sử dụng lớp khối đa hàng đợi (blk-mq) được tối ưu hóa cho SSD, có thể xử lý hàng triệu hoạt động mỗi giây. Bạn sẽ thường gặp bốn loại sau:
- mq-deadline: Mặc định an toàn cho hầu hết các máy chủ. Nó ưu tiên việc đọc hơn việc ghi để tránh ứng dụng bị treo trong khi chờ dữ liệu.
- bfq (Budget Fair Queuing): Một bộ lập lịch thông minh. Nó cực kỳ tốt trong việc giữ cho hệ thống phản hồi nhanh ngay cả khi đang sao chép dữ liệu nền dung lượng lớn, dù tốn thêm một chút CPU.
- kyber: Được Facebook phát triển cho ổ lưu trữ NVMe cao cấp. Nó tập trung vào việc giữ độ trễ nghiêm ngặt dưới một ngưỡng mục tiêu, chẳng hạn như 2ms cho việc đọc.
- none: Được sử dụng cho các ổ NVMe cực nhanh, nơi mà chi phí quản lý của kernel thực tế tiêu tốn nhiều hiệu suất hơn là nó mang lại.
Hãy coi ionice giống như ‘Nice’ dành cho ổ đĩa
Trong khi bộ lập lịch quản lý toàn bộ đĩa, ionice thiết lập mức ưu tiên cho một tiến trình cụ thể. Nó tương đương với lệnh nice của CPU nhưng dành cho đĩa. Có ba phân lớp chính:
- Idle (Phân lớp 3): Tiến trình chỉ truy cập đĩa khi không có ai khác cần đến. Điều này hoàn hảo cho việc sao lưu hoặc xoay vòng log (log rotation).
- Best-effort (Phân lớp 2): Mặc định tiêu chuẩn. Nó có các mức ưu tiên từ 0-7, trong đó 0 là cao nhất.
- Real-time (Phân lớp 1): Tiến trình được quyền truy cập đĩa ngay lập tức. Hãy sử dụng tùy chọn này một cách thận trọng; một tiến trình mất kiểm soát có thể khiến toàn bộ hệ điều hành bị “đói” tài nguyên đĩa.
Thực hành: Tối ưu hóa máy chủ thực tế
Việc áp dụng các khái niệm này đòi hỏi bạn phải xác định được các điểm nghẽn phần cứng hiện tại. Hãy chuyển từ lý thuyết sang tối ưu hóa thực tế trên một hệ thống đang chạy.
Bước 1: Xác định bộ lập lịch đang hoạt động
Mỗi đĩa có thể sử dụng một “cảnh sát giao thông” khác nhau. Kiểm tra xem cái nào đang hoạt động bằng cách truy vấn hệ thống tệp sysfs. Thay thế sda bằng tên ổ đĩa của bạn, chẳng hạn như nvme0n1 hoặc vda.
cat /sys/block/sda/queue/scheduler
Kết quả sẽ hiển thị dạng như [mq-deadline] kyber bfq none. Tên nằm trong dấu ngoặc vuông là bộ lập lịch hiện đang được sử dụng.
Bước 2: Thay đổi bộ lập lịch không cần khởi động lại
Bạn có thể hoán đổi các bộ lập lịch ngay lập tức. Nếu bạn đang quản lý một máy chủ tệp đang gặp khó khăn với hàng trăm tác vụ ghi nhỏ đồng thời, việc chuyển sang bfq thường mang lại hiệu quả tức thì.
# Áp dụng BFQ cho ổ SATA đầu tiên
echo bfq | sudo tee /sys/block/sda/queue/scheduler
Bước 3: Giảm ưu tiên các tác vụ nền bằng ionice
Đây là thủ thuật hiệu quả nhất dành cho quản trị viên hệ thống. Nếu bạn đang chạy một bản sao lưu rsync 500GB, đừng để nó làm nghẽn máy chủ web. Hãy chạy nó trong phân lớp **Idle**.
# Cờ '-c 3' đưa rsync vào nhóm idle (chờ)
ionice -c 3 rsync -av /var/www/ /backup/www/
Nếu tiến trình đã đang chạy và làm chậm hệ thống, hãy tìm PID của nó và điều chỉnh ngay lập tức:
# Ép tiến trình 1234 sang 'Best-effort' với mức ưu tiên cao nhất (0)
sudo ionice -c 2 -n 0 -p 1234
Bước 4: Thiết lập thay đổi vĩnh viễn
Các thay đổi trực tiếp vào /sys/block/ sẽ mất khi bạn khởi động lại. Để cố định chúng, hãy tạo một quy tắc udev tại /etc/udev/rules.d/60-scheduler.rules:
# Sử dụng bfq cho ổ HDD truyền thống
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
# Sử dụng mq-deadline cho ổ SSD tiêu chuẩn
ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# Sử dụng none cho ổ NVMe tốc độ cao
ACTION=="add|change", KERNEL=="nvme[0-n]*", ATTR{queue/scheduler}="none"
Kiểm tra thực tế: Kiểm thử thiết lập của bạn
Đừng chỉ chọn một bộ lập lịch vì nghe có vẻ nhanh. Hiệu suất phần cứng là thứ khó dự đoán. Theo kinh nghiệm của tôi khi quản lý các cụm hơn 50 máy chủ vật lý, một cấu hình hoạt động tốt trên SSD doanh nghiệp của Samsung có thể làm nghẽn một ổ đĩa giá rẻ từ nhà cung cấp đám mây.
Luôn luôn đo kiểm (benchmark). Sử dụng fio để mô phỏng khối lượng công việc cụ thể của bạn. Đối với database, hãy theo dõi IOPS và độ trễ ở phân vị thứ 99 (99th-percentile). Đối với máy chủ truyền thông, hãy tập trung vào Băng thông (Throughput) tính bằng MB/s.
Chạy một tải giả lập trong khi script sao lưu đang hoạt động. Nếu độ trễ database của bạn vẫn dưới 20ms trong khi việc sao lưu chạy với ionice -c 3, bạn đã tối ưu hóa hệ thống thành công.
Tổng kết
Tối ưu hóa I/O đĩa không phải là tìm kiếm một nút “tăng tốc” thần kỳ. Đó là về việc phân bổ tài nguyên. Database cần đọc độ trễ thấp. Máy chủ tệp cần ghi băng thông cao. Các tác vụ nền cần tránh gây ảnh hưởng. Hãy bắt đầu bằng cách xác định bộ lập lịch hiện tại, sử dụng ionice cho lần truyền tệp lớn tiếp theo và sử dụng các quy tắc udev để duy trì các tối ưu hóa của bạn. Người dùng của bạn sẽ nhận thấy sự khác biệt về tốc độ phản hồi ngay lập tức.

