Med I/O Scheduler Tuning optimerer jeg specifikt Kernen-sti til hukommelsesadgang og reducere ventetiden i hostingmiljøer. Artiklen viser på en praktisk måde, hvordan jeg tilpasser Linux-diskplanlægning til hardware og arbejdsbyrde for at hosting ydeevne på en sikker måde.
Centrale punkter
Følgende nøglepunkter giver dig et hurtigt overblik over indholdet i denne artikel.
- Valg af planlægningsprogramNoop/none, mq-deadline, BFQ, Kyber afhængigt af hardware og arbejdsbyrde
- målemetodeFio, iostat, P95/P99, IOPS og throughput før/efter ændringer
- Finjusteringer: Readahead, RQ-Affinity, Cgroups, ionice til QoS
- Vedholdenhed: udev-regler og GRUB-parametre for vedvarende profiler
- ØvelseFejlfinding af latenstidstoppe, fairness og NVMe-specifikke forhold
Sådan fungerer Linux' diskplanlægning
Jeg ser I/O-planlæggeren som et kontrolcenter, der konverterer anmodninger til Stikord sorterer, fletter og prioriterer. Med HDD'er undgår jeg dyre hovedbevægelser ved at organisere forespørgsler efter blokadresser og dermed reducere søgetiden. På SSD'er og NVMe dominerer parallelisme, og derfor gør multikø-subsystemet blk-mq stien bredere og prioriterer flere anmodninger. CPU'er distribueret. Det reducerer ventetiden, udjævner spidsbelastninger og holder gennemstrømningen på sporet, selv om mange tjenester skriver og læser på samme tid. I hosting kommer webservere, databaser og backup-jobs sammen, så jeg tilpasser altid planlægningen til de dominerende adgangsmønstre.
De almindelige planlæggere kort forklaret
Til NVMe og moderne SSD'er vælger jeg ofte ingen (svarende til Noop i blk-mq-sammenhæng), fordi controlleren er optimeret internt, og ethvert ekstra overhead koster penge. mq-deadline sætter faste deadlines for læsninger og skrivninger, prioriterer læseoperationer og leverer konstante svartider i blandede serverbelastninger. BFQ fordeler båndbredden retfærdigt på tværs af processer og er velegnet til opsætninger med flere lejere, hvor individuelle VM'er ellers ville optage disken. Kyber sigter mod lave latenstider og sænker indgående anmodninger, hvis måltiderne overskrides. CFQ betragtes som en ældre løsning og passer næppe til NVMe; jeg bruger kun CFQ, når ældre opsætninger kræver det, eller test viser klare fordele; jeg giver en detaljeret oversigt her: Guide til I/O-planlægning.
Tuning af I/O Scheduler trin for trin
Jeg starter med en klar Baseline-målingskørsel, så jeg kan vise gevinster objektivt. Jeg bruger fio til syntetiske mønstre, iostat til enhedsstatistik og indsamler P95/P99-latencies for læsninger og skrivninger. Derefter tjekker jeg den aktive scheduler pr. enhed og ændrer den på runtime for hurtigt at lave en modtest. Jeg foretager kun vedvarende justeringer, når stabile målinger viser, at valget er rigtigt. På den måde undgår jeg forkerte beslutninger, som senere tvinger mig til at foretage dyre tilbagerulninger.
# Tjek den aktuelle planlægger
cat /sys/block//queue/scheduler
# Skift i farten (eksempel: nvme0n1 til mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
# Hurtig sammenligning med fio (tilfældige læsninger 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1
Jeg holder øje med CPU-belastningen, fordi en uegnet planlægningsprogram skaber yderligere kontekstskift og reducerer dermed nettoydelsen. Så snart ventetiden falder, og gennemstrømningen stiger, gemmer jeg beslutningen og dokumenterer testprofilerne. Hvert trin efterfølges af en ændring og derefter en måling, så jeg klart kan adskille årsag og virkning. Denne disciplin betaler sig, når der er installeret flere diskklasser på serveren, og de enkelte enheder reagerer forskelligt.
Fine justeringer: Readahead, RQ-affinitet, C-grupper
Når jeg har valgt planlæggeren, justerer jeg Kø-parameter for indlæsningen. For sekventielle backups hæver jeg readahead, for tilfældig IO sænker jeg den, så jeg ikke indlæser unødvendige sider. Med RQ-affinitet sikrer jeg, at afslutninger lander på den kerne, der genererede anmodningen, hvilket forbedrer latenstid og cache-lokalitet. Jeg bruger ionice til at nedgradere processer som sikkerhedskopiering og indeksering, så webanmodninger ikke lider skade. I miljøer med flere lejere regulerer jeg båndbredde og IOPS via Cgroups v2 for at sætte hårde grænser pr. kunde.
# Readahead for sekventielle mønstre
echo 128 | sudo tee /sys/block//queue/read_ahead_kb
# RQ-affinitet: 2 = færdiggørelse på den genererende kerne
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Lavere backup-proces
ionice -c2 -n7 -p
# Cgroup v2: vægt og grænse (eksempel 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
Hvilket valg er det rigtige til hosting af profiler?
Jeg beslutter, at planlægningsprogram-Valg i henhold til hardwareklasse, adgangsmønster og målstørrelse (latenstid vs. gennemstrømning vs. retfærdighed). NVMe SSD'er i VM'er med én lejer har normalt ingen fordele, fordi controlleren udfører omfattende optimering, og hvert softwarelag tæller. Til blandede læse-/skrivebelastninger på SSD'er bruger jeg ofte mq-deadline, da den prioriterer læseanmodninger og dermed beskytter svartiderne. I delte hostingmiljøer vælger jeg BFQ for at sikre retfærdighed mellem kunderne og forhindre båndbreddemonopoler. Jeg bruger Kyber, når target latencies er kritiske, og jeg er nødt til at overholde hårde grænser for visse workloads.
| planlægningsprogram | Passende hardware | Typiske arbejdsbelastninger | Fordele | Noter |
|---|---|---|---|---|
| Nej/ingen | NVMe, moderne SSD | Mange parallelle læsninger/skrivninger, VM'er | Minimalt overhead, høj parallelitet | Controller overtager sortering; test i SAN/RAID |
| mq-udløbsdato | SSD, SAS, hurtig harddisk | Blandede web/DB-belastninger | Prioriteret læsetid, god gennemstrømning | Deadline-værdier konservative; finjustering mulig |
| BFQ | SSD/HDD i multi-tenant | Mange brugere, cgroups | Tydelig fairness og båndbreddekontrol | En vis administrativ indsats, ren vægtning |
| Kyber | SSD, NVMe | Latency-kritiske tjenester | Målforsinkelser kan kontrolleres | Mål præcist for at indstille drosling korrekt |
| CFQ | Ældre hardware | Ældre arbejdsbyrder | Tidligere standardopløsning | Sjældent brugbar på moderne NVMe/SSD |
Praktiske profiler og målte værdier
For webservere med mange små Læser P95-latency tæller mere end ren IOPS, så jeg tester get requests med keep-alive og TLS i kombination. Databaser bringer synkroniseringsskrivninger i spil, og derfor simulerer jeg flush-adfærd og fsync-omkostninger med fio-jobfiler. Backup-vinduer har ofte sekventielle streams; her måler jeg throughput i MB/s og sørger for, at frontend-anmodninger ikke venter for længe. I mine tests ser jeg 20-50 % kortere svartider, afhængigt af den oprindelige situation, hvis planlæggeren og readahead matcher arbejdsbelastningen. Hvis du har brug for mere baggrundsviden om måling af diskgennemstrømning, kan du finde en introduktion her: Diskgennemstrømning i hosting.
Vedvarende konfiguration og automatisering
Jeg forankrer Valgmuligheder permanent via udev-regel, så enhederne starter direkte i den rette tilstand efter genstart. Jeg indstiller ofte none for NVMe, mq-deadline for SSD'er og BFQ for roterende medier, hvis fairness er altafgørende. Jeg indstiller eventuelt en global standard via GRUB, hvis jeg kører en homogen opsætning. Jeg holder reglerne korte og dokumenterer dem i Configuration Repository, så teamet kan følge dem. For mere dybdegående kerneoptimering supplerer denne artikel opsætningen: Kernens ydeevne i hosting.
# /etc/udev/rules.d/60-ioschedulers.rules
# NVMe: ingen
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
# SSD'er: mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]|vd[a-z]", ATTR{rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# HDD'er: BFQ
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{rotational}=="1", ATTR{queue/scheduler}="bfq"
# Genindlæs/test regler
udevadm control --reload
udevadm udløser
# Valgfri global standard via GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevator=mq-deadline"
opdater-grub
QoS med Cgroups v2 og ionice
Så intet job forlader tallerkenen blokeret, Jeg bruger QoS-regler med Cgroups v2 og tilføjer prioriteter via ionice. For premium-lejere hæver jeg io.weight, mens jeg sætter hårde grænser for støjende naboer med io.max. Jeg binder systemd-enheder direkte til Cgroups, så tjenesterne automatisk glider ind i den rigtige klasse ved opstart. Jeg drosler midlertidigt ned for kortvarigt vedligeholdelsesarbejde, så kundeforespørgsler fortsat kører problemfrit. Dette samspil mellem vægtning, grænser og procesprioritet skaber forudsigelige svartider, selv under belastning.
# Opret cgroup og sæt grænser
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
Flyt #-processen til Cgroup
echo | tee /sys/fs/cgroup/hosting/cgroup.procs
# Lav IO-prioritet for sekundære jobs
ionice -c2 -n7 -p
Overvågning og fejlfinding
Jeg opbevarer altid telemetri tæt på på arbejdsbelastningerne, ellers går jeg glip af beslutninger. Jeg bruger iostat til at aflæse servicetider og kø-dybder, blktrace til at analysere forespørgselsstrømme og sar/dstat til at se systembelastningen over tid. Når det gælder latenstider, ser jeg ikke kun på gennemsnitsværdier, men altid på P95/P99, fordi mærkbare hackere bliver synlige der. Hvis P95 er god, men P99 ikke er, justerer jeg kø-dybden eller RQ-affiniteten og tjekker konkurrerende jobs. Efter hver korrektion sammenligner jeg de samme nøgletal, så effekten forbliver pålidelig.
Typiske snublesten og løsninger
Høj Forsinkelse på SSD'er indikerer ofte en uegnet scheduler; så tester jeg straks mq-deadline og tjekker, om læsningerne bliver hurtigere. Jeg løser uretfærdig fordeling i multi-tenant-opsætninger med BFQ og rydder Cgroup-vægte, så stærke kunder ikke fortrænger svagere. NVMe-timeouts indikerer firmware eller for aggressiv polling; i sådanne tilfælde deaktiverer jeg io_poll og sænker dybden, indtil stabiliteten vender tilbage. Svingende gennemstrømning i backup-vinduer kan ofte udjævnes med tilpasset readahead, især når store filer dominerer. Hvis flere faktorer roterer på samme tid, går jeg frem trin for trin: en ændring, så måling, så den næste.
Scheduler-tunables i detaljer
Når det grundlæggende valg er truffet, drejer jeg på justeringsskruerne i den respektive scheduler. Jeg starter altid med at se på de tilgængelige parametre for hver enhed, da de varierer afhængigt af kernen og distroen.
# Vis tilgængelige tunables
ls -1 /sys/block//queue/iosched
cat /sys/block//queue/iosched/*
# Eksempel: mq-deadline mere konservativ for skrivetunge jobs
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
# Eksempel: BFQ for strengere retfærdighed og lavere tomgangstider
echo 1 | sudo tee /sys/block//queue/iosched/low_latency
echo 0 | sudo tee /sys/block//queue/iosched/slice_idle
På mq-deadline regulerer jeg primært read_expire/write_expire (i millisekunder) og front_merges til sammenlægning af afventende anmodninger. Med BFQ skifter jeg afhængigt af lejertætheden lav_latens og slice_idle, for at reducere ventetiden mellem flows. Jeg dokumenterer alle ændringer med målte værdier, da forkerte udløb kan udløse uønskede ventetidsspidser under burst-belastning.
Filsystem og mount-muligheder
Scheduler-tuning kommer først rigtigt til sin ret, når filsystemet matcher. Jeg er opmærksom på:
- relatime/noatimeUndgå unødvendig skriveadgang til metadata.
- kasseres mod fstrim: På SSD'er/NVMe bruger jeg normalt periodisk fstrim i stedet for online discard for at undgå latenstidstoppe.
- JournaliseringFor ext4 har følgende bevist deres værd data=ordnet (standard) og en passende commit=-interval (f.eks. 10-30s afhængigt af tolerancen for datatab).
- BarriererSkrivebarrierer forbliver aktive; jeg deaktiverer dem ikke, medmindre hardwaren garanterer beskyttelse mod strømsvigt (batteri/kondensator).
# Eksempel på /etc/fstab for ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2
# Aktivér periodisk TRIM i stedet for discard-indstilling
systemctl aktiver fstrim.timer
systemctl start fstrim.timer
For XFS satte jeg også Ingen tid og foretrækker fstrim.timer. Journal- eller barrieremuligheder er distributionsafhængige; jeg tester altid den specifikke kernel/FS-kombination og måler P95/P99.
RAID, LVM, DM-crypt og Multipath
I stablede opsætninger (Device Mapper, LVM, mdraid, Multipath) definerer jeg planlæggeren der, hvor applikationen ser I/O - dvs. ved Enhed på øverste niveau - og forhindre dobbeltsortering nedenunder.
# Indstil planlæggeren på øverste niveau (f.eks. dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler
# Underliggende NVMe/SAS-enheder "none" for at undgå dobbeltplanlægning
for d in /sys/block/nvme*n1 /sys/block/sd*; do echo none | sudo tee $d/queue/scheduler; done
# mdraid: Optimer readahead og stripe-cache (RAID5/6)
sudo blockdev --setra 4096 /dev/md0
echo 4096 | sudo tee /sys/block/md0/md/stripe_cache_size
Med krypterede diske (dm-crypt/LUKS) er jeg opmærksom på CPU-offload (AES-NI) og sikrer, at I/O-stien ikke vandrer unødigt på tværs af arbejdskøer. Jeg måler specifikt sync-write latencies, da disse kan øges på grund af kryptolaget. I multipath-miljøer (SAN/iSCSI) indstiller jeg planlæggeren på multipath-enheden (dm-X) og kontrollerer, at path failover ikke genererer nogen outliers.
Virtualisering og containere: vært vs. gæst
I KVM-stakken adskiller jeg bevidst vært og gæst. I Gæst Jeg bruger normalt til virtio-enheder ingen, så hypervisoren overtager optimeringen. På Vært Derefter vælger jeg den scheduler for hver fysisk enhed, der matcher hardwaren (ofte none/mq-deadline på SSD/NVMe).
#-gæst (virtio-blk/virtio-scsi): Sæt planlæggeren til "none
echo none | sudo tee /sys/block/vda/queue/scheduler
#-vært: QEMU med iothreads og multiqueue til virtio-blk
qemu-system-x86_64 \
-drive if=none,id=vd0,file=/var/lib/libvirt/images/guest.qcow2,cache=none,aio=native \
-objektet iothread,id=ioth0 \
-enhed virtio-blk-pci,drev=vd0,num-queues=8,iothread=ioth0
Jeg binder containere direkte til Cgroups v2 og bruger systemd-egenskaber (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax), så tjenesterne starter automatisk med de korrekte I/O-budgetter. Vigtigt: Prioritér kun på ét niveau - enten i containeren eller i værtstjenesten - for at undgå modstridende regler.
Optimering af NUMA, IRQ og polling
På systemer med flere sokler overvejer jeg I/O og CPU Tæt på NUMA. Jeg tjekker fordelingen af NVMe-interrupts og justerer dem om nødvendigt, hvis irqbalance fungerer suboptimalt. Jeg bruger også blk-mq-indstillinger til at holde afslutninger lokale.
# Tjek NVMe-afbrydelser og indstil kernemasker (eksempel)
grep -i nvme /proc/interrupts
echo | sudo tee /proc/irq//smp_affinity
# blk-mq: Afslutninger på genererende kerne
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Valgfrit: Test I/O-polling afhængigt af arbejdsbyrden (brug omhyggeligt)
echo 0 | sudo tee /sys/block//queue/io_poll
For NVMe kan jeg bruge controllerfunktioner til at justere parametrene for interrupt coalescing for at udjævne forholdet mellem CPU-belastning og latenstid. Jeg tager små skridt her og tjekker, om P99 forbliver stabil, eller om coalescing fører til synlig træghed.
Eksempel på fio-jobprofiler og måleplan
Jeg opretter reproducerbare jobfiler og noterer kernel, scheduler, kø-parametre og filsystem-mounts. Det giver mig mulighed for at sammenligne resultater over flere uger.
# db-sync.fio - DB-lignende synkroniseringsskrivninger (ext4/XFS)
[global]
ioengine=libaio
direkte=1
filnavn=/dev/
time_based=1
runtime=90
tråd=1
numjobs=8
iodepth=1
[randwrite-sync4k]
rw=randwrite
bs=4k
fsync=1
# web-randread.fio - Web-lignende læsninger
[global]
ioengine=libaio
direkte=1
filnavn=/dev/
time_based=1
runtime=90
tråd=1
numjobs=8
iodepth=32
[randread-4k]
rw=randread
bs=4k
# Måleramme
# 1) Opvarmning 60s, 2) Måling 90s, 3) Nedkøling 30s
# Parallelt: kør iostat, pidstat og blktrace
iostat -x 1 | tee iostat.log &
pidstat -dl 1 | tee pidstat.log &
blktrace -d /dev/ -o - | blkparse -i - -d trace.dump &
# Trace: Træk P95/P99 fra 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
Jeg ændrer altid kun én variabel, f.eks. scheduler eller read_ahead_kb, og sammenligner de identiske jobfiler igen. Først når forbedringerne er konsekvente over flere kørsler, gemmer jeg indstillingerne.
Forandringsledelse: sikker introduktion og tilbagerulning
I produktive hostingmiljøer udruller jeg I/O-ændringer forskudt Jeg kører en canary host, derefter en lille AZ/cluster-batch og først derefter den brede udrulning. Jeg versionerer Udev-regler og vedhæfter hver ændring til en ticket med målte værdier. Til tilbagerulningen har jeg et script klar, som afspiller de tidligere værdier (scheduler, read_ahead_kb, Cgroup-grænser). På den måde er indgrebene reversible, hvis arbejdsbelastningen ændrer sig med kort varsel.
Resumé: Sådan gør jeg
Jeg starter med en klar Faktisk værdi, Jeg måler latency og throughput og dokumenterer opsætningen. Derefter vælger jeg en passende scheduler til hver enhed: ingen til NVMe/virtuelle SSD'er, mq-deadline til blandede serverbelastninger, BFQ til delte miljøer med mange brugere. Derefter justerer jeg readahead, RQ-affinitet og procesprioriteter for at prioritere front-end-arbejdsbelastninger. Hvis målingerne konsekvent viser, at valget fungerer, retter jeg det via udev/GRUB og skriver parametrene. Overvågningen forbliver aktiv, fordi arbejdsbyrderne ændrer sig, og med små rettelser holder jeg Ydelse permanent høj.


