Met I/O Scheduler Tuning optimaliseer ik specifiek de Kernel-pad voor geheugentoegang en het verminderen van latency in hostingomgevingen. Het artikel laat op een praktische manier zien hoe ik Linux schijfplanning aanpas aan de hardware en werklast om hosting prestaties veilig.
Centrale punten
De volgende belangrijke punten geven je een snel overzicht van de inhoud van dit artikel.
- Scheduler selecterenNoop/none, mq-deadline, BFQ, Kyber afhankelijk van hardware en werklast
- meetstrategieFio, iostat, P95/P99, IOPS en doorvoer voor/na wijzigingen
- Fijne aanpassingenReadahead, RQ-Affiniteit, C-groepen, ionice voor QoS
- Volhardingudev regels en GRUB parameters voor persistente profielen
- PraktijkProblemen oplossen voor latentiepieken, eerlijkheid en NVMe-specifieke kenmerken
Hoe Linux schijfplanning werkt
Ik zie de I/O scheduler als een controlecentrum dat verzoeken omzet in Cues sorteert, samenvoegt en prioriteert. Met HDD's vermijd ik dure kopbewegingen door verzoeken te organiseren op basis van blokadressen en zo de zoektijd te verkorten. Op SSD's en NVMe domineert parallellisme, daarom maakt het multi-queue subsysteem blk-mq het pad breder en geeft het prioriteit aan meerdere verzoeken. CPU's gedistribueerd. Dit vermindert latenties, vlakt pieken af en houdt de doorvoer op schema, zelfs als veel services tegelijkertijd aan het schrijven en lezen zijn. Bij hosting komen webservers, databases en back-uptaken samen, dus ik stem de planning altijd af op de dominante toegangspatronen.
De gebruikelijke planners kort uitgelegd
Voor NVMe en moderne SSD's kies ik vaak geen (gelijk aan Noop in de blk-mq context), omdat de controller intern geoptimaliseerd is en elke extra overhead geld kost. mq-deadline stelt vaste deadlines in voor lezen en schrijven, geeft voorrang aan leesbewerkingen en levert constante responstijden bij gemengde serverbelastingen. BFQ verdeelt bandbreedte eerlijk over processen en is geschikt voor multi-tenant opstellingen waarbij individuele VM's anders de schijf in beslag zouden nemen. Kyber streeft naar lage latencies en vertraagt inkomende verzoeken als de doeltijden worden overschreden. CFQ wordt beschouwd als een legacy-oplossing en past nauwelijks bij NVMe; ik gebruik CFQ alleen als legacy-opstellingen dit vereisen of als tests duidelijke voordelen laten zien; ik geef hier een gedetailleerd overzicht: I/O-planner gids.
I/O Scheduler stap voor stap afstemmen
Ik begin met een duidelijke Basislijn-metingen uitvoeren zodat ik objectief de winst kan laten zien. Ik gebruik fio voor synthetische patronen, iostat voor apparaatstatistieken en verzamel P95/P99 latencies voor lezen en schrijven. Vervolgens controleer ik de actieve scheduler per apparaat en verander deze tijdens runtime om snel een tegentest uit te voeren. Ik maak alleen blijvende aanpassingen als stabiele metingen laten zien dat de keuze juist is. Op deze manier voorkom ik verkeerde beslissingen die later dure rollbacks forceren.
# Controleer huidige scheduler
cat /sys/block//queue/scheduler
# Verander on the fly (voorbeeld: nvme0n1 naar mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
# Snelle vergelijking met fio (Willekeurig lezen 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1
Ik houd de CPU-belasting in de gaten omdat een ongeschikte planner creëert extra contextwisselingen en verlaagt dus de nettoprestaties. Zodra latencies dalen en doorvoer toeneemt, sla ik de beslissing op en documenteer ik testprofielen. Elke stap wordt gevolgd door een verandering en daarna door een meting, zodat ik oorzaak en gevolg duidelijk kan scheiden. Deze discipline loont wanneer er meerdere schijfklassen in de server zijn geïnstalleerd en individuele apparaten verschillend reageren.
Fijne aanpassingen: Readahead, RQ-Affiniteit, Cgroepen
Nadat ik de planner heb geselecteerd, pas ik de Wachtrij-parameter voor het laden. Voor sequentiële back-ups verhoog ik readahead, voor random IO verlaag ik het zodat ik geen onnodige pagina's laad. Met RQ affiniteit zorg ik ervoor dat voltooiingen landen op de core die het verzoek heeft gegenereerd, wat de latentie en cache lokaliteit verbetert. Ik gebruik ionice om processen zoals back-ups en indexering te downgraden zodat webverzoeken er niet onder lijden. In multi-tenant omgevingen regel ik bandbreedte en IOPS via Cgroups v2 om harde limieten per klant in te stellen.
# Readahead voor sequentiële patronen
echo 128 | sudo tee /sys/block//queue/read_ahead_kb
# RQ affiniteit: 2 = voltooiing op genererende kern
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Back-up proces verlagen
ionice -c2 -n7 -p
# Cgroup v2: gewicht en limiet (voorbeeld 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
Welke keuze is de juiste voor het hosten van profielen?
Ik besluit de planner-Keuze op basis van hardwareklasse, toegangspatroon en doelgrootte (latency vs. throughput vs. fairness). NVMe SSD's in single-tenant VM's hebben meestal geen voordeel omdat de controller uitgebreide optimalisatie uitvoert en elke softwarelaag telt. Voor gemengde lees-/schrijfbelastingen op SSD's gebruik ik vaak mq-deadline omdat het leesverzoeken voorrang geeft en zo de responstijden beschermt. In shared hosting omgevingen kies ik voor BFQ om eerlijkheid tussen klanten te garanderen en bandbreedte monopolies te voorkomen. Ik gebruik Kyber wanneer doellatenties kritisch zijn en ik me moet houden aan harde limieten voor bepaalde werklasten.
| planner | Geschikte hardware | Typische werklasten | Voordelen | Opmerkingen |
|---|---|---|---|---|
| Noop/none | NVMe, moderne SSD | Veel parallelle lezen/schrijven, VM's | Minimale overhead, hoog parallellisme | Controller neemt sorteren over; test in SAN/RAID |
| mq-deadline | SSD, SAS, snelle HDD | Gemengde web/DB-belastingen | Leeslatenties geprioriteerd, goede doorvoer | Termijnwaarden conservatief; fine-tuning mogelijk |
| BFQ | SSD/HDD in multi-tenant | Veel gebruikers, cgroepen | Duidelijke eerlijkheid en bandbreedtecontrole | Enige administratieve inspanning, schone weging |
| Kyber | SSD, NVMe | Latency-kritieke services | Doellatenties regelbaar | Meet nauwkeurig om throttling correct in te stellen |
| CFQ | Verouderde hardware | Legacy werklasten | Voormalige standaardoplossing | Zelden nuttig op moderne NVMe/SSD |
Praktische profielen en gemeten waarden
Voor webservers met veel kleine Leest de P95 latency telt zwaarder dan pure IOPS, dus ik test get requests met keep-alive en TLS in combinatie. Databases brengen sync writes in het spel, daarom simuleer ik flush gedrag en fsync kosten met fio job bestanden. Back-up vensters hebben vaak sequentiële streams; hier meet ik doorvoer in MB/s en zorg ik ervoor dat frontend verzoeken niet te lang wachten. In mijn testen zie ik 20-50 % kortere responstijden, afhankelijk van de uitgangssituatie, als de scheduler en readahead overeenkomen met de workloads. Als je meer context nodig hebt over het meten van schijfdoorvoer, kun je hier een inleiding vinden: Schijfdoorvoer in hosting.
Permanente configuratie en automatisering
Ik veranker de Keuze permanent via udev-regel zodat apparaten direct in de juiste modus starten na opnieuw opstarten. Ik stel vaak none in voor NVMe, mq-deadline voor SSD's en BFQ voor roterende media als eerlijkheid belangrijk is. Optioneel stel ik een globale standaard in via GRUB als ik een homogene setup draai. Ik houd de regels kort en documenteer ze in de configuratierepository zodat het team ze kan volgen. Voor meer diepgaande kerneloptimalisatie vult dit artikel de setup aan: Kernelprestaties in hosting.
# /etc/udev/rules.d/60-ioschedulers.rules
# NVMe: geen
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none".
# SSD's: mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]|vd[a-z]", ATTR{rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# HDD's: BFQ
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{rotational}=="1", ATTR{queue/scheduler}="bfq"
# Regels herladen/testen
udevadm control --reload
udevadm trigger
# Optionele globale standaard via GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevator=mq-deadline"
update-grub
QoS met Cgroups v2 en ionice
Zodat geen enkele taak het bord verlaat geblokkeerd, Ik vertrouw op QoS-regels met Cgroups v2 en voeg prioriteiten toe via ionice. Voor premium huurders verhoog ik io.weight, terwijl ik harde grenzen stel voor luidruchtige buren met io.max. Ik bind systemd units direct aan Cgroups zodat diensten automatisch in de juiste klasse vallen bij het opstarten. Ik geef tijdelijk gas bij kortetermijnonderhoudswerk zodat aanvragen van klanten soepel blijven verlopen. Dit samenspel van weging, limieten en procesprioriteit zorgt voor voorspelbare responstijden, zelfs onder belasting.
# Maak cgroup en stel limieten in
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
Verplaats # proces naar Cgroup
echo | tee /sys/fs/cgroup/hosting/cgroup.procs
# Lage IO prioriteit voor secundaire taken
ionice -c2 -n7 -p
Monitoring en probleemoplossing
Ik bewaar altijd telemetrie sluiten op de werklasten, anders mis ik beslissingen. Ik gebruik iostat om servicetijden en wachtrijdiepten af te lezen, blktrace om aanvraagstromen te analyseren en sar/dstat om de systeembelasting in de loop van de tijd te bekijken. Voor latencies kijk ik niet alleen naar gemiddelde waarden, maar altijd naar P95/P99, omdat merkbare hackers daar zichtbaar worden. Als P95 goed is, maar P99 niet, pas ik de wachtrijdiepte of RQ affiniteit aan en controleer concurrerende jobs. Na elke correctie vergelijk ik dezelfde kengetallen, zodat het effect betrouwbaar blijft.
Typische struikelblokken en oplossingen
Hoog Latency op SSD's duidt vaak op een ongeschikte scheduler; ik test dan meteen mq-deadline en controleer of lezen sneller wordt. Ik los oneerlijke distributie in multi-tenant setups op met BFQ en wis Cgroup gewichten zodat sterke klanten de zwakkere niet verdringen. NVMe timeouts duiden op firmware of te agressieve polling; in zulke gevallen deactiveer ik io_poll en verlaag ik de diepte totdat de stabiliteit terugkeert. Fluctuerende doorvoer in back-upvensters kan vaak worden afgevlakt met aangepaste readahead, vooral wanneer grote bestanden domineren. Als er meer factoren tegelijkertijd draaien, ga ik stap voor stap te werk: één verandering, dan meten, dan de volgende.
Scheduler-tunables in detail
Zodra de basisselectie is gemaakt, draai ik aan de stelschroeven van de respectievelijke schedulers. Ik begin altijd met het bekijken van de beschikbare parameters voor elk apparaat, aangezien deze variëren afhankelijk van de kernel en distro.
# Beschikbare tunables weergeven
ls -1 /sys/block//queue/iosched
cat /sys/block//queue/iosched/*
# Voorbeeld: mq-deadline conservatiever voor jobs met veel schrijfwerk
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
# Voorbeeld: BFQ voor striktere eerlijkheid en lagere inactiviteitstijden
echo 1 | sudo tee /sys/block//queue/iosched/low_latency
echo 0 | sudo tee /sys/block//queue/iosched/slice_idle
Bij mq-deadline regel ik voornamelijk read_expire/write_expire (in milliseconden) en front_merges voor het samenvoegen van lopende aanvragen. Met BFQ schakel ik, afhankelijk van de huurdersdichtheid low_latency en slice_idle, om wachttijden tussen flows te verminderen. Ik documenteer elke verandering met gemeten waarden, omdat onjuiste verlopen ongewenste latentiepieken kunnen veroorzaken onder burstbelasting.
Bestandssysteem en koppelopties
Scheduler tuning komt pas echt tot zijn recht als het bestandssysteem overeenkomt. Ik let op:
- relatijd/geen-tijdVermijd onnodige schrijftoegang tot metadata.
- weggooien vs. fstrimOp SSD's/NVMe gebruik ik meestal periodieke fstrim in plaats van online discard om latency-pieken te vermijden.
- DagboekVoor ext4 hebben de volgende zich bewezen data=geordend (standaard) en een geschikte verbinden=-interval (bijv. 10-30s afhankelijk van de tolerantie voor gegevensverlies).
- BelemmeringenSchrijfbarrières blijven actief; ik deactiveer ze niet tenzij de hardware bescherming tegen stroomuitval garandeert (batterij/condensator).
# Voorbeeld /etc/fstab voor ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2
# Periodieke TRIM inschakelen in plaats van weggooi-optie
systemctl activeer fstrim.timer
systemctl start fstrim.timer
Voor XFS stel ik ook noatime en geef de voorkeur aan fstrim.timer. Journaal of barrière opties zijn distributie afhankelijk; ik test altijd de specifieke kernel/FS combinatie en meet P95/P99.
RAID, LVM, DM-crypt en Multipath
In gestapelde opstellingen (Device Mapper, LVM, mdraid, Multipath) definieer ik de scheduler daar waar de applicatie I/O ziet, dus bij de Apparaat op het hoogste niveau - en dubbele sortering eronder voorkomen.
# Stel scheduler in op het hoogste niveau (bijv. dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler
# Onderliggende NVMe/SAS apparaten "none" om dubbele scheduling te voorkomen
for d in /sys/block/nvme*n1 /sys/block/sd*; do echo none | sudo tee $d/queue/scheduler; done
# mdraid: leeskop en stripe cache optimaliseren (RAID5/6)
sudo blockdev --setra 4096 /dev/md0
echo 4096 | sudo tee /sys/block/md0/md/stripe_cache_size
Met versleutelde volumes (dm-crypt/LUKS) let ik op CPU offload (AES-NI) en zorg ik ervoor dat het I/O-pad niet onnodig over werkwachtrijen dwaalt. Ik meet specifiek sync-write latencies, omdat deze kunnen toenemen door de cryptolaag. In multipath omgevingen (SAN/iSCSI) stel ik de scheduler in op het multipath apparaat (dm-X) en controleer ik of path failover geen uitschieters genereert.
Virtualisatie en containers: host vs. gast
In de KVM-stack heb ik host en gast bewust gescheiden. In de Gast Ik gebruik meestal voor virtio-apparaten geen, zodat de hypervisor de optimalisatie overneemt. Op de Gastheer Vervolgens selecteer ik de scheduler voor elk fysiek apparaat dat overeenkomt met de hardware (vaak none/mq-deadline op SSD/NVMe).
# Gast (virtio-blk/virtio-scsi): Stel scheduler in op "none
echo none | sudo tee /sys/block/vda/queue/scheduler
# Host: QEMU met iothreads en multiqueue voor virtio-blk
qemu-systeem-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
Ik bind containers direct aan Cgroups v2 en gebruik systemd eigenschappen (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax) zodat services automatisch starten met de juiste I/O-budgetten. Belangrijk: Prioriteer slechts op één niveau - in de container of in de hostservice - om conflicterende regels te voorkomen.
NUMA, IRQ en polling optimalisatie
Op multi-socket systemen beschouw ik I/O en CPU Dichtbij NUMA. Ik controleer de verdeling van NVMe interrupts en pas ze indien nodig aan als irqbalance suboptimaal werkt. Ik gebruik ook blk-mq opties om voltooiingen lokaal te houden.
# NVMe interrupts controleren en kernmaskers instellen (voorbeeld)
grep -i nvme /proc/interrupts
echo | sudo tee /proc/irq//smp_affinity
# blk-mq: voltooiingen op het genereren van kern
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Optioneel: Test I/O polling afhankelijk van de werklast (voorzichtig gebruiken)
echo 0 | sudo tee /sys/block//queue/io_poll
Voor NVMe kan ik controllerfuncties gebruiken om de parameters voor interruptcoalescing aan te passen om de verhouding tussen CPU-belasting en latentie glad te strijken. Ik neem hier kleine stappen en controleer of P99 stabiel blijft of dat coalescing leidt tot zichtbare traagheid.
Voorbeeldfunctieprofielen en meetplan fio
Ik maak reproduceerbare jobbestanden en noteer de kernel, scheduler, wachtrijparameters en mounts van het bestandssysteem. Hierdoor kan ik de resultaten over weken vergelijken.
# db-sync.fio - DB-achtige synchronisatie schrijft (ext4/XFS)
[global]
io-engine=libaio
direct=1
bestandsnaam=/dev/
tijdgebaseerd=1
runtime=90
thread=1
numjobs=8
iodepth=1
[randwrite-sync4k]
rw=randwrite
bs=4k
fsync=1
# web-randread.fio - Web-achtig lezen
[global]
io-engine=libaio
direct=1
bestandsnaam=/dev/
tijdgebaseerd=1
runtime=90
thread=1
numjobs=8
iodepth=32
[randread-4k]
rw=randread
bs=4k
# Meetframe
# 1) Opwarmen 60s, 2) Meting 90s, 3) Afkoelen 30s
# Parallel: iostat, pidstat en blktrace uitvoeren
iostat -x 1 | tee iostat.log &
pidstat -dl 1 | tee pidstat.log &
blktrace -d /dev/ -o - | blkparse -i -d trace.dump &
# Trace: P95/P99 uit fio-JSON halen
fio --output-format=json --output=fio.json db-sync.fio
jq '.jobs[].lat_ns["percentile"]|{p95:.["95.000000"],p99:.["99.000000"]}' fio.json
Ik verander altijd maar één variabele, bijvoorbeeld scheduler of read_ahead_kb, en vergelijk de identieke jobbestanden opnieuw. Pas als de verbeteringen consistent zijn over meerdere runs, leg ik de instellingen vast.
Veranderingsbeheer: veilig introduceren en terugdraaien
In productieve hostingomgevingen rol ik I/O-wijzigingen uit gespreid Ik begin met een canary host, dan een kleine AZ/cluster batch, gevolgd door een brede uitrol. Ik versie Udev regels en koppel elke verandering aan een ticket met gemeten waarden. Voor de rollback heb ik een script klaarstaan dat de vorige waarden (scheduler, read_ahead_kb, Cgroup limieten) afspeelt. Op deze manier blijven interventies omkeerbaar als workloads op korte termijn veranderen.
Samenvatting: Zo ga ik te werk
Ik begin met een duidelijke Werkelijke waarde, Ik meet latencies en doorvoer en documenteer de opstelling. Vervolgens selecteer ik een geschikte scheduler voor elk apparaat: geen voor NVMe/virtuele SSD's, mq-deadline voor gemengde serverbelastingen, BFQ voor gedeelde omgevingen met veel gebruikers. Vervolgens pas ik readahead, RQ affiniteit en procesprioriteiten aan om front-end werklasten voorrang te geven. Als metingen consistent laten zien dat de keuze werkt, zet ik het vast via udev/GRUB en schrijf ik de parameters weg. Het monitoren blijft actief omdat werklasten veranderen en met kleine correcties houd ik de Prestaties permanent hoog.


