...

Kernel I/O Scheduler Tuning: Optimering för prestanda på webbhotell

Med I/O Scheduler Tuning optimerar jag specifikt Kärnan-väg för minnesåtkomst och minska latensen i värdmiljöer. Artikeln visar på ett praktiskt sätt hur jag anpassar Linux diskschemaläggning till maskinvaran och arbetsbelastningen för att värdskap prestanda på ett säkert sätt.

Centrala punkter

Följande huvudpunkter ger dig en snabb överblick över innehållet i denna artikel.

  • Val av schemaläggareNoop/none, mq-deadline, BFQ, Kyber beroende på hårdvara och arbetsbelastning
  • mätstrategiFio, iostat, P95/P99, IOPS och genomströmning före/efter ändringar
  • Finjusteringar: Readahead, RQ-Affinity, Cgroups, ionice för QoS
  • Uthållighet: udev-regler och GRUB-parametrar för permanenta profiler
  • ÖvningFelsökning för fördröjningstoppar, rättvisa och NVMe-specifika detaljer

Hur Linux diskschemaläggning fungerar

Jag ser I/O-schemaläggaren som ett kontrollcenter som omvandlar förfrågningar till Ledtrådar sorterar, slår samman och prioriterar. Med hårddiskar undviker jag dyra huvudrörelser genom att organisera förfrågningar enligt blockadresser och därmed minska söktiderna. På SSD-enheter och NVMe dominerar parallelliteten, vilket är anledningen till att subsystemet blk-mq med flera köer gör sökvägen bredare och prioriterar flera förfrågningar. Processorer distribuerad. Detta minskar latenserna, jämnar ut toppar och håller genomströmningen på rätt spår, även om många tjänster skriver och läser samtidigt. Inom hosting samsas webbservrar, databaser och backupjobb, så jag anpassar alltid schemaläggningen efter de dominerande åtkomstmönstren.

De vanliga schemaläggarna förklaras kortfattat

För NVMe och moderna SSD-enheter väljer jag ofta ingen (motsvarande Noop i blk-mq-sammanhang), eftersom styrenheten optimeras internt och varje ytterligare overhead kostar pengar. mq-deadline sätter fasta deadlines för läsningar och skrivningar, prioriterar läsoperationer och ger konstanta svarstider i blandade serverbelastningar. BFQ fördelar bandbredden rättvist mellan processer och är lämplig för multitenant-konfigurationer där enskilda virtuella datorer annars skulle uppta disken. Kyber strävar efter låga latenser och saktar ner inkommande förfrågningar om måltiderna överskrids. CFQ anses vara en äldre lösning och passar knappast NVMe; jag använder CFQ endast när äldre konfigurationer kräver det eller när tester visar tydliga fördelar; jag ger en detaljerad översikt här: Guide för I/O-schemaläggare.

Tuning av I/O Scheduler steg för steg

Jag börjar med en tydlig Baslinje-mätningskörning så att jag kan visa vinster objektivt. Jag använder fio för syntetiska mönster, iostat för enhetsstatistik och samlar in P95/P99-latenstider för läsningar och skrivningar. Jag kontrollerar sedan den aktiva schemaläggaren per enhet och ändrar den vid körning för att snabbt göra ett mottest. Jag gör bara permanenta justeringar när stabila mätningar visar att valet är rätt. På så sätt undviker jag felaktiga beslut som senare tvingar fram dyra återställningar.

# Kontrollera aktuell schemaläggare
cat /sys/block//queue/scheduler

# Ändra i farten (exempel: nvme0n1 till mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler

# Snabb jämförelse med fio (slumpmässiga läsningar 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1

Jag håller ett öga på CPU-belastningen eftersom en olämplig schemaläggare skapar ytterligare kontextbyten och minskar därmed nettoprestandan. Så snart latenserna sjunker och genomströmningen ökar sparar jag beslutet och dokumenterar testprofilerna. Varje steg följs av en förändring och sedan en mätning, så att jag tydligt kan skilja på orsak och verkan. Den här disciplinen lönar sig när flera diskklasser installeras i servern och enskilda enheter reagerar olika.

Fina justeringar: Readahead, RQ-Affinity, Cgroups

Efter att ha valt schemaläggaren justerar jag -parameter för belastningen. För sekventiella säkerhetskopior höjer jag readahead, för slumpmässig IO sänker jag den så att jag inte laddar några onödiga sidor. Med RQ-affinitet ser jag till att slutföranden landar på den kärna som genererade begäran, vilket förbättrar latens och cache-lokalitet. Jag använder ionice för att nedgradera processer som säkerhetskopiering och indexering så att webbförfrågningar inte blir lidande. I miljöer med flera hyresgäster reglerar jag bandbredd och IOPS via Cgroups v2 för att sätta hårda gränser per kund.

# Readahead för sekventiella mönster
echo 128 | sudo tee /sys/block//queue/read_ahead_kb

# RQ-affinitet: 2 = slutförande på genererande kärna
echo 2 | sudo tee /sys/block//queue/rq_affinity

# Lägre säkerhetskopieringsprocess
ionice -c2 -n7 -p 

# Cgroup v2: vikt och gräns (exempel 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

Vilket val är rätt för hostingprofiler?

Jag bestämmer schemaläggare-Val beroende på maskinvaruklass, åtkomstmönster och målstorlek (latens vs. genomströmning vs. rättvisa). NVMe SSD-enheter i virtuella datorer med en enda hyresgäst har vanligtvis ingen fördel eftersom styrenheten utför omfattande optimering och varje programvarulager räknas. För blandade läs-/skrivbelastningar på SSD-enheter använder jag ofta mq-deadline eftersom det prioriterar läsförfrågningar och därmed skyddar svarstiderna. I miljöer med delad hosting väljer jag BFQ för att säkerställa rättvisa mellan kunderna och förhindra monopol på bandbredd. Jag använder Kyber när mållatenserna är kritiska och jag måste hålla mig till hårda gränser för vissa arbetsbelastningar.

schemaläggare Lämplig hårdvara Typiska arbetsbelastningar Fördelar Anteckningar
Noop/ingen NVMe, modern SSD Många parallella läsningar/skrivningar, virtuella datorer Minimalt overhead, hög parallellitet Styrenheten tar över sorteringen; test i SAN/RAID
mq-deadline SSD, SAS, snabb hårddisk Blandade webb-/DB-belastningar Prioriterade läslatenstider, bra genomströmning Konservativa värden för tidsfristen; finjusteringar möjliga
BFQ SSD/HDD i multi-tenant Många användare, cgroups Tydlig rättvisa och bandbreddskontroll Viss administrativ insats, ren viktning
Kyber SSD, NVMe Latenskritiska tjänster Målets latenstid kan kontrolleras Mät exakt för att ställa in strypningen korrekt
CFQ Äldre hårdvara Äldre arbetsbelastningar Tidigare standardlösning Sällan användbar på moderna NVMe/SSD

Praktiska profiler och uppmätta värden

För webbservrar med många små Läsning P95-latenstiden räknas mer än rena IOPS, så jag testar get requests med keep-alive och TLS i kombination. Databaser medför synkroniserade skrivningar, och därför simulerar jag flush-beteende och fsync-kostnader med fio-jobbfiler. Backup-fönster har ofta sekventiella strömmar; här mäter jag genomströmning i MB/s och ser till att frontend-förfrågningar inte väntar för länge. I mina tester ser jag 20-50 % kortare svarstider, beroende på den ursprungliga situationen, om schemaläggaren och readahead matchar arbetsbelastningen. Om du behöver mer information om hur man mäter diskgenomströmning kan du hitta en introduktion här: Diskgenomströmning i hosting.

Kontinuerlig konfiguration och automatisering

Jag förankrar Val permanent via udev-regeln så att enheterna startar direkt i rätt läge efter omstart. Jag ställer ofta in none för NVMe, mq-deadline för SSD-enheter och BFQ för roterande media om rättvisa är av största vikt. Jag ställer eventuellt in en global standard via GRUB om jag kör en homogen installation. Jag håller reglerna korta och dokumenterar dem i konfigurationsarkivet så att teamet kan spåra dem. För mer djupgående optimering av kärnan kompletterar den här artikeln installationen: Kernel-prestanda i hosting.

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

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

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

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

# Ladda om/testa regler
udevadm kontroll --återuppladda
udevadm utlösare
# Valfri global standard via GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevator=mq-deadline"
uppdatera-grub

QoS med Cgroups v2 och ionice

Så att inget jobb lämnar plattan blockerad, Jag förlitar mig på QoS-regler med Cgroups v2 och lägger till prioriteringar via ionice. För premiumhyresgäster höjer jag io.weight, medan jag sätter hårda gränser för bullriga grannar med io.max. Jag binder systemd-enheter direkt till Cgroups så att tjänsterna automatiskt hamnar i rätt klass vid uppstart. Jag stryper tillfälligt kortsiktigt underhållsarbete så att kundförfrågningar fortsätter att fungera smidigt. Detta samspel mellan viktning, gränser och processprioritering skapar förutsägbara svarstider även under belastning.

# Skapa cgroup och sätt 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

Flytta #-processen till Cgroup
echo  | tee /sys/fs/cgroup/hosting/cgroup.procs

# Låg IO-prioritet för sekundära jobb
ionice -c2 -n7 -p

Övervakning och felsökning

Jag behåller alltid telemetri nära på arbetsbelastningen, annars missar jag beslut. Jag använder iostat för att läsa av servicetider och ködjup, blktrace för att analysera requestflöden och sar/dstat för att se systembelastningen över tid. För latenser tittar jag inte bara på medelvärden, utan alltid på P95/P99, eftersom märkbara hackare blir synliga där. Om P95 är bra, men P99 inte är det, justerar jag ködjupet eller RQ-affiniteten och kontrollerar konkurrerande jobb. Efter varje korrigering jämför jag samma nyckeltal så att effekten förblir tillförlitlig.

Typiska stötestenar och lösningar

Hög Fördröjning på SSD-diskar tyder ofta på en olämplig schemaläggare; jag testar då omedelbart mq-deadline och kontrollerar om läsningarna blir snabbare. Jag löser orättvis fördelning i multitenantuppsättningar med BFQ och rensar Cgroup-vikter så att starka kunder inte tränger ut svagare. NVMe-timeouts indikerar firmware eller för aggressiv polling; i sådana fall avaktiverar jag io_poll och sänker djupet tills stabiliteten återvänder. Fluktuerande genomströmning i backupfönster kan ofta jämnas ut med anpassad readahead, särskilt när stora filer dominerar. Om fler faktorer roterar samtidigt går jag vidare steg för steg: en förändring, sedan mätning, sedan nästa.

Schemaläggningsinställningar i detalj

När det grundläggande urvalet har gjorts vrider jag på justeringsskruvarna för respektive schemaläggare. Jag börjar alltid med att titta på de tillgängliga parametrarna för varje enhet, eftersom de varierar beroende på kärna och distro.

# Visa tillgängliga tunables
ls -1 /sys/block//queue/iosched
cat /sys/block//kö/iosched/*

# Exempel: mq-deadline mer konservativ för skrivtunga jobb
echo 100 | sudo tee /sys/block//kö/iosched/read_expire
echo 500 | sudo tee /sys/block//kö/iosched/write_expire
echo 1 | sudo tee /sys/block//kö/iosched/front_merges

# Exempel: BFQ för striktare rättvisa och lägre inaktivitetstider
echo 1 | sudo tee /sys/block//kö/iosched/low_latency
echo 0 | sudo tee /sys/block//kö/iosched/slice_idle

Vid mq-deadline reglerar jag främst read_expire/write_expire (i millisekunder) och front_merges för att slå samman väntande förfrågningar. Med BFQ, beroende på hyresgästtätheten, byter jag låg latens och slice_idle, för att minska väntetiderna mellan flöden. Jag dokumenterar varje ändring med uppmätta värden, eftersom felaktiga expires kan ge upphov till oönskade fördröjningstoppar under burst-belastning.

Alternativ för filsystem och montering

Schemaläggning kommer bara till sin rätt när filsystemet matchar. Jag är uppmärksam på:

  • relatid/noatidUndvik onödig skrivåtkomst till metadata.
  • kasta mot fstrim: På SSD/NVMe använder jag vanligtvis periodisk fstrim istället för online discard för att undvika latens toppar.
  • JournalföringFör ext4 har följande visat sig fungera data=ordnad (standard) och en lämplig commit=-intervall (t.ex. 10-30s beroende på tolerans för dataförlust).
  • HinderSkrivspärrarna förblir aktiva; jag avaktiverar dem inte om inte hårdvaran garanterar skydd mot strömavbrott (batteri/kondensator).
# Exempel på /etc/fstab för ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2

# Aktivera periodisk TRIM i stället för alternativet discard
systemctl aktivera fstrim.timer
systemctl starta fstrim.timer

För XFS ställer jag också in ingen tid och föredrar fstrim.timer. Journal- eller barriäralternativ är distributionsberoende; jag testar alltid den specifika kernel/FS-kombinationen och mäter P95/P99.

RAID, LVM, DM-crypt och Multipath

I staplade konfigurationer (Device Mapper, LVM, mdraid, Multipath) definierar jag schemaläggaren där programmet ser I/O - dvs. vid Enhet på högsta nivå - och förhindra dubbelsortering undertill.

# Ställ in schemaläggaren på högsta nivån (t.ex. dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler

# Underliggande NVMe/SAS-enheter "none" för att undvika dubbel schemaläggning
for d in /sys/block/nvme*n1 /sys/block/sd*; do echo none | sudo tee $d/queue/scheduler; done

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

Med krypterade volymer (dm-crypt/LUKS) är jag uppmärksam på CPU-avlastning (AES-NI) och ser till att I/O-vägen inte i onödan vandrar över arbetsköer. Jag mäter specifikt sync-write-latenstider, eftersom dessa kan öka på grund av kryptolagret. I multipath-miljöer (SAN/iSCSI) ställer jag in schemaläggaren på multipath-enheten (dm-X) och kontrollerar att path failover inte genererar några outliers.

Virtualisering och containrar: värd kontra gäst

I KVM-stacken separerar jag medvetet värd och gäst. I Gäst Jag använder vanligtvis för virtio-enheter ingen, så att hypervisorn tar över optimeringen. På Värd Jag väljer sedan schemaläggaren för varje fysisk enhet som matchar maskinvaran (ofta none/mq-deadline på SSD/NVMe).

# Gäst (virtio-blk/virtio-scsi): Ställ in schemaläggaren till "none
echo none | sudo tee /sys/block/vda/queue/scheduler

#-värd: QEMU med iothreads och multiqueue för virtio-blk
qemu-system-x86_64 \
  -drive if=none,id=vd0,file=/var/lib/libvirt/images/guest.qcow2,cache=none,aio=native \
  -objekt iothread,id=ioth0 \
  -enhet virtio-blk-pci,enhet=vd0,antal-köer=8,iothread=ioth0

Jag binder containrar direkt till Cgroups v2 och använder systemd-egenskaper (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax) så att tjänsterna startar automatiskt med rätt I/O-budgetar. Viktigt: Prioritera endast på en nivå - antingen i behållaren eller i värdtjänsten - för att undvika motstridiga regler.

Optimering av NUMA, IRQ och polling

På system med flera socklar tar jag hänsyn till I/O och CPU Nära till NUMA. Jag kontrollerar fördelningen av NVMe-avbrott och justerar dem om det behövs om irqbalance fungerar suboptimalt. Jag använder också blk-mq-alternativ för att hålla slutföranden lokala.

# Kontrollera NVMe-avbrott och ställ in kärnmasker (exempel)
grep -i nvme /proc/avbrott
echo  | sudo tee /proc/irq//smp_affinity

# blk-mq: Slutförande av genererande kärna
echo 2 | sudo tee /sys/block//queue/rq_affinity

# Valfritt: Testa I/O-polling beroende på arbetsbelastning (använd försiktigt)
echo 0 | sudo tee /sys/block//queue/io_poll

För NVMe kan jag använda styrenhetens funktioner för att justera parametrarna för sammanfogning av avbrott för att jämna ut förhållandet mellan CPU-belastning och latens. Jag tar små steg här och kontrollerar om P99 förblir stabilt eller om koalesering leder till synlig tröghet.

Exempel på fio-jobbprofiler och mätplan

Jag skapar reproducerbara jobbfiler och antecknar kärnan, schemaläggaren, köparametrarna och filsystemmonteringarna. Detta gör att jag kan jämföra resultat över veckor.

# db-sync.fio - DB-liknande synkroniserade skrivningar (ext4/XFS)
[global]
ioengine=libaio
direkt=1
filnamn=/dev/
tidsbaserad=1
körtid=90
tråd=1
antal jobb=8
iodepth=1

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

# web-randread.fio - Webbliknande läsningar
[global]
ioengine=libaio
direkt=1
filnamn=/dev/
tidsbaserad=1
körtid=90
tråd=1
antal jobb=8
iodepth=32

[randread-4k]
rw=randread
bs=4k
# Mätningsram
# 1) Uppvärmning 60s, 2) Mätning 90s, 3) Nedkylning 30s
# Parallellt: kör iostat, pidstat och blktrace

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

# Spårning: Hämta P95/P99 från 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

Jag ändrar alltid bara en variabel, t.ex. scheduler eller read_ahead_kb, och jämför de identiska jobbfilerna igen. Först när förbättringarna är konsekventa över flera körningar bekräftar jag inställningarna.

Förändringshantering: Säkert införande och återgång

I produktiva hostingmiljöer rullar jag ut I/O-ändringar förskjuten Jag börjar med en kanariefågelvärd, sedan en liten AZ / klusterbatch och först sedan den breda utrullningen. Jag versionerar Udev-regler och bifogar varje ändring till en biljett med uppmätta värden. För återställningen har jag ett skript redo som spelar ut de tidigare värdena (schemaläggare, read_ahead_kb, Cgroup-gränser). På så sätt förblir interventionerna reversibla om arbetsbelastningen ändras med kort varsel.

Sammanfattning : Så här går jag tillväga

Jag börjar med en tydlig Verkligt värde, Jag mäter latenstider och genomströmning och dokumenterar installationen. Sedan väljer jag en lämplig schemaläggare för varje enhet: ingen för NVMe/virtuella SSD-enheter, mq-deadline för blandade serverbelastningar, BFQ för delade miljöer med många användare. Jag justerar sedan readahead, RQ-affinitet och processprioriteringar för att prioritera arbetsbelastningar i frontend. Om mätningar konsekvent visar att valet fungerar fixar jag det via udev/GRUB och skriver parametrarna. Övervakningen förblir aktiv eftersom arbetsbelastningen förändras, och med små korrigeringar behåller jag Prestanda permanent hög.

Aktuella artiklar