Con I/O Scheduler Tuning ottimizzo in modo specifico l'algoritmo di Kernel-per l'accesso alla memoria e ridurre la latenza negli ambienti di hosting. L'articolo mostra in modo pratico come adattare lo scheduling del disco di Linux all'hardware e al carico di lavoro, al fine di che ospita prestazioni in modo sicuro.
Punti centrali
I seguenti punti chiave forniranno una rapida panoramica del contenuto di questo articolo.
- Selezione dello schedulerNoop/nessuno, mq-deadline, BFQ, Kyber, a seconda dell'hardware e del carico di lavoro.
- strategia di misurazioneFio, iostat, P95/P99, IOPS e throughput prima/dopo le modifiche
- Regolazioni finiReadahead, RQ-Affinity, Cgroups, ionice per il QoS
- PersistenzaRegole udev e parametri GRUB per i profili persistenti
- PraticaRisoluzione dei problemi relativi ai picchi di latenza, all'equità e alle specifiche NVMe
Come funziona la programmazione dei dischi di Linux
Vedo lo scheduler di I/O come un centro di controllo che converte le richieste in Spunti ordina, unisce e stabilisce le priorità. Con gli HDD, evito costosi spostamenti della testina organizzando le richieste in base agli indirizzi dei blocchi e riducendo così i tempi di ricerca. Su SSD e NVMe, il parallelismo domina, ed è per questo che il sottosistema multi-queue blk-mq rende il percorso più ampio e assegna priorità a più richieste. CPU distribuito. In questo modo si riducono le latenze, si attenuano i picchi e si mantiene il throughput, anche se molti servizi scrivono e leggono contemporaneamente. Nell'hosting, i server web, i database e i lavori di backup si incontrano, quindi allineo sempre la pianificazione ai modelli di accesso dominanti.
I comuni schedulatori spiegati brevemente
Per le unità NVMe e le moderne unità SSD, spesso scelgo nessuno (equivalente a Noop nel contesto blk-mq), perché il controller è ottimizzato internamente e ogni overhead aggiuntivo costa denaro. mq-deadline stabilisce scadenze fisse per le letture e le scritture, dà priorità alle operazioni di lettura e fornisce tempi di risposta costanti in caso di carichi server misti. BFQ distribuisce la larghezza di banda in modo equo tra i processi ed è adatto alle configurazioni multi-tenant in cui le singole macchine virtuali occuperebbero altrimenti il disco. Kyber punta a basse latenze e rallenta le richieste in arrivo se vengono superati i tempi previsti. CFQ è considerata una soluzione legacy e difficilmente si adatta a NVMe; utilizzo CFQ solo quando le configurazioni legacy lo richiedono o i test mostrano chiari vantaggi; fornisco una panoramica dettagliata qui: Guida allo Scheduler I/O.
Messa a punto dello Scheduler I/O passo dopo passo
Inizio con una chiara Linea di base-in modo da poter mostrare i guadagni in modo oggettivo. Uso fio per i pattern sintetici, iostat per le statistiche dei dispositivi e raccolgo le latenze P95/P99 per le letture e le scritture. Controllo poi lo scheduler attivo per ogni dispositivo e lo modifico in fase di esecuzione per effettuare rapidamente una controprova. Effettuo modifiche persistenti solo quando le misurazioni stabili dimostrano che la scelta è giusta. In questo modo, evito di prendere decisioni sbagliate che poi costringono a costosi rollback.
# Controllare lo scheduler corrente
cat /sys/block//queue/scheduler
# Cambiare al volo (esempio: nvme0n1 a mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
# Confronto veloce con fio (Letture casuali 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1
Tengo d'occhio il carico della CPU, perché un carico non idoneo scheduler crea ulteriori commutazioni di contesto e quindi riduce le prestazioni nette. Non appena le latenze diminuiscono e il throughput aumenta, salvo la decisione e documento i profili di test. Ogni fase è seguita da una modifica e poi da una misurazione, in modo da poter separare chiaramente causa ed effetto. Questa disciplina dà i suoi frutti quando nel server sono installate diverse classi di dischi e i singoli dispositivi reagiscono in modo diverso.
Regolazioni fini: Readahead, RQ-Affinity, Cgroups
Dopo aver selezionato lo scheduler, regolo il parametro Coda-per il caricamento. Per i backup sequenziali aumento il readahead, per l'IO casuale lo abbasso in modo da non caricare pagine non necessarie. Con l'affinità RQ, mi assicuro che il completamento avvenga sul core che ha generato la richiesta, il che migliora la latenza e la localizzazione nella cache. Uso ionice per declassare processi come i backup e l'indicizzazione, in modo che le richieste web non ne risentano. Negli ambienti multi-tenant, regolo la larghezza di banda e gli IOPS tramite Cgroups v2 per stabilire limiti rigidi per cliente.
# Readahead per schemi sequenziali
echo 128 | sudo tee /sys/block//queue/read_ahead_kb
# Affinità RQ: 2 = completamento sul core generante
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Abbassare il processo di backup
ionice -c2 -n7 -p
# Cgroup v2: peso e limite (esempio 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
Qual è la scelta giusta per i profili di hosting?
Decido io il scheduler-Scelta in base alla classe hardware, al modello di accesso e alle dimensioni del target (latenza, throughput, equità). Le unità SSD NVMe nelle macchine virtuali single-tenant di solito non traggono alcun vantaggio, perché il controller esegue un'ampia ottimizzazione e ogni livello software è importante. Per carichi misti di lettura/scrittura su SSD, uso spesso mq-deadline, che dà priorità alle richieste di lettura e quindi protegge i tempi di risposta. Negli ambienti di hosting condiviso, scelgo BFQ per garantire l'equità tra i clienti e prevenire i monopoli della larghezza di banda. Uso Kyber quando le latenze di destinazione sono critiche e devo rispettare limiti rigidi per determinati carichi di lavoro.
| scheduler | Hardware adatto | Carichi di lavoro tipici | Vantaggi | Note |
|---|---|---|---|---|
| Noop/nessuno | NVMe, SSD moderno | Molte letture/scritture in parallelo, macchine virtuali | Overhead minimo, elevato parallelismo | Il controller si occupa dello smistamento; test in SAN/RAID |
| mq-scadenza | SSD, SAS, HDD veloce | Carichi misti web/DB | Priorità alle latenze di lettura, buon throughput | I valori di scadenza sono conservativi; è possibile una messa a punto fine. |
| BFQ | SSD/HDD in multi-tenant | Molti utenti, cgroup | Controllo chiaro dell'equità e della larghezza di banda | Qualche sforzo amministrativo, ponderazione pulita |
| Kyber | SSD, NVMe | Servizi critici per la latenza | Latenze target controllabili | Misurare con precisione per impostare correttamente la strozzatura |
| CFQ | Hardware legacy | Carichi di lavoro legacy | Ex soluzione standard | Raramente utile sui moderni NVMe/SSD |
Profili pratici e valori misurati
Per i server web con molti piccoli Letture La latenza di P95 conta più delle IOPS pure, per cui verifico le richieste di get con keep-alive e TLS in combinazione. I database mettono in gioco le scritture di sincronizzazione, per cui simulo il comportamento di flush e i costi di fsync con file di lavoro fio. Le finestre di backup hanno spesso flussi sequenziali; qui misuro il throughput in MB/s e mi assicuro che le richieste del frontend non attendano troppo a lungo. Nei miei test, vedo tempi di risposta inferiori di 20-50 %, a seconda della situazione iniziale, se lo scheduler e il readahead corrispondono ai carichi di lavoro. Se avete bisogno di ulteriori informazioni sulla misurazione del throughput del disco, potete trovare un'introduzione qui: Produttività del disco in hosting.
Configurazione e automazione persistenti
Ancoro il Scelta in modo permanente tramite la regola udev, in modo che i dispositivi si avviino direttamente nella modalità appropriata dopo il riavvio. Spesso imposto nessuna per NVMe, mq-deadline per gli SSD e BFQ per i supporti rotanti se la correttezza è fondamentale. Opzionalmente imposto un default globale tramite GRUB se sto eseguendo una configurazione omogenea. Mantengo le regole brevi e le documento nel repository di configurazione in modo che il team possa tenerne traccia. Per un'ottimizzazione più approfondita del kernel, questo articolo integra la configurazione: Prestazioni del kernel in hosting.
# /etc/udev/rules.d/60-ioschedulers.rules
# NVMe: nessuno
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
SSD #: mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]|vd[a-z]", ATTR{rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
HDD #: BFQ
AZIONE=="aggiungi|modifica", KERNEL=="sd[a-z]", ATTR{rotational}=="1", ATTR{queue/scheduler}="bfq"
# Ricaricare/verificare le regole
udevadm control --reload
udevadm trigger
# Predefinito globale opzionale tramite GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevatore=mq-deadline"
aggiornare-grub
QoS con Cgroups v2 e ionice
In modo che nessun lavoro lasci il piatto bloccato, Mi affido alle regole QoS con Cgroups v2 e aggiungo priorità tramite ionice. Per gli inquilini premium, aumento io.weight, mentre imposto limiti rigidi per i vicini rumorosi con io.max. Lego le unità di systemd direttamente a Cgroups, in modo che i servizi passino automaticamente alla classe giusta all'avvio. Limito temporaneamente i lavori di manutenzione a breve termine, in modo che le richieste dei clienti continuino a svolgersi senza problemi. Questa interazione tra ponderazione, limiti e priorità dei processi crea tempi di risposta prevedibili anche sotto carico.
# Creare il cgroup e impostare i limiti
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
Spostare il processo # nel gruppo C
echo | tee /sys/fs/cgroup/hosting/cgroup.procs
# Bassa priorità IO per i lavori secondari
ionice -c2 -n7 -p
Monitoraggio e risoluzione dei problemi
Conservo sempre la telemetria chiudere sui carichi di lavoro, altrimenti non prendo decisioni. Uso iostat per leggere i tempi di servizio e la profondità delle code, blktrace per analizzare i flussi di richieste e sar/dstat per vedere il carico del sistema nel tempo. Per quanto riguarda le latenze, non mi limito a guardare i valori medi, ma guardo sempre a P95/P99, perché gli hacker evidenti diventano visibili lì. Se P95 è buono, ma P99 non lo è, regolo la profondità della coda o l'affinità RQ e controllo i lavori in concorrenza. Dopo ogni correzione, confronto le stesse cifre chiave in modo che l'effetto rimanga affidabile.
Ostacoli e rimedi tipici
Alto Latenza su SSD spesso indica uno scheduler non adatto; allora provo immediatamente mq-deadline e verifico se le letture diventano più veloci. Risolvo la distribuzione non equa nelle configurazioni multi-tenant con BFQ e cancello i pesi di Cgroup in modo che i clienti più forti non escludano quelli più deboli. I timeout di NVMe indicano un firmware o un polling troppo aggressivo; in questi casi disattivo io_poll e abbasso la profondità finché non torna la stabilità. Le fluttuazioni del throughput nelle finestre di backup possono spesso essere attenuate con un readahead personalizzato, soprattutto quando dominano i file di grandi dimensioni. Se più fattori ruotano contemporaneamente, procedo per gradi: una modifica, poi una misura, poi la successiva.
Scheduler Tunables in dettaglio
Una volta effettuata la selezione di base, si procede alla regolazione delle viti dei rispettivi scheduler. Inizio sempre con l'esaminare i parametri disponibili per ogni dispositivo, poiché variano a seconda del kernel e della distro.
# Visualizzare i sintonizzatori disponibili
ls -1 /sys/block//queue/iosched
cat /sys/block//queue/iosched/*
# Esempio: mq-deadline più conservativo per lavori pesanti in scrittura
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
# Esempio: BFQ per un'equità più rigorosa e tempi di inattività più bassi
echo 1 | sudo tee /sys/block//queue/iosched/low_latency
echo 0 | sudo tee /sys/block//queue/iosched/slice_idle
In mq-deadline mi occupo principalmente di read_expire/write_expire (in millisecondi) e front_merges per unire le richieste in sospeso. Con BFQ, a seconda della densità degli inquilini, passo a bassa_latenza e slice_idle, per ridurre i tempi di attesa tra i flussi. Documento ogni modifica con i valori misurati, poiché le scadenze errate possono innescare picchi di latenza indesiderati in caso di carico a raffica.
File system e opzioni di montaggio
La messa a punto dello Scheduler si rende necessaria solo quando il file system è compatibile. Presto attenzione a:
- relatime/noatimeEvitare l'accesso non necessario alla scrittura dei metadati.
- scarto vs. fstrimSu SSD/NVMe di solito uso fstrim periodico invece di scartare online per evitare picchi di latenza.
- DiarioPer ext4, si sono dimostrati validi i seguenti dati=ordinati (predefinito) e un adeguato impegnarsi=-Intervallo (ad esempio 10-30s, a seconda della tolleranza alla perdita di dati).
- BarriereLe barriere di scrittura rimangono attive; non le disattivo a meno che l'hardware non garantisca una protezione contro l'interruzione dell'alimentazione (batteria/capacitore).
# Esempio di /etc/fstab per ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2
# Abilitare il TRIM periodico invece dell'opzione discard
systemctl enable fstrim.timer
systemctl start fstrim.timer
Per XFS ho anche impostato noatime e preferisco fstrim.timer. Le opzioni di journal o di barriera dipendono dalla distribuzione; io provo sempre la specifica combinazione kernel/FS e misuro P95/P99.
RAID, LVM, DM-crypt e Multipath
Nelle configurazioni impilate (Device Mapper, LVM, mdraid, Multipath) definisco lo scheduler nel punto in cui l'applicazione vede l'I/O, vale a dire nel punto in cui si trova l'applicazione. Dispositivo di primo livello - e impedire la doppia cernita sotto di sé.
# Impostare lo scheduler al livello superiore (ad esempio dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler
# Dispositivi NVMe/SAS sottostanti "nessuno" per evitare una doppia schedulazione
per d in /sys/block/nvme*n1 /sys/block/sd*; fare echo none | sudo tee $d/queue/scheduler; done
# mdraid: ottimizzare readahead e stripe cache (RAID5/6)
sudo blockdev --setra 4096 /dev/md0
echo 4096 | sudo tee /sys/block/md0/md/stripe_cache_size
Con i volumi criptati (dm-crypt/LUKS), faccio attenzione all'offload della CPU (AES-NI) e mi assicuro che il percorso di I/O non vaghi inutilmente tra le code di lavoro. Misuro in particolare le latenze di sincronizzazione-scrittura, che possono aumentare a causa del livello di crittografia. Negli ambienti multipath (SAN/iSCSI), imposto lo scheduler sul dispositivo multipath (dm-X) e verifico che il path failover non generi alcun outlier.
Virtualizzazione e container: host vs. guest
Nello stack KVM, faccio una distinzione consapevole tra host e guest. Nello stack KVM, faccio una distinzione consapevole tra host e guest. Ospite Di solito utilizzo per i dispositivi virtio nessuno, in modo che l'hypervisor si occupi dell'ottimizzazione. Sul Ospite Quindi seleziono lo scheduler per ogni dispositivo fisico che corrisponde all'hardware (spesso none/mq-deadline su SSD/NVMe).
# Guest (virtio-blk/virtio-scsi): Impostare lo scheduler su "none
echo none | sudo tee /sys/block/vda/queue/scheduler
Host #: QEMU con iothread e multiqueue per virtio-blk
qemu-system-x86_64 \
-drive if=none,id=vd0,file=/var/lib/libvirt/images/guest.qcow2,cache=none,aio=native \
-oggetto iothread,id=ioth0 \
-dispositivo virtio-blk-pci,drive=vd0,num-queues=8,iothread=ioth0
Lego i container direttamente a Cgroups v2 e uso le proprietà di systemd (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax) in modo che i servizi si avviino automaticamente con i budget di I/O corretti. Importante: la prioritizzazione deve avvenire a un solo livello, nel contenitore o nel servizio host, per evitare regole contrastanti.
Ottimizzazione di NUMA, IRQ e polling
Sui sistemi multi-socket, considero l'I/O e la CPU Vicino a NUMA. Controllo la distribuzione degli interrupt NVMe e li regolo se necessario se irqbalance funziona in modo non ottimale. Uso anche le opzioni di blk-mq per mantenere il completamento locale.
# Controllare gli interrupt NVMe e impostare le maschere del core (esempio)
grep -i nvme /proc/interrupts
echo | sudo tee /proc/irq//smp_affinity
# blk-mq: completamento del nucleo di generazione
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Opzionale: testare il polling I/O a seconda del carico di lavoro (usare con attenzione)
echo 0 | sudo tee /sys/block//queue/io_poll
Per NVMe, posso utilizzare le funzioni del controller per regolare i parametri di coalescenza degli interrupt, in modo da attenuare il rapporto tra carico e latenza della CPU. Faccio piccoli passi e verifico se il P99 rimane stabile o se la coalescenza porta a una visibile lentezza.
Esempi di profili professionali e piani di misurazione fio
Creo file di lavoro riproducibili e prendo nota del kernel, dello scheduler, dei parametri della coda e dei mount del file system. Questo mi permette di confrontare i risultati nell'arco di settimane.
# db-sync.fio - Scritture di sincronizzazione di tipo DB (ext4/XFS)
[globale]
ioengine=libaio
diretto=1
nome file=/dev/
basato sul tempo=1
tempo di esecuzione=90
thread=1
numjobs=8
iodepth=1
[randwrite-sync4k]
rw=randwrite
bs=4k
fsync=1
# web-randread.fio - Lettura di tipo web
[globale]
ioengine=libaio
diretto=1
nome del file=/dev/
basato sul tempo=1
tempo di esecuzione=90
thread=1
numjobs=8
iodepth=32
[randread-4k]
rw=randread
bs=4k
# Telaio di misurazione
# 1) Riscaldamento 60s, 2) Misurazione 90s, 3) Raffreddamento 30s
# Parallelo: eseguire iostat, pidstat e blktrace
iostat -x 1 | tee iostat.log &
pidstat -dl 1 | tee pidstat.log &
blktrace -d /dev/ -o - | blkparse -i - -d trace.dump &
Traccia #: estrarre P95/P99 da 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
Modifico sempre e solo una variabile, ad esempio scheduler o read_ahead_kb, e confronto nuovamente i file di lavoro identici. Solo quando i miglioramenti sono consistenti in diverse esecuzioni, eseguo il commit delle impostazioni.
Gestione dei cambiamenti: introduzione e rollback in sicurezza
Negli ambienti di hosting produttivi, eseguo le modifiche di I/O sfalsato Inizio con un host canarino, poi un piccolo lotto di AZ/cluster e solo in seguito il rollout su larga scala. Utilizzo le regole di Udev e allego ogni modifica a un ticket con i valori misurati. Per il rollback, ho pronto uno script che riproduce i valori precedenti (scheduler, read_ahead_kb, limiti Cgroup). In questo modo, gli interventi rimangono reversibili se i carichi di lavoro cambiano a breve termine.
Sommario: Ecco come procedo
Inizio con una chiara Valore effettivo, Misuro le latenze e il throughput e documento la configurazione. Quindi seleziono uno scheduler adatto per ogni dispositivo: nessuno per gli SSD NVMe/virtuali, mq-deadline per carichi server misti, BFQ per ambienti condivisi con molti utenti. Quindi modifico readahead, affinità RQ e priorità dei processi per dare priorità ai carichi di lavoro front-end. Se le misurazioni mostrano costantemente che la scelta funziona, la correggo tramite udev/GRUB e scrivo i parametri. Il monitoraggio rimane attivo perché i carichi di lavoro cambiano e, con piccole correzioni, mantengo i parametri di Prestazioni permanentemente alta.


