...

Kernel I/O Scheduler Tuning: Optymalizacja pod kątem wydajności hostingu

Dzięki I/O Scheduler Tuning optymalizuję w szczególności Jądro-ścieżka dostępu do pamięci i zmniejszyć opóźnienia w środowiskach hostingowych. Artykuł pokazuje w praktyczny sposób, jak dostosować planowanie dysków w systemie Linux do sprzętu i obciążenia, aby Hosting bezpieczna wydajność.

Punkty centralne

Poniższe kluczowe punkty dadzą ci szybki przegląd treści tego artykułu.

  • Wybór harmonogramuNoop/none, mq-deadline, BFQ, Kyber w zależności od sprzętu i obciążenia.
  • strategia pomiarowaFio, iostat, P95/P99, IOPS i przepustowość przed/po zmianach
  • Precyzyjna regulacjaReadahead, RQ-Affinity, Cgroups, ionice dla QoS
  • WytrwałośćReguły udev i parametry GRUB dla trwałych profili
  • PraktykaRozwiązywanie problemów w zakresie szczytów opóźnień, sprawiedliwości i specyfiki NVMe

Jak działa planowanie dysków w systemie Linux

Harmonogram I/O postrzegam jako centrum kontroli, które konwertuje żądania na Wskazówki sortuje, łączy i nadaje priorytety. W przypadku dysków HDD unikam kosztownych ruchów głowicy, organizując żądania zgodnie z adresami bloków, a tym samym skracając czas wyszukiwania. Na dyskach SSD i NVMe dominuje równoległość, dlatego podsystem wielu kolejek blk-mq poszerza ścieżkę i nadaje priorytety wielu żądaniom. Procesory rozproszone. Zmniejsza to opóźnienia, wygładza szczyty i utrzymuje przepustowość na właściwym poziomie, nawet jeśli wiele usług zapisuje i odczytuje w tym samym czasie. W hostingu serwery WWW, bazy danych i zadania tworzenia kopii zapasowych występują razem, więc zawsze dostosowuję harmonogramy do dominujących wzorców dostępu.

Krótkie wyjaśnienie najpopularniejszych programów planujących

W przypadku NVMe i nowoczesnych dysków SSD często wybieram brak (odpowiednik Noop w kontekście blk-mq), ponieważ kontroler jest zoptymalizowany wewnętrznie i każdy dodatkowy narzut kosztuje. mq-deadline ustala stałe terminy dla odczytów i zapisów, nadaje priorytet operacjom odczytu i zapewnia stały czas odpowiedzi przy mieszanych obciążeniach serwera. BFQ sprawiedliwie rozdziela przepustowość pomiędzy procesy i nadaje się do konfiguracji z wieloma dzierżawcami, w których poszczególne maszyny wirtualne zajmowałyby dysk. Kyber dąży do niskich opóźnień i spowalnia przychodzące żądania, jeśli docelowe czasy zostaną przekroczone. CFQ jest uważane za starsze rozwiązanie i raczej nie pasuje do NVMe; używam CFQ tylko wtedy, gdy wymagają tego starsze konfiguracje lub testy wykazują wyraźne zalety; przedstawiam szczegółowy przegląd tutaj: Przewodnik po harmonogramie we/wy.

Dostrajanie harmonogramu we/wy krok po kroku

Zaczynam od jasnego Linia bazowa-aby móc obiektywnie pokazać zyski. Używam fio do syntetycznych wzorców, iostat do statystyk urządzeń i zbieram opóźnienia P95/P99 dla odczytów i zapisów. Następnie sprawdzam aktywny harmonogram dla każdego urządzenia i zmieniam go w czasie wykonywania, aby szybko przeprowadzić kontr-test. Dokonuję trwałych korekt tylko wtedy, gdy stabilne pomiary pokazują, że wybór jest właściwy. W ten sposób unikam błędnych decyzji, które później wymuszają kosztowne wycofanie.

# Sprawdzenie bieżącego harmonogramu
cat /sys/block//queue/scheduler

# Zmiana w locie (przykład: nvme0n1 na mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler

# Szybkie porównanie z fio (losowe odczyty 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1

Zwracam uwagę na obciążenie procesora, ponieważ nieodpowiednie harmonogram tworzy dodatkowe przełączniki kontekstowe, a tym samym zmniejsza wydajność netto. Gdy tylko opóźnienia spadną, a przepustowość wzrośnie, zapisuję decyzję i dokumentuję profile testowe. Po każdym kroku następuje zmiana, a następnie pomiar, dzięki czemu mogę wyraźnie oddzielić przyczynę od skutku. Ta dyscyplina opłaca się, gdy w serwerze zainstalowanych jest kilka klas dysków, a poszczególne urządzenia reagują inaczej.

Dokładne regulacje: Readahead, RQ affinity, Cgroups

Po wybraniu harmonogramu ustawiam następujące ustawienia Kolejka-dla obciążenia. Dla sekwencyjnych kopii zapasowych podnoszę readahead, dla losowego IO obniżam go, aby nie ładować żadnych niepotrzebnych stron. Dzięki powinowactwu RQ upewniam się, że ukończenia trafiają do rdzenia, który wygenerował żądanie, co poprawia opóźnienia i lokalizację pamięci podręcznej. Używam ionice do obniżania wydajności procesów, takich jak tworzenie kopii zapasowych i indeksowanie, aby nie ucierpiały na tym żądania internetowe. W środowiskach z wieloma dzierżawcami reguluję przepustowość i IOPS za pomocą Cgroups v2, aby ustawić twarde limity dla każdego klienta.

# Readahead dla wzorców sekwencyjnych
echo 128 | sudo tee /sys/block//queue/read_ahead_kb

# RQ affinity: 2 = zakończenie na generującym rdzeniu
echo 2 | sudo tee /sys/block//queue/rq_affinity

# Niższy proces tworzenia kopii zapasowej
ionice -c2 -n7 -p 

# Cgroup v2: waga i limit (przykład major:minor 8:0)
echo 1000 | sudo tee /sys/fs/cgroup/hosting/io.weight
echo "8:0 rbps=50M wbps=25M" | sudo tee /sys/fs/cgroup/hosting/io.max

Który wybór jest odpowiedni dla profili hostingowych?

Decyduję harmonogram-Wybór w zależności od klasy sprzętu, wzorca dostępu i rozmiaru docelowego (opóźnienie vs. przepustowość vs. sprawiedliwość). Dyski SSD NVMe w maszynach wirtualnych z jednym dzierżawcą zwykle nie korzystają z żadnego z nich, ponieważ kontroler przeprowadza szeroko zakrojoną optymalizację i liczy się każda warstwa oprogramowania. W przypadku mieszanych obciążeń odczytu/zapisu na dyskach SSD często używam mq-deadline, ponieważ nadaje on priorytet żądaniom odczytu, a tym samym chroni czasy odpowiedzi. W środowiskach hostingu współdzielonego wybieram BFQ, aby zapewnić sprawiedliwość między klientami i zapobiec monopolom przepustowości. Używam Kyber, gdy docelowe opóźnienia są krytyczne i muszę przestrzegać sztywnych limitów dla niektórych obciążeń.

harmonogram Odpowiedni sprzęt Typowe obciążenia Zalety Uwagi
Noop/brak NVMe, nowoczesny dysk SSD Wiele równoległych odczytów/zapisów, maszyny wirtualne Minimalny narzut, wysoka równoległość Kontroler przejmuje sortowanie; test w SAN/RAID
mq-deadline SSD, SAS, szybki dysk twardy Mieszane obciążenia web/DB Priorytetowe opóźnienia odczytu, dobra przepustowość Wartości terminów są konserwatywne; możliwe jest ich dostosowanie
BFQ Dyski SSD/HDD dla wielu najemców Wielu użytkowników, grupy c Wyraźna sprawiedliwość i kontrola przepustowości Trochę wysiłku administracyjnego, czyste ważenie
Kyber SSD, NVMe Usługi o krytycznym opóźnieniu Możliwość kontrolowania opóźnień docelowych Precyzyjny pomiar w celu prawidłowego ustawienia dławienia
CFQ Starszy sprzęt Starsze obciążenia Dawne rozwiązanie standardowe Rzadko przydatne na nowoczesnych NVMe/SSD

Praktyczne profile i zmierzone wartości

Dla serwerów internetowych z wieloma małymi Odczyty Opóźnienie P95 liczy się bardziej niż czysty IOPS, więc testuję żądania z połączeniem keep-alive i TLS. Bazy danych wprowadzają do gry zapisy synchroniczne, dlatego symuluję zachowanie płukania i koszty fsync za pomocą plików zadań fio. Okna kopii zapasowych często mają sekwencyjne strumienie; tutaj mierzę przepustowość w MB/s i upewniam się, że żądania frontendu nie czekają zbyt długo. W moich testach widzę 20-50 % krótsze czasy odpowiedzi, w zależności od sytuacji początkowej, jeśli harmonogram i readahead pasują do obciążeń. Jeśli potrzebujesz więcej informacji na temat pomiaru przepustowości dysku, możesz znaleźć wprowadzenie tutaj: Przepustowość dysku w hostingu.

Trwała konfiguracja i automatyzacja

Zakotwiczam Wybór na stałe za pomocą reguły udev, aby urządzenia uruchamiały się bezpośrednio w odpowiednim trybie po ponownym uruchomieniu. Często ustawiam none dla NVMe, mq-deadline dla SSD i BFQ dla nośników obrotowych, jeśli najważniejsza jest sprawiedliwość. Opcjonalnie ustawiam globalne ustawienia domyślne za pośrednictwem GRUB-a, jeśli korzystam z jednorodnej konfiguracji. Reguły są krótkie i dokumentuję je w repozytorium konfiguracji, aby zespół mógł je śledzić. Aby uzyskać bardziej dogłębną optymalizację jądra, ten artykuł uzupełnia konfigurację: Wydajność jądra w hostingu.

# /etc/udev/rules.d/60-ioschedulers.rules

# NVMe: brak
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"

Dyski SSD #: mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]|vd[a-z]", ATTR{rotational}=="0", ATTR{queue/scheduler}="mq-deadline"

Dyski twarde #: BFQ
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{rotational}=="1", ATTR{queue/scheduler}="bfq"

# Przeładowanie/test reguł
udevadm control --reload
udevadm trigger
# Opcjonalne globalne ustawienia domyślne przez GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevator=mq-deadline"
update-grub

QoS z Cgroups v2 i ionice

Aby żadne zadanie nie opuściło talerza zablokowany, Polegam na regułach QoS z Cgroups v2 i dodaję priorytety za pośrednictwem ionice. Dla najemców premium podnoszę io.weight, podczas gdy dla hałaśliwych sąsiadów ustawiam twarde limity za pomocą io.max. Wiążę jednostki systemd bezpośrednio z Cgroups, aby usługi automatycznie wślizgiwały się do odpowiedniej klasy podczas uruchamiania. Tymczasowo dławię krótkoterminowe prace konserwacyjne, aby zapytania klientów nadal działały płynnie. Ta interakcja wagi, limitów i priorytetu procesu tworzy przewidywalne czasy odpowiedzi nawet pod obciążeniem.

# Utwórz grupę cgroup i ustaw limity
mkdir -p /sys/fs/cgroup/hosting
echo 1000 | tee /sys/fs/cgroup/hosting/io.weight
echo "8:0 rbps=100M wbps=60M" | tee /sys/fs/cgroup/hosting/io.max

Przenieś proces # do Cgroup
echo  | tee /sys/fs/cgroup/hosting/cgroup.procs

# Niski priorytet IO dla zadań drugorzędnych
ionice -c2 -n7 -p

Monitorowanie i rozwiązywanie problemów

Zawsze utrzymuję telemetrię blisko na obciążeniach, w przeciwnym razie brakuje mi decyzji. Używam iostat do odczytywania czasów usług i głębokości kolejek, blktrace do analizowania przepływów żądań i sar/dstat, aby zobaczyć obciążenie systemu w czasie. W przypadku opóźnień nie patrzę tylko na wartości średnie, ale zawsze na P95/P99, ponieważ tam widoczne są zauważalne czkawki. Jeśli P95 jest dobre, ale P99 nie, dostosowuję głębokość kolejki lub powinowactwo RQ i sprawdzam konkurencyjne zadania. Po każdej korekcie porównuję te same kluczowe wartości, aby efekt pozostał wiarygodny.

Typowe przeszkody i środki zaradcze

Wysoki Opóźnienie na dyskach SSD często wskazuje na nieodpowiedni harmonogram; następnie natychmiast testuję mq-deadline i sprawdzam, czy odczyty stają się szybsze. Niesprawiedliwą dystrybucję w konfiguracjach z wieloma dzierżawcami rozwiązuję za pomocą BFQ i usuwam wagi Cgroup, aby silni klienci nie wypierali słabszych. Limity czasu NVMe wskazują na oprogramowanie układowe lub zbyt agresywne odpytywanie; w takich przypadkach dezaktywuję io_poll i zmniejszam głębokość, aż do przywrócenia stabilności. Wahania przepustowości w oknach kopii zapasowych często można złagodzić za pomocą niestandardowego readahead, zwłaszcza gdy dominują duże pliki. Jeśli więcej czynników obraca się w tym samym czasie, postępuję krok po kroku: jedna zmiana, potem pomiar, potem następna.

Szczegółowe informacje o parametrach harmonogramu

Po dokonaniu podstawowego wyboru przekręcam śruby regulacyjne odpowiedniego harmonogramu. Zawsze zaczynam od sprawdzenia dostępnych parametrów dla każdego urządzenia, ponieważ różnią się one w zależności od jądra i dystrybucji.

# Wyświetlanie dostępnych ustawień
ls -1 /sys/block//queue/iosched
cat /sys/block//queue/iosched/*

# Przykład: mq-deadline bardziej konserwatywny dla zadań wymagających dużego zapisu
echo 100 | sudo tee /sys/block//queue/iosched/read_expire
echo 500 | sudo tee /sys/block//queue/iosched/write_expire
echo 1 | sudo tee /sys/block//queue/iosched/front_merges

Przykład #: BFQ dla większej sprawiedliwości i krótszych czasów bezczynności
echo 1 | sudo tee /sys/block//queue/iosched/low_latency
echo 0 | sudo tee /sys/block//queue/iosched/slice_idle

W mq-deadline reguluję głównie read_expire/write_expire (w milisekundach) i front_merges do scalania oczekujących żądań. W przypadku BFQ, w zależności od gęstości dzierżawców, przełączam niskie opóźnienie oraz slice_idle, aby skrócić czas oczekiwania między przepływami. Każdą zmianę dokumentuję zmierzonymi wartościami, ponieważ nieprawidłowe wygaśnięcia mogą powodować niepożądane szczyty opóźnień przy dużym obciążeniu.

Opcje systemu plików i montowania

Dostrajanie harmonogramu jest naprawdę przydatne tylko wtedy, gdy system plików pasuje. Zwracam uwagę na:

  • relatime/noatimeUnikanie niepotrzebnego zapisu metadanych.
  • discard vs. fstrimNa dyskach SSD/NVMe zwykle używam okresowego fstrim zamiast odrzutu online, aby uniknąć szczytów opóźnień.
  • DziennikarstwoW przypadku ext4 sprawdziły się następujące rozwiązania data=ordered (domyślnie) i odpowiedni commit=-interwał (np. 10-30s w zależności od tolerancji utraty danych).
  • BarieryBariery zapisu pozostają aktywne; nie dezaktywuję ich, chyba że sprzęt gwarantuje ochronę przed awarią zasilania (bateria/kondensator).
# Przykład /etc/fstab dla ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2

# Włączenie okresowego TRIM zamiast opcji discard
systemctl enable fstrim.timer
systemctl start fstrim.timer

Dla XFS ustawiłem również noatime i preferuję fstrim.timer. Opcje dziennika lub bariery zależą od dystrybucji; zawsze testuję konkretną kombinację kernel/FS i mierzę P95/P99.

RAID, LVM, szyfrowanie DM i wielościeżkowość

W konfiguracjach stosowych (Device Mapper, LVM, mdraid, Multipath) definiuję harmonogram w miejscu, w którym aplikacja widzi wejścia/wyjścia - tj. przy Urządzenie najwyższego poziomu - i zapobiec podwójnemu sortowaniu pod spodem.

# Ustaw harmonogram na najwyższym poziomie (np. dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler

# Podstawowe urządzenia NVMe/SAS "brak", aby uniknąć podwójnego harmonogramowania
for d in /sys/block/nvme*n1 /sys/block/sd*; do echo none | sudo tee $d/queue/scheduler; done

# mdraid: Optymalizacja readahead i stripe cache (RAID5/6)
sudo blockdev --setra 4096 /dev/md0
echo 4096 | sudo tee /sys/block/md0/md/stripe_cache_size

W przypadku wolumenów szyfrowanych (dm-crypt/LUKS) zwracam uwagę na odciążenie procesora (AES-NI) i upewniam się, że ścieżka I/O nie wędruje niepotrzebnie po kolejkach roboczych. W szczególności mierzę opóźnienia synchronizacji i zapisu, ponieważ mogą one wzrosnąć z powodu warstwy kryptograficznej. W środowiskach wielościeżkowych (SAN/iSCSI) ustawiam harmonogram na urządzeniu wielościeżkowym (dm-X) i sprawdzam, czy przełączanie awaryjne ścieżki nie generuje wartości odstających.

Wirtualizacja i kontenery: host kontra gość

W stosie KVM celowo oddzielam hosta od gościa. W Gość Zwykle używam do urządzeń virtio brak, aby hiperwizor przejął optymalizację. Na Gospodarz Następnie wybieram harmonogram dla każdego urządzenia fizycznego, który pasuje do sprzętu (często none/mq-deadline na SSD/NVMe).

Gość # (virtio-blk/virtio-scsi): Ustaw harmonogram na "none
echo none | sudo tee /sys/block/vda/queue/scheduler

Host #: QEMU z iothreads i multiqueue dla virtio-blk
qemu-system-x86_64 \
  -drive if=none,id=vd0,file=/var/lib/libvirt/images/guest.qcow2,cache=none,aio=native \
  -object iothread,id=ioth0 \
  -device virtio-blk-pci,drive=vd0,num-queues=8,iothread=ioth0

Wiążę kontenery bezpośrednio z Cgroups v2 i używam właściwości systemd (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax), aby usługi uruchamiały się automatycznie z odpowiednimi budżetami I/O. Ważne: priorytetyzuj tylko na jednym poziomie - albo w kontenerze, albo w usłudze hosta - aby uniknąć sprzecznych reguł.

Optymalizacja NUMA, IRQ i odpytywania

W systemach wielogniazdowych uwzględniam wejścia/wyjścia i procesor Blisko NUMA. Sprawdzam dystrybucję przerwań NVMe i dostosowuję je w razie potrzeby, jeśli irqbalance działa nieoptymalnie. Używam również opcji blk-mq, aby zachować lokalne zakończenia.

# Sprawdzenie przerwań NVMe i ustawienie masek rdzenia (przykład)
grep -i nvme /proc/interrupts
echo  | sudo tee /proc/irq//smp_affinity

# blk-mq: Zakończono generowanie rdzenia
echo 2 | sudo tee /sys/block//queue/rq_affinity

# Opcjonalnie: Przetestuj odpytywanie I/O w zależności od obciążenia (używaj ostrożnie)
echo 0 | sudo tee /sys/block//queue/io_poll

W przypadku NVMe mogę użyć funkcji kontrolera, aby dostosować parametry koalescencji przerwań w celu wygładzenia stosunku obciążenia procesora i opóźnień. Robię tutaj małe kroki i sprawdzam, czy P99 pozostaje stabilny, czy też koalescencja prowadzi do widocznego spowolnienia.

Przykładowe profile stanowisk fio i plan pomiarów

Tworzę powtarzalne pliki zadań i notuję jądro, scheduler, parametry kolejki i uchwyty systemu plików. Pozwala mi to porównywać wyniki na przestrzeni tygodni.

# db-sync.fio - Synchronizacja zapisu podobna do DB (ext4/XFS)
[global]
ioengine=libaio
direct=1
filename=/dev/
time_based=1
runtime=90
thread=1
numjobs=8
iodepth=1

[randwrite-sync4k]
rw=randwrite
bs=4k
fsync=1

# web-randread.fio - Web-like reads
[global]
ioengine=libaio
direct=1
filename=/dev/
time_based=1
runtime=90
thread=1
numjobs=8
iodepth=32

[randread-4k]
rw=randread
bs=4k
# Rama pomiarowa
# 1) Rozgrzewka 60s, 2) Pomiar 90s, 3) Schładzanie 30s
# Równolegle: uruchom iostat, pidstat i blktrace

iostat -x 1 | tee iostat.log &
pidstat -dl 1 | tee pidstat.log &
blktrace -d /dev/ -o - | blkparse -i - -d trace.dump &

# Trace: Wyciągnięcie P95/P99 z fio-JSON
fio --output-format=json --output=fio.json db-sync.fio
jq '.jobs[].lat_ns["percentile"]|{p95:.["95.000000"],p99:.["99.000000"]}' fio.json

Zawsze zmieniam tylko jedną zmienną, np. scheduler lub read_ahead_kb, i ponownie porównuję identyczne pliki zadań. Dopiero gdy ulepszenia są spójne w kilku uruchomieniach, zatwierdzam ustawienia.

Zarządzanie zmianami: bezpieczne wprowadzanie i wycofywanie zmian

W produktywnych środowiskach hostingowych wdrażam zmiany we/wy rozłożony w czasie Zaczynam od hosta kanaryjskiego, następnie małej partii AZ/klastra, a następnie szerokiego wdrożenia. Wersjonuję reguły Udev i dołączam każdą zmianę do biletu ze zmierzonymi wartościami. Do wycofania mam gotowy skrypt, który odtwarza poprzednie wartości (scheduler, read_ahead_kb, limity Cgroup). W ten sposób interwencje pozostają odwracalne, jeśli obciążenie zmieni się w krótkim czasie.

Podsumowanie: Tak właśnie postępuję

Zaczynam od jasnego Wartość rzeczywista, Mierzę opóźnienia i przepustowość oraz dokumentuję konfigurację. Następnie wybieram odpowiedni harmonogram dla każdego urządzenia: brak dla NVMe/wirtualnych dysków SSD, mq-deadline dla mieszanych obciążeń serwerów, BFQ dla współdzielonych środowisk z wieloma użytkownikami. Następnie dostosowuję readahead, powinowactwo RQ i priorytety procesów, aby nadać priorytet obciążeniom front-end. Jeśli pomiary konsekwentnie pokazują, że wybór działa, naprawiam go za pomocą udev/GRUB i zapisuję parametry. Monitorowanie pozostaje aktywne, ponieważ obciążenia się zmieniają, a przy niewielkich korektach utrzymuję parametry. Wydajność stale wysoki.

Artykuły bieżące