NVMe passthrough vs VirtIO: Переможець продуктивності, про якого ніхто не каже

Було корисно?

Деякі проблеми з продуктивністю очевидні. CPU завантажений на 100%. Мережевий канал уповільнився. Сховище підступніше: воно ламається тихо, у проміжках між мікросекундами. Вашій VM «має NVMe диск», прилади показують, що IOPS у нормі, але хвостова затримка руйнує вашу базу даних, збірку або конвеєр логування.

Звичний диспут подається як бій: NVMe passthrough (VFIO) проти VirtIO. Незручна правда: переможець частіше — ні той, ні інший. Переможець — це видалення одного зайвого шару, про який ви не знали, і налаштування шару, який ви не можете видалити. Ось чому команди програють у продакшні, хоча виграють у бенчмарках.

Що фактично змінюється між passthrough і VirtIO

На високому рівні:

  • NVMe passthrough (VFIO PCIe passthrough) дає гостьовій VM прямий доступ до фізичного NVMe контролера. Гість завантажує рідний nvme драйвер, володіє чергами, відправляє адміністративні команди і «бачить» пристрій близький до bare-metal.
  • VirtIO дає гістьовій ОС паравіртуалізований пристрій (virtio-blk або virtio-scsi). Гість відправляє запити у virtqueue; хост (QEMU + vhost + kernel block layer) обробляє їх через бекап-пристрій або файл.

Але історія продуктивності в основному про довжину шляху і точки планувань:

  • Скільки разів I/O-запит переходить між користувацьким і ядром?
  • Скільки черг існує, і чи співвіднесені вони з ядрами CPU?
  • Де запит може затриматися через конкуренцію: блокування, переривання, cgroup-тротлінг, host page cache, журнали файлової системи або гіпервізорний потік, який був дескедульований?

Фактичні шляхи I/O (спрощено)

VirtIO (поширений virtio-blk з QEMU):

  1. Гостьовий додаток робить I/O (syscall).
  2. Блок-слой ядра гостя планує його.
  3. Драйвер VirtIO розміщує дескриптори у virtqueue.
  4. Вихід на хост (virtio kick / interrupt), QEMU/vhost обробляє запит.
  5. Хостовий блок-слой відправляє на фізичний пристрій (або файлову систему, якщо використовуються образи).
  6. Завершення «піднімається» назад; гість отримує переривання; додаток відновлює роботу.

NVMe passthrough:

  1. Гостьовий додаток робить I/O (syscall).
  2. Драйвер NVMe гостя відправляє безпосередньо в апаратну чергу (через PCIe MMIO/DMA).
  3. Апарат завершує; переривання до гостя (MSI-X), завершення обробляється в гості.

Passthrough видаляє host-блокову стеку з «гарячого» шляху. Це може суттєво зменшити затримку. Також воно прибирає деякі контрольні точки хоста, які можуть бути потрібні — кешування на хості, снапшоти, гнучка міграція і просте мультиплексування зберігання.

Правило, яким я користуюсь в продакшні: якщо навантаження чутлива до затримки і передбачувана, passthrough привабливий. Якщо навантаження змішане, багатокористувацьке і операційно заплутане, VirtIO зазвичай правильний компроміс — якщо, звісно, ви не запускаєте його з дефолтними налаштуваннями й потім дивуєтесь, чому це не магія.

Цікаві факти та історичний контекст (те, що пояснює сьогодення)

Віртуалізація зберігання не ускладнилася просто так. Вона ускладнилася тому, що реальні системи складні. Кілька конкретних контекстних пунктів, що важливі при виборі між passthrough і VirtIO:

  1. VirtIO з’явився через біль: рання емуляція пристроїв (наприклад IDE) була повільною, бо кожен I/O виглядав як програмна вечірка переривань.
  2. NVMe відразу стандартизував мультичерги, створені для паралелізму. Це підходить сучасним CPU; також означає, що відображення черг і маршрутизація переривань тепер набагато важливіші, ніж у старого SATA.
  3. MSI-X зробив можливим високий IOPS, підтримуючи кілька переривань на пристрій. Саме тому «один диск» може масштабуватися по ядрах, і чому погана афініті переривань може зіпсувати вам день.
  4. Linux blk-mq змінив правила гри: multi-queue блок-слой зменшив конкуренцію за блокування й покращив масштабування, але також додав нові налаштування і нові можливості для помилок конфігурації.
  5. vhost створили, щоб витіснити QEMU з шляху: перенос datapath у ядро зменшив контекстні переключення і підвищив пропускну здатність для virtio-мережі та сховища.
  6. Планувальники I/O перейшли від «обери один» до «не обирай нічого» для NVMe: для швидких пристроїв планувальники можуть додавати затримку без суттєвої вигоди, тож «none» часто перемагає.
  7. Раніше люди бенчмаркували 4k random read і оголошували перемогу. Сучасні сервіси зазвичай змішані: читання, запис, fsync, операції з метаданими і сплески — тож стара культура бенчмарків все ще вводить в оману.
  8. Хмарні гіпервізори нормалізували VirtIO: операційні можливості (міграція, снапшоти, контролі доступу) мали значення не менше за сирі швидкості, тому VirtIO виграв завдяки практичності.
  9. Passthrough став практичним у масштабі, коли IOMMU перестав бути екзотикою: VFIO і ізоляція IOMMU зробили це менш страшним, але «менш страшно» ≠ «без компромісів».

Переможець продуктивності, про якого ніхто не каже: менше шарів, менше брехні

Коли команди сперечаються «passthrough vs VirtIO», вони часто упускають справжнє питання: які зайві шари ви випадково додаєте, і чи вимірюєте ви правильну річ?

Приклад: VM використовує VirtIO, під ним лежить QCOW2-образ, на якому ext4 на LVM поверх RAID-контролера з write-back кешем, про який ніхто не питав. Потім хтось порівнює це з NVMe passthrough і називає це наукою. Це не наука; це багатошаровий торт із латентною прикрасою.

«Нікто не згадує» переможець зазвичай один із цих варіантів:

  • VirtIO з правильним режимом: virtio-scsi з кількома чергами, iothreads, правильною опцією кешу і прямими LUN замість образів може наблизитися до passthrough для багатьох навантажень.
  • Правильна топологія CPU і переривань: прив’язка vCPU, вирівнювання черг по ядрах і відведення переривань від «шумних» сусідів може драматично зменшити хвостову затримку без зміни пристрою.
  • Видалення неправильного кешуючого шару: host page cache, гостевий page cache і device write cache можуть взаємодіяти так, що все виглядає швидким, поки не трапиться крах або сплеск flush.

Суха правда: ви можете купити NVMe, який робить мільйони IOPS, і все одно отримати 50 ms спади, бо потік завершення I/O гостя голодний. Зберігання — це не лише проблема пристрою; це проблема планування в масці диска.

Жарт №1: Бенчмарки зберігання — як резюме: технічно правдиві, стратегічно неповні і зазвичай не містять частини, де все ламається під тиском.

Коли NVMe passthrough перемагає (і чому)

Passthrough перемагає, коли вузьким місцем є програмний оверхед у шарі віртуалізації, а навантаження чутливе до нього. Типові ознаки:

  • Вас більше хвилює хвостова затримка (p99, p999), ніж середня затримка.
  • Ви часто робите fsync або дрібні синхронні записи (бази даних, черги повідомлень, журнальні файлові системи під навантаженням).
  • Ви маєте високі IOPS з малими блоками, і VirtIO додає помітну CPU вартість на I/O.
  • Ви можете виділити пристрій під VM без скарг на використання.

Чому це швидше

NVMe вже протокол на базі черг з низькою затримкою. Passthrough дозволяє гостю відправляти безпосередньо в черги контролера. Немає потоку QEMU для планування. Немає метаданих хостової файлової системи. Менше контекстних переключень. Менше блокувань. Гість бачить реальний NVMe-пристрій, тож ядро гостя може застосувати NVMe-специфічні оптимізації.

Де це болить

Passthrough — не «встановив і забув». Воно змінює операційну фізику:

  • Жива міграція ускладнюється або стає неможливою в звичному сенсі. Пристрій, прив’язаний до VM, не хоче телепортуватися.
  • Скиди пристрою і помилки стають видимими гостю. Якщо NVMe контролер виведе помилку, ваша VM отримає це в обличчя.
  • Спільне використання пристрою складне, якщо у вас немає SR-IOV або NVMe namespace для цього (і навіть тоді керування ускладнюється).
  • Безпека/ізоляція залежать від IOMMU. Якщо IOMMU налаштовано неправильно, це не passthrough — це гра у довіру до випадку.

Коли VirtIO перемагає (і чому)

VirtIO перемагає, коли система більша за одну VM і один диск. Це більшість продакшн-середовищ.

Операційні можливості, яких вам бракуватиме

  • Жива міграція значно простіша з віртуальними дисками.
  • Снапшоти, резервні копії, реплікація простіші, коли зберігання — керований артефакт (LVM LV, Ceph RBD, ZVOL тощо).
  • Оверкоміт і пулінг стають можливими. Не завжди мудро, але часто економічно необхідно.
  • Застосування політик: ви можете накладати тротлінг, пріоритизацію I/O і межі арендатора на рівні хоста або бекенду.

Продуктивність не обов’язково погана

VirtIO часто дає відмінну продуктивність, якщо уникати самоіндукованих проблем:

  • Використовуйте raw блок-пристрої (LVM LV, NVMe namespace, що експонується як /dev/… на хості) замість QCOW2 на файловій системі для навантажень з високою продуктивністю.
  • Використовуйте virtio-scsi з мультичергами, якщо потрібне масштабування та конкурентність. virtio-blk може бути достатнім, але virtio-scsi дає більше гнучкості з чергами і моделлю пристрою.
  • Додайте iothreads, щоб завершення I/O не застрягало за головним потоком QEMU, який робить інші речі.
  • Обирайте режим кешу навмисно з урахуванням вимог до надійності, а не інтуїції.

Жарт №2: Стандарти VirtIO за замовчуванням — як стандартні паролі: вони існують, щоб почати, а не щоб бути безпечними в продакшні.

Чому мікробенчмарки брешуть, а продакшн карає

Бенчмарки потрібні. Вони також часто шкідливі.

Мікробенчмарки зазвичай:

  • Працюють на ідл-хості з розігрітими кешами.
  • Використовують один джоб, одну глибину черги і ідеальну локальність.
  • Вимірюють середню затримку, а не хвостову.
  • Ігнорують витрачений CPU на I/O, де VirtIO може «платити» за свою гнучкість.

Продакшн-навант…

(Продовження видимого тексту)

Продакшн-навантаки зазвичай:

  • Мають змішані читання/записи та операції з метаданими.
  • Мають сплески, що спричиняють черги.
  • Конкурентно використовують CPU із іншими гостями та демон-процесами хоста.
  • Зі сторони виникають періодичні flush-і, commit-і журналів, trim/discard і фонове обслуговування.

Переможець продуктивності, якого ви шукаєте, часто — це покращення хвостового перцентиля латентності, а не загальна цифра IOPS. Passthrough може покращити p99, зменшуючи програмний джиттер. VirtIO може утримувати p99 стабільним, роблячи поведінку системи керованою — якщо його налаштували і бекенд не робить нічого підступного.

Цитата, що тут доречна, бо відмови зберігання рідко бувають «тільки одним» фактором:

«Everything fails, all the time.» — Werner Vogels

Практичні завдання: команди, виводи та рішення (12+)

Ось завдання, які я виконую, коли кажуть «зберігання повільне». Кожне містить: команду, що означає вивід, і рішення, яке треба прийняти.

Завдання 1: Підтвердити, що гість вважає за диск

cr0x@server:~$ lsblk -o NAME,MODEL,TRAN,ROTA,SIZE,TYPE,MOUNTPOINT
NAME        MODEL            TRAN   ROTA   SIZE TYPE MOUNTPOINT
vda         Virtio Block Dev        0     200G disk
├─vda1                              0     512M part /boot
└─vda2                              0   199.5G part /

Значення: vda з моделлю «Virtio Block Dev» — це VirtIO. Якщо ви бачите nvme0n1 і реальну модель вендора, то скоріше за все це passthrough.

Рішення: Якщо очікування продуктивності передбачають «NVMe», узгодьте із зацікавленими сторонами: VirtIO — це не NVMe, а «ніби SSD» — не специфікація.

Завдання 2: Перевірити, чи passthrough — це справді NVMe у гості

cr0x@server:~$ sudo nvme list
Node             SN                   Model                                    Namespace Usage                      Format           FW Rev
/dev/nvme0n1     S5XXXXXXXXXXXX       SAMSUNG MZVLB1T0HALR-00000                1         200.04  GB / 200.04  GB    512   B +  0 B   EXF7

Значення: Гість бачить реальний NVMe-пристрій і модель; passthrough правдоподібний.

Рішення: Якщо вам потрібні адмін-фічі (SMART лог, firmware, namespaces), passthrough дає видимість; VirtIO часто це приховує.

Завдання 3: На хості перевірити прив’язку VFIO для passthrough

cr0x@server:~$ lspci -nnk | grep -A3 -i nvme
5e:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd NVMe SSD Controller [144d:a808]
        Subsystem: Samsung Electronics Co Ltd Device [144d:a801]
        Kernel driver in use: vfio-pci
        Kernel modules: nvme

Значення: «Kernel driver in use: vfio-pci» вказує, що хост передав пристрій в VFIO, а не хостовому nvme драйверу.

Рішення: Якщо хост все ще використовує nvme, ви не робите passthrough; виправте прив’язку перед тим, як сперечатися про продуктивність.

Завдання 4: Визначити модель VirtIO та кількість черг у гості

cr0x@server:~$ grep -H . /sys/block/vda/queue/nr_requests /sys/block/vda/queue/scheduler /sys/block/vda/queue/nr_hw_queues 2>/dev/null
/sys/block/vda/queue/nr_requests:128
/sys/block/vda/queue/scheduler:[none] mq-deadline kyber bfq
/sys/block/vda/queue/nr_hw_queues:1

Значення: Одна апаратна черга означає обмежену паралельність; опції планувальника показують доступні режими, зараз обрано none.

Рішення: Якщо ви вимагаєте конкурентності і бачите nr_hw_queues:1, розгляньте virtio-scsi з мультичергами або налаштування multi-queue для virtio-blk на хості.

Завдання 5: Перевірити планувальник блоків і глибину черги на хості

cr0x@server:~$ cat /sys/block/nvme0n1/queue/scheduler
[none] mq-deadline kyber bfq

Значення: Для NVMe none часто коректний. Якщо ви бачите BFQ на високопродуктивному NVMe-бекенді, ви можете платити зайву затримку за справедливість, яку не просили.

Рішення: Для виділеного NVMe, що обслуговує VM, віддавайте перевагу none або mq-deadline залежно від навантаження; тестуйте p99 латентність.

Завдання 6: Виміряти розподіл затримки за допомогою fio (гость)

cr0x@server:~$ fio --name=randread --filename=/dev/vda --direct=1 --ioengine=libaio --rw=randread --bs=4k --iodepth=32 --numjobs=4 --time_based --runtime=30 --group_reporting
randread: (g=0): rw=randread, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=32
...
  read: IOPS=180k, BW=703MiB/s (737MB/s)(20.6GiB/30001msec)
    slat (nsec): min=900, max=150000, avg=4200, stdev=1900
    clat (usec): min=70, max=22000, avg=680, stdev=1200
     lat (usec): min=75, max=22010, avg=684, stdev=1200
    clat percentiles (usec):
     |  1.00th=[  140],  5.00th=[  180], 10.00th=[  210], 50.00th=[  410]
     | 90.00th=[ 1500], 95.00th=[ 2600], 99.00th=[ 6000], 99.90th=[16000]

Значення: Середнє виглядає прийнятним (avg=680us), але p99/p99.9 жахливі. Саме такий «все нормально», що вбиває бази даних.

Рішення: Якщо хвостова затримка висока, дослідіть планування CPU, iothreads, IRQ affinity, конкуренцію на хості, режими кешу та поведінку бекенда при writeback. Не ганяйтеся лише за IOPS.

Завдання 7: Визначити, чи платите ви за host page cache (хост)

cr0x@server:~$ ps -eo pid,comm,%cpu,%mem,args | grep -E 'qemu-system|qemu-kvm' | head -n 1
2143 qemu-system-x86 175.2 8.1 /usr/bin/qemu-system-x86_64 ... -drive file=/var/lib/libvirt/images/vm01.qcow2,if=virtio,cache=writeback ...

Значення: cache=writeback означає, що host page cache може брати участь. Це може бути швидко, поки тиск пам’яті не спричинить writeback-сплески.

Рішення: Для критичних до затримки навантажень віддавайте перевагу cache=none з O_DIRECT, щоб зменшити взаємодію з host cache (і впевніться у вашій моделі збереження надійності).

Завдання 8: Перевірити тиск пам’яті та ризик writeback на хості (хост)

cr0x@server:~$ grep -E 'Dirty:|Writeback:|MemAvailable:' /proc/meminfo
MemAvailable:   2148320 kB
Dirty:           842112 kB
Writeback:        90112 kB

Значення: Великий Dirty/Writeback відносно доступної пам’яті вказує, що ядро інтенсивно скидає дані. Це може корелювати зі спадами затримки.

Рішення: Якщо Dirty постійно високий, налаштуйте поведінку writeback для VM, зменшіть використання host cache або виділіть більше RAM/гарантійного запасу. Перевірте, чи бекенд справді встигає поглинати записи.

Завдання 9: Виявити CPU steal і проблеми планування (гость)

cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (vm01) 	02/04/2026 	_x86_64_	(8 CPU)

02:10:01 AM  CPU   %usr %nice %sys %iowait %irq %soft %steal %idle
02:10:02 AM  all   12.5  0.0   6.4   4.1    0.0  0.8    9.7   66.5
02:10:02 AM    0   15.0  0.0   8.0   2.0    0.0  0.5   18.0   56.5

Значення: %steal вказує, що гіпервізор відбирає CPU-час. Високий steal означає, що шлях завершення I/O може бути відкладений, навіть якщо диск у порядку.

Рішення: Якщо steal значний під час сплесків затримки, вирішіть проблему CPU-конкуренції (pinning, резервації, зменшення overcommit). Зміна драйвера зберігання не вилікує голодування планувальника.

Завдання 10: Інспектувати переривання і підтвердити розподіл (хост або гість)

cr0x@server:~$ cat /proc/interrupts | egrep 'nvme|virtio|vfio' | head
  45:  1203941  0  0  0  IR-PCI-MSI 327680-edge      nvme0q0
  46:  893220   0  0  0  IR-PCI-MSI 327681-edge      nvme0q1
  47:  901144   0  0  0  IR-PCI-MSI 327682-edge      nvme0q2
  48:  876990   0  0  0  IR-PCI-MSI 327683-edge      nvme0q3

Значення: Існують кілька NVMe-черг (q0..q3). Якщо всі переривання інкрементуються лише на CPU0, у вас проблема з афініті.

Рішення: Якщо переривання сконцентровані, налаштуйте IRQ affinity або увімкніть irqbalance з політикою, яка не шкодить латентним CPU.

Завдання 11: Виявити IO wait vs реальне насичення пристрою (гость)

cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (vm01) 	02/04/2026 	_x86_64_	(8 CPU)

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s w_await wareq-sz aqu-sz  %util
vda            4500.0  18000.0     0.0    0.0   1.20     4.00   1200.0   9600.0   2.40     8.00  4.10  92.0

Значення: %util близько до 100% вказує, що черга пристрою завантажена. r_await/w_await показують час, витрачений у черзі пристрою, а не лише в CPU.

Рішення: Якщо %util високий і awaits ростуть, ви близькі до ліміту пристрою або бекенда; розгляньте більше черг, швидший бекенд або шардинг по пристроях. Якщо %util низький, але латентність висока, дивіться на планування/кешування/локи.

Завдання 12: Підтвердити поведінку discard/TRIM (гость)

cr0x@server:~$ lsblk -D
NAME   DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
vda           0      512B       2G         0

Значення: Гранулярність discard і max вказують, чи підтримується TRIM/discard і в яких розмірах.

Рішення: Якщо discard увімкнено ненавмисно на бекенді, який обробляє його погано (тонко провізовані або мережеві системи), це може спричинити сплески латентності. Плануйте fstrim у непіковий час або вимкніть опцію discard при монтуванні.

Завдання 13: Перевірити модель потоків QEMU і наявність iothreads (хост)

cr0x@server:~$ ps -T -p $(pgrep -n qemu-system) -o spid,comm,pcpu | head
 2143 qemu-system-x86  98.4
 2160 IO iothread0     35.2
 2161 CPU 0            12.1
 2162 CPU 1            11.7

Значення: Наявність IO iothread0 означає, що у вас є виділений I/O-потік; якщо його немає, I/O може ділити головний event loop QEMU.

Рішення: Для високих IOPS або чутливих до затримки VirtIO додайте iothreads і ізолюйте їх на CPU, щоб вони не конкурували з іншим шумом.

Завдання 14: Підтвердити, що бекенд — raw блок, а не QCOW2-на-файловій-системі (хост)

cr0x@server:~$ virsh domblklist vm01
Target   Source
------------------------------------------------
vda      /var/lib/libvirt/images/vm01.qcow2

Значення: QCOW2 додає метадані та ризик фрагментації; може бути прийнятним для багатьох випадків, але не безкоштовно при високих записах.

Рішення: Якщо ви ганяєтеся за стабільною низькою затримкою, перемістіть «гарячі» диски на raw LV або виділений блок-пристрій; лишіть QCOW2 для завантажувальних дисків і зручності там, де це доречно.

Швидка діагностика: план дій

Це «у вас 20 хвилин, поки інцидент не стане гарячим» послідовність. Мета — швидко локалізувати шар вузького місця, а не створювати ідеальний бенчмарк.

Спочатку: вирішіть, чи це насичення пристрою або джиттер планування

  • Перевірте guest iostat: якщо %util високий і await зростає з навантаженням, підозрюйте насичення бекенда.
  • Перевірте guest CPU steal: якщо %steal стрибає під час сплесків затримки, підозрюйте конкуренцію CPU на хості.
  • Перевірте fio перцентилі: якщо середнє нормальне, але p99/p99.9 жахливі, підозрюйте черги і конкуренцію, а не сирий швидкісний потенціал пристрою.

По-друге: ідентифікуйте шлях I/O і приберіть найпідозріліший шар

  • Якщо ви на VirtIO з QCOW2: це ваш перший підозрюваний. Тимчасово переключіть гарячий диск на raw block/LV для тесту.
  • Якщо ви на VirtIO без iothreads: додайте iothreads, потім перетестуйте p99.
  • Якщо ви на passthrough і все ще повільно: не звинувачуйте VirtIO; дивіться на IRQ affinity, налаштування ядра гостя і NVMe power/latency фічі.

По-третє: підтвердіть стан бекенда хоста

  • Подивіться host dmesg на предмет помилок NVMe, скидів, таймаутів.
  • Перевірте тиск пам’яті хоста (Dirty/Writeback), що може створювати flush-шторм.
  • Перевірте CPU-використання QEMU-потоків; якщо I/O-потік завантажений або позбавлений CPU, ви знайшли кривдника.

Якщо зробити тільки одну річ: виміряйте p99 латентність і %steal під час відтворення проблеми. Ця пара скаже вам, чи ви воюєте зі зберіганням, чи з планувальником.

Типові помилки: симптом → корінь → фікс

1) Симптом: «VirtIO повільний, тож потрібен passthrough»

Корінь: VirtIO-диск під QCOW2 на файловій системі з host caching та writeback-сплесками; також відсутні iothreads.

Фікс: Помістіть гарячі диски на raw block (LV, RBD або прямий пристрій), використовуйте cache=none там, де доречно, увімкніть iothreads і перевірте конфігурацію черг. Потім порівняйте знову.

2) Симптом: Чудова середня латентність, жахлива p99.9 під піком

Корінь: CPU-конкуренція: QEMU I/O-потік або vCPU дескедульований; IRQ-и накопичуються на одному ядрі; host reclaim/writeback-шторм.

Фікс: Закріпіть iothreads і vCPU, виправте IRQ affinity, зменшіть overcommit і забезпечте запас пам’яті на хості. Перевірте з mpstat і /proc/interrupts.

3) Симптом: Passthrough VM швидкий, поки раптом не перестає бути; потім падає з урвища

Корінь: Скиди NVMe контролера, баг firmware або PCIe-помилка, що безпосередньо видима гостю; відновлення жорстке і болісне.

Фікс: Перевірте firmware, дивіться PCIe AER логи, моніторьте лічильники помилок NVMe і проєктуйте відмовостійкість (реплікація, кластеризація). Passthrough видаляє шари; воно також зменшує амортизацію відмов.

4) Симптом: Сплески латентності кожні кілька хвилин як годинник

Корінь: Періодичний flush/commit журналу, fstrim/discard роботи або пороги writeback на хості, що викликають сплески.

Фікс: Плануйте trims, налаштуйте параметри writeback, уникайте подвійного кешування і гарантуйте, що налаштування надійності відповідають очікуванням бази даних (не «оптимізуйте» flush-и, якщо не хочете втрати даних).

5) Симптом: «Ми додали більше глибини черги і стало гірше»

Корінь: Занадто багато черг робить довші черги й збільшує затримку; ви фактично будуєте більшу кімнату очікування. Також можливі конкуренція за блокування або посилення насичення бекенда через конкуруючі запити.

Фікс: Налаштуйте iodepth відповідно до пристрою і навантаження; вимірюйте хвостову латентність. Для баз даних менший iodepth часто покращує p99 навіть якщо пікові IOPS падають.

6) Симптом: Жива міграція працює, але продуктивність непослідовна між хостами

Корінь: Різні моделі CPU, NUMA-топологія, політики балансування IRQ або різні моделі/firmware NVMe у кластері.

Фікс: Стандартизувати профілі хостів, закріплювати переривання/потоки консистентно і сприймати «той самий тип інстансу» як оперативну угоду, а не маркетинг.

Три корпоративні міні-історії з полів зберігання

Міні-історія 1: Інцидент через хибне припущення

Середня компанія тримала флот VM з агенти збірки та кеші артефактів. Збірки були «випадково повільні», що змушує всіх спочатку звинувачувати мережу, а не зберігання. Хтось помітив, що хости мають блискучі NVMe-диски і вирішив, що VMs «на NVMe». Це формулювання перетворилось на догму.

Під час релізного тижня час збірки подвоївся. На черзі не було явного насичення диска. IOPS були нормальні. Середня латентність — ок. Але довгий хвіст був жорстокий. Інструмент збірки чекав на дрібні операції з метаданими файлів і синхронні fsync-и — навантаження, що карає джиттер.

Хибне припущення було простим: VM використовували VirtIO диски, під якими QCOW2-образи на ext4. Host page cache робив систему швидкою при легкому навантаженні, а потім тиск пам’яті спричинив writeback у найгірший час. NVMe був реальний, але похований під купою оверхеду і конкуренції.

Фікс не був героїчним. Вони перенесли гарячі диски на raw LV, навмисно виставили режими кешу і додали iothreads. Хвостова латентність покращилася настільки, що уповільнення збірки зникло. Важливіша була культурна зміна: вони перестали казати «на NVMe» як гарантію і почали вказувати реальний шлях I/O.

Міні-історія 2: Оптимізація, що відмовила

Команда фінансових сервісів мала VM з базою даних, яка постійно боролася з p99. Вони обрали NVMe passthrough через чудові бенчмарки. І кілька тижнів усе було добре: нижча латентність, менше CPU-оверходу, менше загадок.

Потім на хості стався PCIe-глюк. Нічого драматичного: транзієнтна помилка, перетренування лінії — те, що трапляється в реальних дата-центрах, коли ви напихаєте щільний зал обладнання і вдаєте, що фізика — опціональна. NVMe контролер скинувся. На bare-metal ОС відновилася після паузи. У passthrough VM пауза виглядала як зникнення зберігання посеред операції.

База даних відреагувала як належить: паніка при I/O помилках, маркування пристрою підозрілим і тригер фейловера. Фейловер спрацював, але було шумно: хвилі перепідключень, прогрів кешу і каскад квитків «чому все повільно». Оптимізація зрушила межу від «хост поглине» до «гість відчує».

Вони залишили passthrough, але додали нудну стійкість: реплікацію налаштовану на швидкий фейловер, алерти по лічильниках скидів NVMe і рукопис із припущенням, що пристрій може зникнути. Урок не в тому, що passthrough поганий. Урок в тому, що passthrough чесний: він показує гострі краї, які раніше приховувалися.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

SaaS-компанія запускала змішані навантаження на кластері віртуалізації. Вони стандартизували VirtIO для більшості VM, бо потребували міграцію й операційну гнучкість. Нічого екзотичного. Але вони зробили нудну річ, тому рідко роблять: зберігали профіль продуктивності зберігання для кожного хоста.

Кожен хост проходив базовий fio при введенні в експлуатацію, фіксуючи не тільки IOPS, але й латентні перцентилі при кількох iodepth. Вони повторювали це після оновлень firmware, ядер і змін обладнання. Результати зберігалися разом з метаданими хоста, так що «цей хост дивний» можна було довести за хвилини.

Одного дня після планового обслуговування підмножина хостів почала показувати періодичні p99-сплески на звичайних VM. Команда порівняла базові профілі і відразу побачила зміну: поведінка writeback на хості і використання CPU QEMU-потоками відрізнялося. Не «ламалося», але достатньо, щоб викликати хвостові проблеми у деяких орендарів.

Вони відкотили конкретну комбінацію налаштувань ядра і поправили політику pinning iothreads. Інцидент не перетворився на багатоденну війну, бо команда мала нудну базу і вважала продуктивність вимірюваною властивістю, а не анекдотом. Рятівний рух — не хитре налаштування, а зробити «нормальне» вимірюваним.

Чеклісти / покроковий план

Чекліст рішення: чи повинна ця VM отримати NVMe passthrough?

  1. Чи навантаження критичне до затримки на p99/p99.9? Якщо ні — не морочтеся.
  2. Чи можете ви виділити пристрій (або namespace) цій VM? Якщо ні — passthrough перетвориться на боротьбу за ресурси.
  3. Чи обійдетеся без живої міграції? Якщо ні — VirtIO перемагає за замовчуванням.
  4. Чи маєте ви операційну зрілість IOMMU і VFIO? Якщо ні — ви вчитиметесь під час інциденту. Це найгірший час навчитися.
  5. Чи маєте дизайн обробки відмов? Passthrough робить скиди і помилки пристрою видимими гостю. Сплануйте це.

VirtIO «зробіть правильно» чекліст (практичні дефолти)

  1. Бекапіть гарячі диски raw block де можливо (LV, ZVOL, RBD) замість QCOW2-на-файловій-системі.
  2. Використовуйте virtio-scsi коли потрібна мультичерга і гнучкість; перевірте кількість черг у гості.
  3. Додайте iothreads і прив’яжіть їх до стабільних CPU. Не дозволяйте I/O-шляху змагатися з емулюючими роботами.
  4. Обирайте режим кешу свідомо:
    • cache=none для прямого I/O і зменшення джиттера host cache.
    • cache=writeback лише коли ви розумієте компроміси надійності і тиск пам’яті.
  5. Перевіряйте хвостову латентність за допомогою fio перцентилів, а не тільки середнього значення.

Passthrough «робіть безпечно» чекліст

  1. Підтвердьте, що пристрій прив’язаний до vfio-pci на хості.
  2. Перевірте, що IOMMU увімкнено і у вас адекватні IOMMU-групи (без несподіваного шарингу пристроїв).
  3. Закріпіть vCPU і налаштуйте IRQ affinity, щоб NVMe-переривання не концентрувалися на одному ядрі.
  4. Моніторьте логи помилок NVMe і скиди; трактуйте їх як прогностичні сигнали.
  5. Проєктуйте застосунок/кластер під помилки на рівні пристрою (реплікація, тестування фейловеру).

FAQ

1) Чи завжди NVMe passthrough швидший за VirtIO?

Ні. Воно часто швидше для дрібних заточених на затримку операцій, бо прибирає хостовий оверхед. Але VirtIO може зрівнятися або навіть перевершити пропускну здатність у деяких конфігураціях, і він зазвичай виграє операційно.

2) Яка найпростіша зміна в VirtIO, що дає реальний приріст?

Припиніть класти високозаписні, високі-IOPS диски на QCOW2 образи поверх загальної файлової системи. Перенесіть гарячі диски на raw block і додайте iothreads.

3) VirtIO-blk чи virtio-scsi?

virtio-scsi часто кращий вибір, коли потрібне мультичергове масштабування і зрілий шлях для складної поведінки збереження. virtio-blk може бути простішим і швидким, але перевіряйте ліміти черг і опції гіпервізора.

4) Чи робить iodepth «якнайбільша» NVMe швидшим?

Вона робить черги глибшими. Це не те саме. Висока iodepth може підвищити пропускну здатність, але знищити хвостову латентність. Налаштовуйте iodepth за p99, а не за самолюбством.

5) Чому p99 латентність погана навіть при низькому %util?

Тому що вузьке місце ймовірно в плануванні або конкуренції: CPU steal, IRQ affinity, зайнятий головний потік QEMU, host memory reclaim або конкуренція за блокування в I/O-шляху. Низьке завантаження пристрою не означає низьку кінцеву латентність.

6) Чи можу я жити з живою міграцією VM з NVMe passthrough?

Не у звичному сенсі «перемістити працюючу VM на інший хост». Є спеціалізовані підходи, але якщо жива міграція — жорстка вимога, VirtIO — практичний вибір.

7) Чи безпечний passthrough у мульти-орендних середовищах?

Може бути, якщо ізоляція IOMMU правильна і ви керуєте призначенням пристроїв. Але воно зменшує можливості хоста щодо застосування політик і збільшує радіус ураження при збоях пристрою.

8) Що щодо NVMe-oF або мережевих блок-пристроїв?

Мережеве зберігання може бути відмінним, але вводить мережевий джиттер і інші режими відмов. Воно також змінює, хто відповідає за проблему чергування. Виміряйте p99 латентність по шляху від кінця до кінця і вирішуйте на підставі чутливості вашого навантаження.

9) Якщо мене цікавить тільки пропускна здатність (MB/s), що обрати?

VirtIO з правильно налаштованим бекендом часто дає достатню пропускну здатність, іноді більше, ніж диск. Якщо ви робите великі послідовні I/O, різниця між passthrough і VirtIO може бути меншою, ніж очікуєте.

10) Хто ж «справжній переможець» знову?

Справжній переможець — це видалення випадкової складності: непотрібних образів, поганих режимів кешу, відсутніх iothreads і невирівняної топології CPU/IRQ. Passthrough — один зі способів прибрати шари. Налаштування VirtIO — інший.

Практичні наступні кроки

  1. Оберіть представницький профіль fio для вашого навантаження (змішайте читання/записи, додайте fsync де це релевантно) і зафіксуйте p50/p95/p99/p99.9 латентності.
  2. Змоделюйте реальний шлях I/O: тип пристрою в гості, режим кешу хоста, тип бекенду (raw vs QCOW2) і планувальник пристрою на бекенді.
  3. Вирішіть легкі перемоги спочатку: raw block для гарячих дисків, iothreads, правильний режим кешу і розміщення CPU/IRQ. Перевірте знову.
  4. Лише потім розглядайте NVMe passthrough для кількох VM, які справді цього потребують — і лише якщо ви готові прийняти операційні компроміси.
  5. Інституціоналізуйте базові профілі: зберігайте профілі продуктивності хостів і VM, щоб виявляти регресії до того, як помітять клієнти.

Якщо вам потрібна одна директива: розглядайте віртуалізацію зберігання як задачу бюджету затримки, а не як проблему вибору драйвера. Бюджет витрачається на шари. Витрачайте його свідомо.

← Попередня
Безпека WordPress: чеклист жорсткого захисту, що не блокує входи користувачів
Наступна →
Як правильно експортувати профілі Wi‑Fi (включаючи паролі)

Залишити коментар