Com o I/O Scheduler Tuning, optimizo especificamente o Kernel-para acesso à memória e reduzir a latência em ambientes de alojamento. O artigo mostra de forma prática como adapto o agendamento de discos do Linux ao hardware e à carga de trabalho, a fim de hospedagem desempenho em segurança.
Pontos centrais
Os seguintes pontos-chave dar-lhe-ão uma visão rápida do conteúdo deste artigo.
- Seleção do programadorNoop/nenhum, mq-deadline, BFQ, Kyber, dependendo do hardware e da carga de trabalho
- estratégia de mediçãoFio, iostat, P95/P99, IOPS e taxa de transferência antes/depois das alterações
- Ajustes finos: Readahead, RQ-Affinity, Cgroups, ionice para QoS
- Persistência: regras do udev e parâmetros do GRUB para perfis persistentes
- PráticaResolução de problemas de picos de latência, equidade e especificidades do NVMe
Como funciona o agendamento de discos do Linux
Vejo o programador de E/S como um centro de controlo que converte os pedidos em Tacos ordena, funde e estabelece prioridades. Com HDDs, evito movimentos de cabeça dispendiosos, organizando os pedidos de acordo com os endereços de bloco e reduzindo assim os tempos de pesquisa. Em SSDs e NVMe, o paralelismo domina, e é por isso que o subsistema de filas múltiplas blk-mq torna o caminho mais amplo e dá prioridade a vários pedidos. CPUs distribuídos. Isto reduz as latências, suaviza os picos e mantém o rendimento no caminho certo, mesmo que muitos serviços estejam a escrever e a ler ao mesmo tempo. No alojamento, os servidores Web, as bases de dados e as tarefas de cópia de segurança estão juntos, pelo que alinho sempre a programação com os padrões de acesso dominantes.
Explicação sucinta dos programadores comuns
Para NVMe e SSDs modernos, escolho frequentemente nenhum (equivalente ao Noop no contexto do blk-mq), porque o controlador é optimizado internamente e qualquer sobrecarga adicional custa dinheiro. O mq-deadline define prazos fixos para leituras e escritas, dá prioridade às operações de leitura e fornece tempos de resposta constantes em cargas de servidor mistas. O BFQ distribui a largura de banda de forma justa pelos processos e é adequado para configurações multi-tenant em que VMs individuais ocupariam o disco. O Kyber tem como objetivo latências baixas e abranda os pedidos de entrada se os tempos alvo forem excedidos. O CFQ é considerado uma solução legada e dificilmente se encaixa no NVMe; eu só uso o CFQ quando as configurações legadas o exigem ou quando os testes mostram vantagens claras; eu forneço uma visão geral detalhada aqui: Guia do programador de E/S.
Sintonização do Programador de E/S passo a passo
Começo com uma clara Linha de base-para que eu possa mostrar os ganhos de forma objetiva. Utilizo o fio para padrões sintéticos, o iostat para estatísticas do dispositivo e recolho latências P95/P99 para leituras e escritas. Em seguida, verifico o agendador ativo por dispositivo e altero-o em tempo de execução para um rápido contra-teste. Só faço ajustes persistentes quando as medições estáveis mostram que a escolha está correta. Desta forma, evito decisões erradas que mais tarde obrigam a reversões dispendiosas.
# Verificar o agendador atual
cat /sys/block//queue/scheduler
# Alterar em tempo real (exemplo: nvme0n1 para mq-deadline)
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
# Comparação rápida com o fio (leituras aleatórias 4k)
fio --name=rr --rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --filename=/dev/nvme0n1
Fico de olho na carga da CPU porque uma carga inadequada agendador cria comutações de contexto adicionais, reduzindo assim o desempenho líquido. Assim que as latências caem e a taxa de transferência aumenta, eu salvo a decisão e documento os perfis de teste. Cada passo é seguido de uma alteração e, em seguida, de uma medição, para que eu possa separar claramente a causa e o efeito. Esta disciplina compensa quando várias classes de disco são instaladas no servidor e os dispositivos individuais reagem de forma diferente.
Ajustes finos: Readaptação, afinidade RQ, grupos C
Depois de selecionar o agendador, ajusto o Fila de espera-para o carregamento. Para backups sequenciais eu aumento o readahead, para IO aleatório eu o diminuo para que eu não carregue nenhuma página desnecessária. Com a afinidade RQ, eu garanto que as conclusões cheguem ao núcleo que gerou a solicitação, o que melhora a latência e a localidade do cache. Utilizo o ionice para fazer o downgrade de processos, como backups e indexação, para que os pedidos Web não sejam afectados. Em ambientes multi-tenant, regulo a largura de banda e o IOPS através do Cgroups v2 para definir limites rígidos por cliente.
# Readahead para padrões sequenciais
echo 128 | sudo tee /sys/block//queue/read_ahead_kb
# Afinidade RQ: 2 = conclusão no núcleo gerador
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Processo de backup inferior
ionice -c2 -n7 -p
# Cgroup v2: peso e limite (exemplo 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 é a melhor opção para alojar perfis?
Eu decido o agendador-Escolha de acordo com a classe de hardware, o padrão de acesso e o tamanho do alvo (latência vs. taxa de transferência vs. equidade). Os SSDs NVMe em VMs de inquilino único geralmente não se beneficiam de nenhum, porque o controlador realiza uma otimização extensiva e cada camada de software conta. Para cargas mistas de leitura/gravação em SSDs, costumo usar o mq-deadline, pois ele prioriza as solicitações de leitura e, assim, protege os tempos de resposta. Em ambientes de alojamento partilhado, escolho o BFQ para garantir a equidade entre clientes e evitar monopólios de largura de banda. Utilizo o Kyber quando as latências alvo são críticas e tenho de respeitar limites rígidos para determinadas cargas de trabalho.
| agendador | Hardware adequado | Cargas de trabalho típicas | Vantagens | Notas |
|---|---|---|---|---|
| Noop/nenhum | NVMe, SSD moderno | Muitas leituras/escritas paralelas, VMs | Mínima sobrecarga, elevado paralelismo | O controlador assume a triagem; teste em SAN/RAID |
| prazo mq | SSD, SAS, disco rígido rápido | Cargas mistas web/DB | Prioridade às latências de leitura, boa taxa de transferência | Valores de prazo conservadores; é possível um ajuste fino |
| BFQ | SSD/HDD em multi-tenant | Muitos utilizadores, cgroups | Controlo claro da equidade e da largura de banda | Algum esforço administrativo, ponderação limpa |
| Kyber | SSD, NVMe | Serviços críticos em termos de latência | Latências alvo controláveis | Medir com precisão para definir corretamente o estrangulamento |
| CFQ | Hardware antigo | Cargas de trabalho antigas | Solução padrão anterior | Raramente útil em NVMe/SSD modernos |
Perfis práticos e valores medidos
Para servidores Web com muitos pequenos Leituras a latência do P95 conta mais do que o IOPS puro, por isso testo os pedidos de get com keep-alive e TLS em combinação. As bases de dados trazem a sincronização das escritas para o jogo, e é por isso que simulo o comportamento de flush e os custos de fsync com ficheiros de trabalho fio. As janelas de backup têm frequentemente fluxos sequenciais; aqui meço o débito em MB/s e certifico-me de que os pedidos de frontend não esperam demasiado tempo. Nos meus testes, vejo tempos de resposta 20-50 % mais curtos, dependendo da situação inicial, se o agendador e o readahead corresponderem às cargas de trabalho. Se precisar de mais contexto sobre a medição da taxa de transferência do disco, pode encontrar uma introdução aqui: Taxa de transferência de disco em alojamento.
Configuração e automatização persistentes
Eu ancoro o Escolha permanentemente via regra udev para que os dispositivos iniciem diretamente no modo apropriado após a reinicialização. Eu geralmente defino none para NVMe, mq-deadline para SSDs e BFQ para mídia rotativa se a justiça for primordial. Eu opcionalmente defino um padrão global via GRUB se eu estiver executando uma configuração homogênea. Mantenho as regras curtas e documento-as no repositório de configuração para que a equipa as possa seguir. Para uma otimização mais aprofundada do kernel, este artigo complementa a configuração: Desempenho do kernel no alojamento.
# /etc/udev/rules.d/60-ioschedulers.rules
# NVMe: nenhum
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
SSDs #: mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]|vd[a-z]", ATTR{rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
HDDs #: BFQ
ACTION=="adicionar|alterar", KERNEL=="sd[a-z]", ATTR{rotational}=="1", ATTR{queue/scheduler}="bfq"
# Recarregar/testar regras
udevadm control --reload
udevadm trigger
# Predefinição global opcional através do GRUB
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="elevator=mq-deadline"
update-grub
QoS com Cgroups v2 e ionice
Para que nenhum trabalho saia do prato bloqueado, Baseio-me em regras de QoS com o Cgroups v2 e adiciono prioridades através do ionice. Para inquilinos premium, aumento o io.weight, enquanto estabeleço limites rígidos para vizinhos barulhentos com o io.max. Eu vinculo unidades do systemd diretamente ao Cgroups para que os serviços entrem automaticamente na classe certa na inicialização. Eu temporariamente estrangulo o trabalho de manutenção de curto prazo para que as consultas dos clientes continuem a funcionar sem problemas. Esta interação de ponderação, limites e prioridade de processos cria tempos de resposta previsíveis mesmo sob carga.
# Criar cgroup e definir limites
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
Mover o processo # para o Cgroup
echo | tee /sys/fs/cgroup/hosting/cgroup.procs
# Baixa prioridade de IO para trabalhos secundários
ionice -c2 -n7 -p
Monitorização e resolução de problemas
Mantenho sempre a telemetria fechar sobre as cargas de trabalho, caso contrário, perco decisões. Utilizo o iostat para ler os tempos de serviço e as profundidades das filas, o blktrace para analisar os fluxos de pedidos e o sar/dstat para ver a carga do sistema ao longo do tempo. Para as latências, não olho apenas para os valores médios, mas sempre para P95/P99, porque os soluços visíveis tornam-se visíveis aí. Se P95 é bom, mas P99 não é, eu ajusto a profundidade da fila ou a afinidade RQ e verifico os trabalhos concorrentes. Após cada correção, comparo os mesmos índices para que o efeito se mantenha fiável.
Obstáculos típicos e soluções
Elevado Latência em SSDs indica frequentemente um agendador inadequado; testo imediatamente o mq-deadline e verifico se as leituras se tornam mais rápidas. Eu resolvo a distribuição injusta em configurações multi-tenant com BFQ e limpo os pesos do Cgroup para que os clientes mais fortes não excluam os mais fracos. Os tempos limite do NVMe indicam firmware ou polling demasiado agressivo; nestes casos, desativo o io_poll e reduzo a profundidade até que a estabilidade regresse. A flutuação da taxa de transferência nas janelas de backup pode ser suavizada com o readahead personalizado, especialmente quando arquivos grandes dominam. Se mais factores estiverem a rodar ao mesmo tempo, procedo passo a passo: uma alteração, depois uma medida, depois a seguinte.
Scheduler-Tunables em detalhe
Uma vez feita a seleção básica, rodo os parafusos de ajuste dos respectivos schedulers. Começo sempre por ver os parâmetros disponíveis para cada dispositivo, uma vez que estes variam consoante o kernel e a distro.
# Mostrar os sintonizáveis disponíveis
ls -1 /sys/block//queue/iosched
cat /sys/block//queue/iosched/*
# Exemplo: mq-deadline mais conservador para trabalhos com muita escrita
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
# Exemplo: BFQ para maior equidade e menores tempos de inatividade
echo 1 | sudo tee /sys/block//queue/iosched/low_latency
echo 0 | sudo tee /sys/block//queue/iosched/slice_idle
No mq-deadline, regulo principalmente read_expire/write_expire (em milissegundos) e front_merges para fundir os pedidos pendentes. Com o BFQ, dependendo da densidade dos inquilinos, mudo baixa latência e slice_idle, para reduzir os tempos de espera entre os fluxos. Eu documento todas as alterações com valores medidos, uma vez que expirações incorrectas podem desencadear picos de latência indesejados sob carga excessiva.
Sistema de ficheiros e opções de montagem
A afinação do agendador só é realmente útil quando o sistema de ficheiros corresponde. Eu presto atenção a:
- tempo de relação/tempo de inatividadeEvitar o acesso desnecessário à escrita de metadados.
- descartar vs. fstrimEm SSDs/NVMe, costumo usar fstrim periódico em vez de descarte online para evitar picos de latência.
- Registo no diárioPara ext4, os seguintes provaram ser eficazes dados=ordenados (predefinição) e um comprometer=-(por exemplo, 10-30s, dependendo da tolerância à perda de dados).
- BarreirasAs barreiras de escrita permanecem activas; não as desactivei a menos que o hardware garanta proteção contra falhas de energia (bateria/capacitor).
# Exemplo de /etc/fstab para ext4
UUID= /data ext4 defaults,noatime,commit=20 0 2
# Ativar a opção TRIM periódica em vez de descartar
systemctl enable fstrim.timer
systemctl start fstrim.timer
Para o XFS eu também defini não há tempo e prefiro o fstrim.timer. As opções de diário ou barreira dependem da distribuição; eu sempre testo a combinação específica de kernel/FS e meço P95/P99.
RAID, LVM, DM-crypt e Multipath
Em configurações empilhadas (Device Mapper, LVM, mdraid, Multipath), defino o agendador onde a aplicação vê as E/S - ou seja, no Dispositivo de nível superior - e evitar a dupla triagem por baixo.
# Definir o agendador no nível superior (por exemplo, dm-0)
echo mq-deadline | sudo tee /sys/block/dm-0/queue/scheduler
# Dispositivos NVMe/SAS subjacentes "none" para evitar agendamento duplo
for d in /sys/block/nvme*n1 /sys/block/sd*; do echo none | sudo tee $d/queue/scheduler; done
# mdraid: Otimizar o readahead e a cache de stripe (RAID5/6)
sudo blockdev --setra 4096 /dev/md0
echo 4096 | sudo tee /sys/block/md0/md/stripe_cache_size
Com volumes criptografados (dm-crypt/LUKS), presto atenção ao descarregamento da CPU (AES-NI) e asseguro que o caminho de E/S não passe desnecessariamente pelas filas de trabalho. Meço especificamente as latências de sincronização e gravação, pois elas podem aumentar devido à camada de criptografia. Em ambientes multipath (SAN/iSCSI), defino o agendador no dispositivo multipath (dm-X) e verifico se o failover do caminho não gera nenhum outlier.
Virtualização e contentores: anfitrião vs. convidado
Na pilha KVM, eu separo deliberadamente o host e o convidado. Na pilha Convidado Normalmente utilizo para dispositivos virtio nenhum, para que o hipervisor assuma a otimização. No Anfitrião Em seguida, selecciono o agendador para cada dispositivo físico que corresponde ao hardware (frequentemente none/mq-deadline em SSD/NVMe).
# Convidado (virtio-blk/virtio-scsi): Definir o agendador como "none
echo none | sudo tee /sys/block/vda/queue/scheduler
Host #: QEMU com iothreads e multiqueue para virtio-blk
qemu-system-x86_64 \
-drive if=none,id=vd0,file=/var/lib/libvirt/images/guest.qcow2,cache=none,aio=native \
-objeto iothread,id=ioth0 \
-dispositivo virtio-blk-pci,drive=vd0,num-queues=8,iothread=ioth0
Eu vinculo os contêineres diretamente ao Cgroups v2 e uso as propriedades do systemd (IOWeight, IOReadBandwidthMax/IOWriteBandwidthMax) para que os serviços iniciem automaticamente com os orçamentos de E/S corretos. Importante: priorize apenas em um nível - seja no contêiner ou no serviço do host - para evitar regras conflitantes.
NUMA, IRQ e otimização de polling
Em sistemas com vários soquetes, considero a E/S e a CPU Perto de NUMA. Verifico a distribuição das interrupções do NVMe e ajusto-as, se necessário, se o irqbalance estiver a funcionar de forma subótima. Eu também uso as opções blk-mq para manter as conclusões locais.
# Verificar as interrupções NVMe e definir máscaras de núcleo (exemplo)
grep -i nvme /proc/interrupts
echo | sudo tee /proc/irq//smp_affinity
# blk-mq: Conclusões no núcleo gerador
echo 2 | sudo tee /sys/block//queue/rq_affinity
# Opcional: Teste o polling de E/S dependendo da carga de trabalho (use com cuidado)
echo 0 | sudo tee /sys/block//queue/io_poll
Para o NVMe, posso usar os recursos do controlador para ajustar os parâmetros de coalescência de interrupção, a fim de suavizar a proporção de carga e latência da CPU. Dou pequenos passos aqui e verifico se o P99 permanece estável ou se a coalescência leva a uma lentidão visível.
Exemplo de perfis de funções de fio e plano de medição
Crio ficheiros de tarefas reproduzíveis e tomo nota do kernel, do agendador, dos parâmetros da fila e das montagens do sistema de ficheiros. Isto permite-me comparar os resultados ao longo das semanas.
# db-sync.fio - Escritas sincronizadas do tipo DB (ext4/XFS)
[global]
ioengine=libaio
direct=1
nome do ficheiro=/dev/
baseado em tempo=1
tempo de execução=90
thread=1
numjobs=8
iodepth=1
[randwrite-sync4k]
rw=randwrite
bs=4k
fsync=1
# web-randread.fio - Leituras tipo Web
[global]
ioengine=libaio
direct=1
nome do ficheiro=/dev/
tempo_baseado=1
tempo de execução=90
thread=1
numjobs=8
iodepth=32
[randread-4k]
rw=randread
bs=4k
# Quadro de medição
# 1) Aquecimento 60s, 2) Medição 90s, 3) Arrefecimento 30s
# Paralelo: executar 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 &
# Trace: Puxar P95/P99 do 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
Apenas altero uma variável, por exemplo, scheduler ou read_ahead_kb, e comparo novamente os ficheiros de trabalho idênticos. Só quando as melhorias são consistentes ao longo de várias execuções é que confirmo as definições.
Gestão da mudança: introduzir e retroceder com segurança
Em ambientes de alojamento produtivos, implemento alterações de E/S escalonado Começo com um anfitrião canário, depois um pequeno lote AZ/cluster, seguido de uma implementação alargada. Eu versiono as regras do Udev e anexo cada alteração a um ticket com valores medidos. Para o rollback, tenho um script pronto que reproduz os valores anteriores (scheduler, read_ahead_kb, limites do Cgroup). Desta forma, as intervenções permanecem reversíveis se as cargas de trabalho mudarem a curto prazo.
Resumo: É assim que procedo
Começo com uma clara Valor real, Meço as latências e a taxa de transferência e documento a configuração. Em seguida, selecciono um agendador adequado para cada dispositivo: nenhum para NVMe/SSDs virtuais, mq-deadline para cargas de servidor mistas, BFQ para ambientes partilhados com muitos utilizadores. Em seguida, ajusto o readahead, a afinidade RQ e as prioridades de processo para priorizar as cargas de trabalho front-end. Se as medições mostram consistentemente que a escolha funciona, eu a corrijo via udev/GRUB e escrevo os parâmetros. O monitoramento permanece ativo porque as cargas de trabalho mudam, e com pequenas correções eu mantenho o Desempenho permanentemente elevado.


