Зачем тюнить virtio в KVM/QEMU
KVM/QEMU давно перестали быть историей про «запустили VM и забыли». В продакшене производительность часто упирается в детали: очереди virtio, количество vCPU у гостя, модель диска (virtio-blk или virtio-scsi), раскладку прерываний, сетевые offload’ы и то, как хостовое ядро управляет энергосбережением и планировщиком.
Хорошая новость: большинство улучшений делаются без «магии» и почти всегда проверяются метриками. Плохая: один неудачный флажок легко добавляет миллисекунды к хвостам (p99) или режет пропускную способность вдвое.
Дальше разберём типовой стек: Linux-хост с KVM/QEMU, Linux-гость, устройства virtio-net и virtio-blk/virtio-scsi, системный тюнинг через tuned. Я буду говорить «хост» — это ОС с qemu-kvm, «гость» — виртуальная машина.
Где прячется latency: virtqueue, IRQ, offload, I/O и CPU
virtio — семейство паравиртуальных устройств: вместо эмуляции железа (e1000/ide/lsi) гость и хост обмениваются дескрипторами через virtqueue. Это снижает накладные расходы, но создаёт несколько типовых точек, где легко потерять производительность или предсказуемость задержек.
- Очереди (multiqueue): мало очередей — упираетесь в одно ядро; слишком много — растёт overhead и шанс IRQ-«снега».
- Прерывания и affinity: плохая раскладка IRQ/vCPU даёт миграции кэшей и очереди в softirq.
- Сетевые offload’ы (GSO/TSO/GRO/CSUM): экономят CPU на throughput, но могут ухудшать tail latency на мелких RPC.
- Дисковый стек: модель устройства,
iothread, режимы кэша, I/O-планировщик на хосте/госте. - Энергосбережение: governor, C-states, jitter от частот и пробуждений.
Практика почти всегда одна: сначала фиксируем baseline, затем включаем правильные устройства/очереди, после — приводим в порядок CPU/IRQ и только потом идём в pinning/NUMA.

Минимальные проверки: что у вас сейчас и где узкое место
Перед любым тюнингом зафиксируйте исходные показатели: disk latency/IOPS, сетевой throughput, а также загрузку CPU и перекосы по softirq. Без этого вы не поймёте, стало ли лучше.
Понять, какие устройства у гостя
Внутри гостя:
lsblk -o NAME,TYPE,SIZE,MODEL
lspci -nn | grep -i virtio
ethtool -i eth0
Для сети вы хотите видеть драйвер virtio_net. Для дисков — virtio-модель (обычно vda для virtio-blk или SCSI-слой при virtio-scsi).
Быстрые метрики по хосту
uptime
vmstat 1 10
mpstat -P ALL 1 5
pidstat -t -p $(pidof qemu-system-x86_64) 1 5
Если видите swap (рост si/so), высокий iowait или один CPU забит softirq — тюнинг будет разным: где-то нужно больше очередей, где-то — меньше агрегации или жёстче раскладывать IRQ.
Замер дисковой latency/IOPS (fio)
Внутри гостя (на проде аккуратно: лучше на тестовом файле/томе):
fio --name=randread --filename=/var/tmp/fio.test --size=2G --direct=1 --ioengine=libaio --iodepth=32 --rw=randread --bs=4k --numjobs=4 --time_based --runtime=60 --group_reporting
Смотрите не только среднюю задержку, но и хвосты (p95/p99). Для баз данных и очередей это зачастую важнее максимальных IOPS.
Замер сети (идея)
Для throughput по сети обычно используют iperf3 между двумя VM/хостами в одном сегменте. Для задержек — отдельно измеряйте ping и «прикладную» latency (например, ответ вашего API), потому что хорошая полоса не гарантирует хороший p99.
virtio-net: multiqueue, offload и IRQ — где чаще всего теряется скорость
virtio-net почти всегда правильный выбор. Но «по умолчанию» вы можете не получить многопоточность: один RX/TX queue упрётся в одно ядро и станет лимитом по PPS/throughput.
Проверяем и включаем multiqueue (гость)
Смотрим, сколько каналов доступно:
ethtool -l eth0
Если сейчас один канал, пробуем увеличить (если драйвер/устройство поддерживает):
ethtool -L eth0 combined 4
Практическое правило: начните с combined = min(vCPU, 4..8). Для маленьких VM (2–4 vCPU) обычно 2–4 очереди дают максимум эффекта без лишнего overhead.
RPS/RFS и softirq: когда multiqueue нет или его мало
Если по архитектуре очередей мало, иногда помогает распределить обработку пакетов через RPS/RFS. Это не замена multiqueue, но может выровнять нагрузку.
Типовой признак проблемы: один CPU забит ksoftirqd, остальные простаивают. Для диагностики:
cat /proc/interrupts | head
cat /proc/net/softnet_stat | head
Если видите явный перекос, дальше логично либо включать multiqueue на устройстве, либо настраивать affinity IRQ и/или RPS (в зависимости от вашей схемы сети и версии ядра).
Offload’ы virtio-net: throughput vs tail latency
Offload’ы обычно включены и экономят CPU на больших потоках. Но для низких задержек (мелкие RPC, очереди, чаты, real-time обработка) иногда полезно отключить часть агрегации и проверить хвосты.
Текущие флаги:
ethtool -k eth0
Кандидаты для A/B-теста:
ethtool -K eth0 gro off gso off tso off
Это не универсальная рекомендация. Отключая GRO/GSO/TSO, вы часто снижаете throughput и повышаете CPU. Делайте сравнение на одинаковой нагрузке и обязательно фиксируйте p95/p99.
virtio-blk vs virtio-scsi: что выбрать и как снизить disk latency
Если нужен «простой и быстрый» диск — virtio-blk часто отлично подходит. Если нужна гибкость (hotplug, сложные конфигурации, больше возможностей на уровне SCSI) — выбирают virtio-scsi. На практике разница часто меньше, чем влияние CPU/IRQ/NUMA и режима работы хранилища на хосте.
Проверьте планировщик I/O в госте
В госте:
lsmod | grep -E 'virtio|scsi'
cat /sys/block/vda/queue/scheduler 2>/dev/null
cat /sys/block/sda/queue/scheduler 2>/dev/null
На современных ядрах для виртуальных дисков обычно разумны none или mq-deadline (зависит от версии ядра и типа backend-хранилища). Если видите «странные» или очень старые значения, иногда дешевле обновить ядро/дистрибутив, чем бесконечно подкручивать параметры вокруг устаревшего стека.
Не путайте «быстро» и «много»: iodepth/numjobs и хвосты
Синтетика с большим iodepth легко рисует красивые IOPS, но p99 latency при этом может стать неприемлемым. Это не «обман», а реальная характеристика очередей и конкуренции за ресурсы.
Для сравнения делайте минимум два профиля тестов:
- latency-oriented:
iodepth=1..4, небольшие блоки, умеренное число потоков; - throughput-oriented:
iodepth=16..64, больше параллелизма.
Кэширование QEMU и writeback: осторожно
Режимы кэша диска задаются на хосте (libvirt XML или параметры QEMU). Неправильный режим может ускорить «цифры» в бенчмарке, но ухудшить надёжность и поведение при авариях.
Если вы не уверены, какой режим кэша безопасен именно для вашей схемы хранения, держитесь консервативных настроек и сначала обеспечьте корректную модель записи (fsync/барьеры/гарантии backend), а уже потом оптимизируйте производительность.
Во многих реальных случаях больший эффект дают не кэш-флаги, а: правильное распределение CPU/IRQ, настройка очередей и адекватный системный профиль на хосте.

tuned: как выбрать профиль под производительность и latency
tuned — менеджер профилей производительности в Linux. Он меняет governor CPU, параметры энергосбережения, sysctl и ряд опций, влияющих на задержки и throughput. Практическая ценность tuned в том, что вы можете быстро переключиться между профилями и сравнить результат измерениями.
Установка и базовые команды
tuned-adm --version
tuned-adm list
tuned-adm active
Типовая логика выбора профиля:
- Если важнее максимальная пропускная способность и «ровная» загрузка — часто подходит
throughput-performance. - Если VM latency-sensitive (БД, очереди, API) — попробуйте
latency-performance. - На выделенных хостах под виртуализацию в некоторых дистрибутивах встречается профиль уровня
virtual-host(название зависит от поставщика).
Применение:
tuned-adm profile latency-performance
tuned-adm active
После переключения обязательно повторите ваши тесты. «На глаз» в теме latency почти всегда ошибочно.
Что в tuned обычно влияет сильнее всего
- CPU governor: перевод в performance и отказ от агрессивного power saving.
- Параметры энергосбережения: влияние C-states на wakeup latency.
- Сетевые sysctl: backlog/буферы и связанные параметры, влияющие на поведение под нагрузкой.
Если хвосты стали лучше, но выросло потребление энергии и средняя загрузка CPU — это типичный компромисс «latency vs efficiency».
Если упираетесь в лимиты CPU/памяти на сервисах внутри VM, проверьте ещё и ограничения systemd: иногда «медленный QEMU» на деле оказывается лимитом cgroups для процесса сервиса. См. материал про лимиты CPU/памяти в systemd.
CPU pinning, NUMA и раскладка IRQ: когда нужны «взрослые» меры
Если вы уже включили virtio, настроили очереди и подобрали tuned, а latency всё ещё «плавает», часто виноваты миграции vCPU по ядрам, конкуренция с другими задачами на хосте и NUMA-эффекты.
Проверяем NUMA на хосте
lscpu | grep -E 'NUMA|Socket|CPU\(s\)'
numactl --hardware
Если хост NUMA, старайтесь держать vCPU и память VM в пределах одного NUMA-узла (особенно для баз данных). Иначе получите непредсказуемые хвосты из-за удалённой памяти.
Идея pinning: меньше миграций — стабильнее p99
Pinning (закрепление vCPU за физическими CPU) и изоляция ядер под VM уменьшают jitter. Это особенно заметно на высоких PPS/IOPS и при «шумных соседях» на хосте.
Реализация зависит от стека управления (libvirt или чистый QEMU), но цель одна: vCPU, IRQ virtio и (если используете) iothread’ы должны быть распределены осмысленно, а не оставлены на усмотрение планировщика.
Прерывания virtio: почему важен affinity
Даже если у VM 8 vCPU, но все IRQ virtio-net обрабатываются на одном ядре, вы снова получите узкое место. Смотрите /proc/interrupts и настраивайте affinity вручную или через связку irqbalance/tuned (в зависимости от политики на хосте).
Практический сценарий тюнинга: от простого к сложному
Фиксируем baseline: fio (диск), сетевые тесты (throughput и latency), CPU/softirq на хосте.
Проверяем устройства: сеть —
virtio-net, диски —virtio-blkилиvirtio-scsi(не e1000/ide).Включаем multiqueue для
virtio-netи проверяем, что нагрузка распределилась по ядрам.Тестируем offload’ы: сначала дефолт, затем точечно отключаем GRO/GSO/TSO, если цель — минимальные хвосты.
Переключаем tuned на хосте: сравниваем
throughput-performanceиlatency-performanceпод вашей нагрузкой.Если хвосты всё ещё плохие: pinning/NUMA/IRQ-affinity, устранение миграций и конкуренции.
Типовые симптомы и быстрые подсказки
Throughput низкий, CPU свободен
Проверьте multiqueue у virtio-net, не упираетесь ли в single queue, и как распределены IRQ. Часто решение — добавить очереди и правильно разложить прерывания.
Throughput высокий, но p99 latency плохой
Ищите агрегацию пакетов (GRO/GSO), энергосбережение CPU, миграции vCPU, NUMA-эффекты. Уменьшайте «очереди ради очередей»: слишком большой iodepth и слишком много очередей тоже добавляют хвосты.
Всплески задержек «ни с того ни с сего»
Проверьте фоновые задачи на хосте: backup/trim, kswapd и давление памяти, периодические джобы. Иногда проблема не в virtio, а в общей конкуренции за ресурсы — и тогда полезно вернуться к основам выбора конфигурации VDS/хоста (CPU/RAM/диск) и «не экономить на базовом». См. как подобрать план VDS по CPU и RAM.
Контрольный список «хорошей практики»
virtio везде: сеть —
virtio-net, диски —virtio-blkилиvirtio-scsiпо задаче.Очереди соразмерны CPU: multiqueue не должен быть «максимальным просто потому что можно».
tuned включён осмысленно: выбирайте профиль под задачу и подтверждайте тестами.
Метрики важнее ощущений: p95/p99 latency, softirq, iowait, распределение IRQ.
Стабильность важнее пиков: для БД и API лучше чуть меньше IOPS, но предсказуемая задержка.
Заключение
Тюнинг KVM/QEMU — это управление очередями, прерываниями и политиками CPU/энергосбережения. Начните с очевидного: virtio-net и virtio-blk/virtio-scsi, затем включите multiqueue и подберите профиль tuned. Если tail latency всё ещё страдает — переходите к pinning/NUMA/IRQ-affinity и повторяйте тесты до стабильного результата.
Если вы выбираете площадку под виртуализацию, где важны предсказуемые хвосты и контроль ресурсов, чаще всего удобнее стартовать с изолированного VDS, а не с «общей кухни». Так проще закреплять CPU, понимать I/O и стабилизировать latency под вашей нагрузкой.


