Ви прив’язуєте vCPU, бо хочете «детермінованості». Потім VM з базою даних починає заїкатися кожні кілька хвилин, ваша p95‑латентність подвоюється,
і хтось неминуче каже: «Та ми ж прив’язали — то не CPU».
Прив’язка CPU в Proxmox може бути інструментом для підвищення продуктивності. Вона також може стати самозаподіяним denial‑of‑service для вашого планувальника.
Трик — знати, який саме тип «прив’язки» ви застосували, що ви вкрали у решти системи і яку саме затримку ви саме погіршили.
Міф: прив’язка дорівнює нижча затримка
Прив’язку продають як «припиніть міграцію VM між ядрами». Це не помилка. Але це не головне.
На сучасному Linux з KVM планувальник уже досить добре тримає «гарячі» потоки на тих самих ядрах.
Ще краще він вміє позичати вільний CPU з будь‑якої сторони під час піку навантаження.
Більшість людей прив’язують через проблему, яку вони не бачать: галасливі сусіди, конкуренція на хості, шквал переривань,
штрафи за віддалений доступ до пам’яті NUMA або оверсубскрипція. Прив’язка дає відчуття контролю. І часом це так.
Але якщо ви прив’язуєте, не розібравшись, куди йде час, ви перетворюєте гнучку систему, що адаптується до навантаження, на набір
крихітних кліток. Затримка тоді погіршується з двох причин:
- Ви прибираєте шляхі відступу планувальника. Коли CPU зайнятий (або піддатися перерванню), VM не може виконатися на іншому CPU.
- Ви концентруєте зіткнення. Потоки vCPU, емуляція QEMU, vhost‑net та службове навантаження хоста можуть битися за одні й ті самі ядра.
Прив’язка — це не «режим продуктивності». Це контракт. Ви обіцяєте VM ці ядра. В обмін ви повинні тримати ці ядра чистими:
мінімум переривань, стабільна частота, передбачувана локальність пам’яті та достатній запас. Більшість середовищ підписують контракт і
потім забувають платити.
Що таке прив’язка CPU насправді (і що насправді змінює Proxmox)
Три різні речі, які називають «прив’язкою»
В світі Proxmox «прив’язкою» називають щонайменше три різні механізми. Плутанина між ними породжує фольклор.
- Афінітет потоків QEMU/KVM (у стилі taskset). Ви прив’язуєте потоки vCPU VM до конкретних CPU хоста. Це класична «прив’язка vCPU».
- cpuset cgroups (жорстке розділення). Ви обмежуєте дерево процесів VM набором CPU. Сильніше за афінітет; ви огороджуєте його.
-
Ізоляція CPU для хоста (параметри завантаження ядра). Ви резервуєте CPU для певних навантажень, відштовхуючи загальну роботу хоста
(RCU callbacks, kworkers, багато переривань). Це та частина, яку люди пропускають.
Що Proxmox відкриває vs що реально планує Linux
Proxmox дає ручки як CPU units, CPU limit, NUMA і іноді «affinity» через args або hooks. Під капотом ваша VM — це процес QEMU з кількома потоками:
один на vCPU, плюс потоки вводу/виводу, плюс емуляція, плюс vhost‑потоки залежно від моделі пристрою.
Linux планує потоки, а не «VM».
Коли ви прив’язуєте «VM», ви насправді прив’язуєте деякий піднабір цих потоків.
Якщо ви прив’язали тільки потоки vCPU і забули про I/O‑потік, ви все одно можете отримати вузьке місце на неприприв’язаному потоці, що виконується на завантаженому ядрі.
Якщо ви прив’язали все на невеликий набір ядер, ви створите латифундий для затримок.
Прив’язка змінює справедливість, а не фізику
Прив’язка не робить CPU швидшими. Вона змінює хто має право виконуватись де.
Це важливо, бо завдання планувальника Linux — розподіляти виконуване навантаження по доступних CPU, зберігаючи справедливість та локальність кешу. Якщо ви обмежуєте його, ви маєте гарантувати для цього піднабору:
- Достатньо CPU‑циклів під піковим навантаженням
- Стабільна частота (без агресивного зниження під час сплесків)
- Прийнятна поведінка переривань
- Прийнятна NUMA‑локальність
Налаштування, що погіршує затримку: обмеження планувальника без ізоляції хоста
Ось шаблон налаштування, який тихо руйнує затримку: прив’язка vCPU (або використання cpuset) на хості Proxmox,
який все ще планує службові задачі хоста та переривання на тих самих ядрах.
Ви хотіли «виділені ядра». Ви отримали «спільні ядра з меншими опціями». VM не може втекти з завантаженого ядра, але ядро зайняте,
бо хост все ще виконує там свої справи: обробка переривань, kworker’и, ZFS‑обслуговування, softirq від мережі
та інші VM, які не були такими «особливими», як прив’язаний.
Результат — класична хвостова затримка. Медіана виглядає нормально. p95/p99 стає огидним. І ця огидність часто корелює з:
- Мережевими сплесками (стрибки часу softirq)
- Сплесками дискових операцій (kworker, ZFS txg sync, завершення IO)
- Періодичною активністю ядра (RCU, тики таймера на системах без NO_HZ)
- Перехідними змінами частоти (поведінка boost під тепловими/енергетичними обмеженнями)
Чому цей патерн гірший, ніж нічого не робити
Без прив’язки потоки vCPU VM можуть мігрувати з тимчасово «поганого» ядра. Планувальник може розподілити роботу.
Коли ви прив’язуєте, ви перетворюєте транзитні перешкоди хоста на жорстку зупинку: vCPU runnable, але не можна запустити на незаблокованому CPU.
Це не «детермінованість». Це «найкоротша черга у супермаркеті, але вона єдина, яку вам дозволено використовувати».
Жарт №1: Прив’язка CPU — як призначити кожному в офісі окремий ліфт. Дуже впорядковано, поки хтось не привезе візок.
Що робити натомість (у більшості випадків)
Якщо ваша мета — знизити затримку, не починайте з прив’язки. Почніть з:
- Запасу потужності: припиніть оверсубскрипцію CPU для вразливих до латентності VM
- Коректності NUMA: тримайте vCPU і пам’ять локально або свідомо приймайте штрафи
- Гігієни переривань: переконайтеся, що «виділені ядра» VM не отримують найгірших IRQ хоста
- Політики частоти CPU: оберіть governor, що відповідає вашому навантаженню (і перевірте)
Прив’язка має сенс, коли вона частина пакета: ізоляція CPU + IRQ affinity + вирівнювання NUMA + розумний розмір vCPU.
Одна лише прив’язка — як купити гоночну шину і поставити її на візок супермаркету.
Цікаві факти та коротка історія (чому це повторюється)
- Факт 1: Афінітет CPU існує в Linux десятиліттями, але став масовим з появою SMP, а потім розширився з віртуалізацією.
- Факт 2: KVM не має окремого планувальника гіпервізора; це модуль ядра. Ваша «планування гіпервізора» — здебільшого планувальник Linux.
- Факт 3: NUMA — це перфомансна міна від часу появи багатосокетних серверів; віддалений доступ до пам’яті виглядає як випадкові стрибки затримки.
- Факт 4: «Безтикові» ядра (NO_HZ) зменшили періодичні таймерні переривання, що важливо при гонитві за мікрозупинками на ізольованих CPU.
- Факт 5: irqbalance створювали для розподілу переривань для пропускної спроможності, а не для захисту ваших низьколатентних ядер від шумних IRQ.
- Факт 6: У віртуалізації vCPU — це потік на хості. Якщо його препемптить довгий softirq, гість відчуває це як «CPU steal» або просто «повільно».
- Факт 7: Поширення NVMe зменшило затримки дисків настільки, що планування CPU і переривання стали новим вузьким місцем у багатьох стеках.
- Факт 8: SMT/Hyper‑Threading ускладнює прив’язку, бо два «CPU» ділять виконавчі ресурси; прив’язка до сусідів може збільшити джиттер при конкуренції.
- Факт 9: cgroups еволюціонували від CPU shares до точніших контролерів; cpuset потужний і легко ним зловживати, як кувалдою в майстерні годинника.
Як прив’язка ламається: основні режими відмов, що впливають на затримку
1) Ви прив’язали vCPU на ядра, що є точками скупчення переривань
Latency‑чутлива VM, прив’язана до ядра, на яке приходять NIC‑переривання — це особливий вид самопошкодження.
Під навантаженням обробка softirq може домінувати. Ваш потік vCPU runnable, але CPU зайнятий мережевою роботою хоста.
Гість бачить випадкові паузи та джиттер.
2) Ви прив’язали через NUMA‑вузли, не контролюючи локальність пам’яті
Ви можете прив’язати vCPU до CPU на двох сокетах, а пам’ять гість отримає в основному на одному вузлі. Тепер половина ваших vCPU
робить віддалений доступ до пам’яті. Віддалена пам’ять не завжди катастрофа, але рідко стабільна. Ви отримаєте хвостові стрибки, коли віддалена пропускна здатність насититься.
3) Ви прив’язали, а потім усе одно оверсубскрибували
Прив’язка не виправляє оверсубскрипцію; вона робить її більш жорсткою. Якщо ви прив’язали кілька завантажених VM на перекриваючі CPU‑набори,
ви створили конкуренцію, навколо якої не можна спланувати. Ось як ви отримуєте «все добре до 10:02, а потім все горить».
4) Ви забули про «інші потоки» (I/O‑потік, vhost, емулятор)
VM з інтенсивним зберіганням або мережею може блокуватись на потоці QEMU I/O або vhost‑потоках.
Якщо ви прив’язали vCPU, але не шлях I/O, ви можете отримати vCPU, що чекають на неприприв’язаний, перевантажений потік.
Це не проблема CPU у гості — це архітектурна проблема на хості.
5) Столкнення SMT‑сиблингів
Прив’язка VM на «два ядра», що фактично є двома потоками одного фізичного ядра, може знизити пропускну спроможність і збільшити джиттер.
Для латентності зазвичай бажані повні фізичні ядра, а не сиблинг‑пари, якщо немає вимірюваного доведеного ефекту.
6) Скалювання частоти та обмеження живлення
Прив’язка зменшує мобільність планувальника, що може погано взаємодіяти з поведінкою boost.
Якщо ваші прив’язані CPU — ті, що першими знижують частоту під тепловим або енергетичним навантаженням,
ви побачите уповільнення, що виглядає як «таємні випадкові затримки». Насправді це політика.
Цитата (перефразована ідея), приписувана: Werner Vogels часто повторює принцип надійності: «Усе ламається; проектуйте так, щоб було безпечно і відновлювано, коли це станеться.»
Прив’язка — це також рішення з надійності — робіть його відновлюваним, а не крихким.
Швидкий план діагностики
Коли затримки стрибнули на хості Proxmox з «прив’язаними» VM, вам потрібні відповіді за хвилини, а не через тиждень інтерпретативного перформанс‑арту.
Ось порядок, що зазвичай найшвидше знаходить винного.
Спочатку: доведіть, що це планування/переривання CPU, а не зберігання
- Перевірте насичення CPU та симптоми типу steal: подивіться на чергу runnable і по‑CPU використання.
- Перевірте час softirq/irq: якщо softirq високий на прив’язаних CPU — це ваш лиходій.
- Перевірте затримку зберігання: якщо диски в нормі і CPU не навантажений — перестаньте автоматично звинувачувати ZFS.
Друге: переконайтесь, що прив’язка реальна і повна
- Які потоки справді прив’язані (тільки vCPU? всі потоки QEMU?)
- Чи використовувані CPU поділяються з іншими VM або роботою хоста?
- Чи випадково ви прив’язали до SMT‑сиблингів?
Третє: перевірте NUMA‑локальність
- Чи розкидані vCPU по вузлах?
- Чи пам’ять виділена на тих самих вузлах?
- Чи VM достатньо велика, щоб віддалені штрафи пам’яті були неминучими?
Четверте: підтвердьте поведінку енергоспоживання/частоти
- Governor і поточні частоти під навантаженням
- Обмеження потужності/події тротлінгу
П’яте: тільки потім змінюйте прив’язку
Прив’язка — крайній крок, бо її легко змінити і важко осмислити наслідки після. Спочатку діагностуйте; потім звужуйте обмеження малими, відкатними кроками.
Практичні завдання: команди, виводи та рішення (12+)
Це перевірки, які я виконую на хості Proxmox, коли хтось каже: «Ми прив’язали CPU і затримка стала гіршою».
Кожне завдання містить: команду, що означає її вивід, і рішення, яке приймаєте.
Завдання 1: Визначте процес QEMU VM та його потоки
cr0x@server:~$ pgrep -a qemu-system
21433 /usr/bin/kvm -id 101 -name vm-db01 -m 32768 -smp 8,sockets=1,cores=8,threads=1 ...
Значення: У вас є PID VM. Прив’язка впливає на цей процес і його потоки.
Рішення: Використовуйте PID 21433 для подальших перевірок афінітету і планування.
Завдання 2: Подивіться використання CPU по потоках (знайдіть потоки vCPU, iothread, vhost)
cr0x@server:~$ top -H -p 21433 -b -n 1 | head -n 20
top - 10:12:41 up 32 days, 2:10, 2 users, load average: 6.12, 5.88, 5.77
Threads: 36 total, 6 running, 30 sleeping, 0 stopped, 0 zombie
%Cpu(s): 62.3 us, 7.2 sy, 0.0 ni, 28.9 id, 1.1 wa, 0.0 hi, 0.5 si, 0.0 st
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
21433 root 20 0 12.3g 1.1g 19772 S 10.0 3.6 2:41.33 kvm
21445 root 20 0 12.3g 1.1g 19772 R 98.7 3.6 22:18.91 CPU 0/KVM
21446 root 20 0 12.3g 1.1g 19772 R 95.4 3.6 21:55.07 CPU 1/KVM
21447 root 20 0 12.3g 1.1g 19772 S 12.3 3.6 1:10.10 IOThread
Значення: Потоки vCPU (CPU 0/KVM, CPU 1/KVM тощо) і IOThread видимі. IOThread теж виконує роботу.
Рішення: Якщо ви прив’язали лише vCPU, а IOThread завантажений, треба або прив’язати IOThread, або надати йому чисте ядро.
Завдання 3: Перевірте поточний афінітет CPU для процесу QEMU
cr0x@server:~$ taskset -cp 21433
pid 21433's current affinity list: 2-5
Значення: Процес QEMU обмежений CPU 2‑5. Це лише чотири CPU простору планування.
Рішення: Підтвердіть, що це відповідає очікуваній кількості vCPU і що CPU 2‑5 не є насиченими перериваннями або спільною роботою.
Завдання 4: Перевірте афінітет по потоках (тут часто «ми прив’язали» розвалюється)
cr0x@server:~$ for t in $(ls /proc/21433/task); do echo -n "$t "; taskset -cp $t | sed 's/^.*: //'; done | head
21433 2-5
21445 2-5
21446 2-5
21447 0-31
21448 0-31
Значення: Потоки vCPU прив’язані до 2‑5, але IOThread (21447) та інший допоміжний потік — ні.
Рішення: Або навмисно прив’яжіть всі релевантні потоки QEMU, або прийміть, що неприприв’язані потоки можуть стати вузьким місцем і додати джиттер.
Завдання 5: Знайдіть, які CPU — SMT‑сиблінги (щоб уникнути випадкової пів‑ядрової прив’язки)
cr0x@server:~$ lscpu -e=CPU,CORE,SOCKET,NODE | head
CPU CORE SOCKET NODE
0 0 0 0
1 0 0 0
2 1 0 0
3 1 0 0
4 2 0 0
5 2 0 0
Значення: CPU 2 і 3 ділять CORE 1, CPU 4 і 5 ділять CORE 2. Якщо ви прив’язали 2‑5, ви використовуєте два фізичні ядра (з SMT).
Рішення: Для латентних VM надавайте перевагу повним фізичним ядрам (наприклад, 2,4,6,8…), а не парам сиблингів, якщо ви не маєте вимірюваного підтвердження.
Завдання 6: Перевірте переривання по CPU (ловіть точки скупчення IRQ)
cr0x@server:~$ egrep -i 'CPU|eth0|nvme|mlx|ixgbe|virtio' /proc/interrupts | head -n 15
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
45: 0 0 9123456 0 0 0 IR-PCI-MSI 524288-edge eth0-TxRx-0
46: 0 0 0 8876543 0 0 IR-PCI-MSI 524289-edge eth0-TxRx-1
97: 11234 11876 10987 11022 11301 11450 IR-PCI-MSI 0000:01:00.0 nvme0q0
Значення: CPU2 і CPU3 приймають більшість NIC‑переривань. Якщо ваша VM прив’язана до 2‑5, половина її «виділеної» потужності обробляє пакети.
Рішення: Перемістіть прив’язку VM від CPU‑локусів з перериваннями або перемістіть IRQ‑афінітет геть від CPU VM. Не діліть випадково.
Завдання 7: Перевірте тиск softirq по CPU (мережеві сплески тут проявляються)
cr0x@server:~$ awk 'NR==1||/NET_RX|NET_TX|SCHED|RCU/ {print}' /proc/softirqs
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
NET_RX 123456 234567 9988776 8877665 345678 456789
NET_TX 98765 87654 5432109 4321098 76543 65432
SCHED 456789 567890 678901 789012 890123 901234
RCU 34567 45678 56789 67890 78901 89012
Значення: NET_RX/NET_TX значно вищі на CPU2/CPU3. Це узгоджується з мапінгом IRQ.
Рішення: Якщо стрибки латентності корелюють з мережею, вважайте розміщення IRQ/softirq пріоритетним до зміни прив’язки.
Завдання 8: Перевірте load vs runnable threads (чи бракує CPU на прив’язаних ядрах)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
7 0 0 812344 90212 4231120 0 0 2 31 8123 19022 64 9 26 1 0
9 0 0 811920 90212 4231400 0 0 0 0 9100 22011 71 11 17 1 0
Значення: Стовпець «r» (runnable) високий. Якщо VM прив’язана до малого набору CPU, черга runnable може концентруватись там.
Рішення: Якщо runnable постійно перевищує доступні CPU, відв’яжіть або розширте CPU‑набір, або зменшіть кількість vCPU, щоб відповідати реальній потужності.
Завдання 9: Подивіться по‑CPU використання і steal‑подібні паузи
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.2.16-xx (pve01) 02/04/2026 _x86_64_ (32 CPU)
12:14:01 PM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
12:14:02 PM 2 68.0 0.0 9.0 0.0 2.0 21.0 0.0 0.0
12:14:02 PM 3 61.0 0.0 8.0 0.0 1.0 29.0 0.0 1.0
12:14:02 PM 4 55.0 0.0 6.0 0.0 0.0 4.0 0.0 35.0
Значення: CPU2/CPU3 мають величезний %soft. На цих CPU виконується робота softirq мережі замість ваших прив’язаних vCPU.
Рішення: Збалансуйте IRQ або перемістіть VM. Прив’язка до softirq‑перевантажених CPU — спосіб штучно створити джиттер.
Завдання 10: Підтвердіть NUMA‑топологію і чи VM охоплює вузли
cr0x@server:~$ numactl -H | head -n 20
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 128755 MB
node 0 free: 42110 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 128705 MB
node 1 free: 39876 MB
Значення: Два NUMA‑вузли. Якщо ви прив’язали VM до CPU по обох вузлах без гарантії локальності пам’яті, будуть віддалені доступи.
Рішення: Тримайте латентні VM в межах одного NUMA‑вузла або увімкніть/перевірте NUMA‑налаштування і вимірюйте.
Завдання 11: Перевірте політику NUMA пам’яті процесу QEMU (чи пам’ять локальна?)
cr0x@server:~$ grep -E 'Mems_allowed_list|Cpus_allowed_list' /proc/21433/status
Cpus_allowed_list: 2-5
Mems_allowed_list: 0-1
Значення: CPU‑набір 2‑5 (вузол 0), але пам’ять дозволена на обох вузлах. Це може призвести до крос‑вузлових виділень залежно від навантаження.
Рішення: Якщо ви щільно прив’язуєте, контролюйте також локальність пам’яті. Інакше отримаєте найгірший набір: фіксовані CPU та «блукаючу» пам’ять.
Завдання 12: Перевірте governor CPU і поточну поведінку частоти
cr0x@server:~$ cat /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
powersave
Значення: Governor — «powersave». Це може бути прийнятно на деяких платформах, але часто погано для імпульсних латентних навантажень.
Рішення: Розгляньте перемикання на «performance» (або налаштовану політику) для хостів з критичними VM і перевірте частоту під навантаженням.
Завдання 13: Перевірте тротлінг / підказки про ліміти енергії (коли доступні)
cr0x@server:~$ dmesg -T | egrep -i 'thrott|powercap|thermal' | tail -n 5
[Mon Feb 3 22:11:52 2026] thermal thermal_zone0: critical temperature reached (97 C), shutting down
[Mon Feb 3 22:11:05 2026] CPU0: Package temperature above threshold, cpu clock throttled
Значення: Якщо бачите тротлінг, ваша «налаштувальна оптимізація» — перестановка меблів на палубі. CPU буквально сповільнюється.
Рішення: Виправте охолодження/енергетичні ліміти перед мікрооптимізаціями планувальника.
Завдання 14: Перевірте затримку ZFS та поведінку txg (щоб не звинувачувати зберігання даремно)
cr0x@server:~$ zpool iostat -v 1 3
capacity operations bandwidth
pool alloc free read write read write
rpool 1.12T 2.38T 120 310 8.2M 21.1M
nvme0n1 1.12T 2.38T 120 310 8.2M 21.1M
Значення: У цьому знімку немає очевидної насиченості зберігання. Якщо затримки VM стрибають, а ZFS спокійна, дивіться назад на CPU/переривання.
Рішення: Не «чіпляйтеся за ZFS», тільки тому що це ZFS. Виправляйте вузьке місце, яке можете виміряти.
Завдання 15: Підтвердіть конфігурацію CPU VM у Proxmox (кількість vCPU, sockets/cores)
cr0x@server:~$ qm config 101 | egrep 'name|cores|sockets|cpu|numa|balloon'
name: vm-db01
cores: 8
sockets: 1
cpu: x86-64-v2-AES
numa: 1
balloon: 0
Значення: NUMA ввімкнено, налаштовано 8 ядер. Якщо ви прив’язали процес до 4 host CPU (Завдання 3), ви створили явну невідповідність.
Рішення: Узгодьте розмір vCPU і прив’язку. Якщо ви хочете 8 vCPU — дайте для них планувальний простір, або зменшіть кількість vCPU, щоб відповідати реальній потужності.
Три корпоративні міні‑історії з полів прив’язки
Міні‑історія 1: Інцидент через неправильне припущення («Прив’язане означає виділене»)
Середня SaaS‑компанія використовувала Proxmox для внутрішніх сервісів: CI‑ранери, зберігання артефактів, моніторинг, кілька кластерів баз даних.
Одного дня основна VM бази даних почала показувати періодичні затримки виконання запитів. Не повільні запити — саме паузи. З’єднання зависали на секунду чи дві,
потім відновлювалися. Графіки виглядали як пилка: спокій, стрибок, спокій, стрибок.
Команда нещодавно «укріпила продуктивність», прив’язавши vCPU бази до чотирьох CPU хоста. Припущення було просте:
прив’язане = виділене, виділене = стабільне. Вони також думали, що якщо VM має чотири прив’язані CPU, її не можуть торкнути інші VM.
Це припущення й відправило їх на кілька годин читання /proc, як детективний роман.
Корінь проблеми був не в базі і навіть не в конфігурації VM. Це було розміщення переривань.
Найзавантаженіші RX/TX черги NIC падали на ті ж CPU, куди була прив’язана VM. Під резервними трафіками й завантаженням артефактів CI
час softirq на цих ядрах різко зростав. Потоки vCPU були runnable, але «їхні» CPU зайняті обробкою пакетів.
Гість відчував випадкові паузи.
Виправлення було нудним: перемістити IRQ‑афінітет з CPU VM і перестати вдавати, що прив’язка — це ізоляція.
Вони також розширили дозволений набір CPU для VM на пару «чистих» ядер, пожертвувавши трохи локальності кешу заради можливості втечі.
Затримки зникли. Постмортем‑висновок: прив’язка — це зобов’язання перед хостом, а не захист від нього.
Міні‑історія 2: Оптимізація, що відкотилася («Прив’язали заради теплоти кешу»)
Інша організація запускала латентні брокери повідомлень у VM. Вони хотіли зменшити хвостову затримку під піками.
Хтось запропонував прив’язувати кожну VM брокера до невеликого набору CPU «для теплоти кешу». План красиво виглядав на фліпчарті:
по дві VM на сокет, кожна з прив’язаними vCPU, без блукання.
У синтетичних тестах це працювало. Потім прийшов продакшн. Реальний трафік — не бенчмарк; це серія дрібних катастроф з часовими підписами.
Під сплесками одна VM брокера насичувала свої прив’язані CPU і нарощувала чергу. Планувальник не міг позичити простоюче час з інших CPU,
бо процес був обмежений. Затримка піднімалась і трималась довше, ніж очікували, бо чергу потрібно було часом осадити.
Тим часом інші CPU хоста були частково простоюючими. Хост мав ємність; VM не мала права її використати.
Оптимізація обміняла «теплі кеші» на «жорсткі стіни». Теплі кеші — приємно. Втрачені повідомлення й тайм‑аути — менш приємно.
Відкат навчив: прибрали жорстку прив’язку, залишили розумні shares, і сконцентрувались на зменшенні конкуренції на хості:
менше шумних сусідів на вузол, кращий розподіл IRQ і NUMA‑усвідомлене розміщення.
Медіана майже не змінилася. Хвостова латентність покращилася. Результат не був героїчним; він був передбачуваним.
Міні‑історія 3: Нудна, але правильна практика, що врятувала день («Виміряй, потім ізолюй лише важливе»)
Фінтех‑суміжна компанія вела кластер Proxmox для змішаних навантажень: пакетні роботи, внутрішні веб‑додатки, кілька латентних сервісів.
У них була нудна, але корисна практика: кожна «змова з продуктивності» вимагала фіксації «до/після» CPU, переривань
і затримки зберігання на рівні хоста, плюс план відкату, виконуваний за кілька хвилин.
У піковий період критична VM показала джиттер. Команда аплікації хотіла миттєво прив’язати CPU.
Інфраструктурна команда ввічливо не відмовила, але наполягла на попередньому захопленні метрик.
За 15 хвилин вони знайшли проблему: VM була в порядку; хост мав періодичний стрибок softirq через зміну налаштувань черг NIC.
Вони виправили розподіл NIC/IRQ, перевірили нормалізацію часу softirq, і джиттер VM зник без доторку до афінітету vCPU.
Ніхто не мав чим похизуватись як «тюнінг». Система просто знову працювала, а це і є головна мета.
Нудна практика, що їх врятувала: виміряти конкуренцію на хості спочатку, вважати прив’язку хірургічним інструментом
і робити зміни відкатними. Це завадило тижню експериментів і «працює на одному хості» дрейфу, що ускладнює експлуатацію кластерів.
Жарт №2: Прив’язувати без вимірювань — як встановлювати годинник, довго на нього дивлячись. Відчуваєш продуктивність; час не вражений.
Поширені помилки: симптом → корінь → виправлення
1) Симптом: p99‑латентність стрибає після прив’язки, при цьому середнє CPU виглядає нормальним
Корінь: Ви прив’язали до IRQ/softirq‑насичених CPU або обмежили VM так, що вона не може уникнути транзитних перешкод хоста.
Виправлення: Перемістіть IRQ‑афінітет з цих CPU і/або розширте дозволений набір CPU. Якщо вам дійсно потрібні «виділені» ядра, реалізуйте ізоляцію CPU правильно.
2) Симптом: VM «зависає» під мережевими сплесками
Корінь: Обробка softirq (NET_RX/NET_TX) відбирає час у прив’язаних vCPU.
Виправлення: Збалансуйте черги/IRQ NIC, розгляньте налаштування RSS і тримайте латентні VM поза цими ядрами.
3) Симптом: Ви прив’язали 8‑vCPU VM, але вона поводиться як 4‑vCPU VM
Корінь: Афінітет процесу QEMU обмежений меншою кількістю host CPU, ніж vCPU‑кількість, або SMT‑сиблинг маскує «ядра».
Виправлення: Узгодьте кількість vCPU з фізичними ядрами (не потоками) і з дозволеним набором CPU. Зменшіть vCPU, якщо не можете надати реальну потужність.
4) Симптом: Продуктивність непостійна між хостами одного кластера
Корінь: Різні налаштування BIOS, CPU governor, мікрокод чи розклад IRQ. Прив’язка підсилює ці відмінності.
Виправлення: Стандартизувати прошивку, параметри ядра та політику продуктивності між вузлами. Перевіряти тими ж командами хоста.
5) Симптом: Зберігання звинувачують, але диски ніби простоюють
Корінь: Конкуренція CPU в шляхах завершення I/O (kworkers, softirq, vhost), а не затримка медіа.
Виправлення: Виміряйте %soft і по‑потокове навантаження; переконайтесь, що I/O‑потоки не застрягають на завантажених CPU; перевірте розміщення IRQ для NVMe і NIC.
6) Симптом: «Прив’язка допомогла один раз, а потім стало гірше після оновлення ядра»
Корінь: Змінився планувальник, за умовчанням IRQ або поведінка драйвера; ваша прив’язка спиралась на крихкі припущення.
Виправлення: Перевірте переривання, softirq і топологію CPU після оновлень. Розглядайте конфіги прив’язки як артефакти, залежні від ядра/платформи.
7) Симптом: VM швидка, поки інша VM на хості не розвантажиться
Корінь: Перекриваючі набори прив’язки або спільні SMT‑сиблинг‑ресурси. Ви вбудували шумних сусідів у ті самі фізичні ресурси.
Виправлення: Забезпечте, щоб набори CPU не перекривались для критичних VM і уникайте сиблинг‑конкуренції. Якщо не можете уникнути перекриття — не прив’язуйте; спирайтесь на shares і запас потужності.
Контрольні списки / покроковий план
Контрольний список A: Визначте, чи взагалі потрібно прив’язувати
- Чи хост CPU оверсубскрибований? Якщо так — виправте це спочатку. Прив’язка не створить додаткової потужності.
- Чи є доведена інтерференція IRQ/softirq? Якщо так — вирішіть це перед прив’язкою.
- Чи порушена локальність NUMA? Якщо так — виправте розміщення і локальність пам’яті перед прив’язкою.
- Чи робота чутлива до латентності чи пропускної спроможності? Якщо на пропуск — прив’язка часто зменшує пікову пропускну здатність, обмежуючи гнучкість планувальника.
- Чи можна швидко відкотити зміни? Якщо ні — не робіть цього в продакшні. Зміни прив’язки мають високий ризик, бо «ніби працюють», поки неправі.
Контрольний список B: Якщо прив’язуєте, робіть це як пакет (план «зробити це справжнім»)
- Вибирайте повні фізичні ядра (уникайте SMT‑сиблингів для основного набору, якщо не маєте вимірювань).
- Тримайте в межах NUMA‑вузла коли можливо: vCPU і пам’ять мають бути разом.
- Зсуньте переривання з тих ядер (звичайні підозрювані: NIC і NVMe).
- Урахуйте непроцесорні потоки: IOThread, vhost, емулятор. Не голодуйте шлях I/O.
- Налаштуйте політику частоти, що відповідає потребам латентності, і перевірте під реальним навантаженням.
- Залиште виходи — трохи більший CPU‑набір може зменшити хвостову латентність у імпульсних середовищах.
Контрольний список C: Розгортання і валідація (не довіряйте першому результату)
- Захопіть базу: mpstat, /proc/interrupts, /proc/softirqs, використання потоків VM, iostat зберігання.
- Змініть одну річ: прив’язку або IRQ‑афінітет або NUMA‑політику — не все одночасно.
- Виміряйте p95/p99 в гості та %soft/%irq на хості.
- Тримайте команду відкату під рукою і перевірену.
- Перевірте після перезавантаження: розподіл IRQ і топологія CPU можуть зміститись.
FAQ
1) Чи слід прив’язувати CPU для кожної VM у Proxmox?
Ні. Більшість VM виграють від гнучкості планувальника. Прив’язуйте лише з виміряної причини: потреба в передбачуваній ізоляції, ліцензійні обмеження
або латентне навантаження, де ви також контролюєте переривання і NUMA‑локальність.
2) Чому прив’язка підвищила пропускну здатність, але погіршила латентність?
Пропускна здатність може зрости через локальність кешу і менше міграцій. Хвостова затримка може погіршити, бо VM не може уникнути транзитних перешкод
на прив’язаних CPU. Сплески виявляють слабкі місця.
3) Чи «CPU limit» те саме, що прив’язка?
Ні. CPU limit — це тротлінг (cgroup quota). Прив’язка — це розміщення (affinity/cpuset). Тротлінг може додати затримки через періодичні сну,
розміщення може додати затримки, забороняючи міграцію від конкуренції. Різні ножі — різні порізи.
4) Чи увімкнення NUMA в VM вирішує NUMA‑проблеми?
Воно може допомогти, але це не чарівництво. Потрібно також стежити, щоб хост коректно розміщував vCPU і пам’ять. Перевіряйте інструментами NUMA хоста і дивіться,
чи CPU‑набір VM відображається на один вузол.
5) Який найпростіший спосіб визначити, чи переривання відбирають прив’язані CPU?
Подивіться в /proc/interrupts і /proc/softirqs, потім зіставте з mpstat %irq/%soft на прив’язаних CPU.
Якщо прив’язані CPU мають високий softirq під час стрибків затримки — відповідь у вас.
6) Чи слід вимикати SMT/Hyper‑Threading для низької латентності?
Іноді. SMT може збільшити пропускну здатність, але також додає конкуренцію і джиттер в деяких навантаженнях. Перед глобальним відключенням спробуйте прив’язувати до одного потоку на ядро
(уникати сиблингів) і вимірюйте. Вимкнення SMT — це сильний інструмент з широкими наслідками.
7) Чи може прив’язка спричинити дрейф часу або проблеми з годинником у гості?
Сама по собі прив’язка прямо не викликає дрейфу годинника, але може збільшити затримки планування, які впливають на часочутливі додатки.
Переконайтеся в коректній синхронізації часу гостя і уникайте політик CPU‑тротлінгу, що вводять періодичні паузи.
8) Моя VM прив’язана і все одно повільна. Що далі?
Перевірте, чи прив’язка повна (всі потоки), переконайтеся, що на цих CPU немає точок скупчення IRQ, перевірте NUMA‑локальність, поведінку частоти та тротлінг,
і підтвердіть, що проблема не в тому, що просто бракує CPU. Прив’язка не виправить «потрібно більше CPU».
9) Краще прив’язувати менше vCPU і вертикально масштабувати, чи більше vCPU і покладатися на планувальник?
Для багатьох латентних сервісів краще мати менше добре завантажених vCPU з запасом, ніж багато vCPU, що борються за обмежені ядра.
Правильний розмір визначайте за чергою runnable і конкуренцією додатку. Вимірюйте, не вгадуйте.
Практичні наступні кроки
Якщо запам’ятати одну річ: прив’язка — це не ізоляція. Це обмеження. Обмеження підвищують вартість помилок.
Якщо ви збираєтеся обмежувати планувальник, ви мусите створити для нього чисте середовище: переривання там, де потрібно, NUMA‑локальність, що має сенс,
і достатній запас CPU, щоб сплески не перетворювались на домашку з теорії черг.
- Прогоніть швидкий план діагностики і перевірте, чи стрибки корелюють з %soft/%irq, runnable backlog або затримкою зберігання.
- Аудитуйте повноту прив’язки: потоки vCPU, IOThread і допоміжні потоки для вашого I/O‑шляху.
- Перестаньте прив’язувати на IRQ‑гарячі ядра. Якщо прив’язка необхідна — робіть це з афінітетом IRQ.
- Узгодьте прив’язку з фізичними ядрами та NUMA‑вузлами. Уникайте випадкової прив’язки до SMT‑сиблингів для критичної латентності.
- Робіть зміни по одній і майте готові відкати. Ваш майбутній «я» буде втомленим і вдячним.
Якщо ви зробите ці кроки, ви рідше прив’язуватимете — а коли прив’яжете, це буде робитися не через забобони, а з поясненим мотивом.
Ось і все завдання.