Чому VM, більша за один сокет CPU, часто повільніша
Як працює NUMA на двосокетному хості, чому VM, що охоплює обидва сокети, платить за кожен доступ до пам’яті, і як прочитати свою топологію та підібрати розмір.

Коли віртуальна машина повільна, рефлекс полягає в тому, щоб дати їй більше ядер. На сучасному двосокетному сервері є точка, де цей рефлекс обертається проти вас, і більша VM працює повільніше за меншу, яку вона замінила. Ця точка є розміром одного сокета CPU, а причина називається NUMA.
Що таке NUMA, в одному абзаці
Сервер з двома процесорами насправді є двома комп’ютерами, що ділять міст. Кожен сокет має власні контролери пам’яті і власний банк пам’яті, під’єднаний безпосередньо до нього. Ядро, що читає пам’ять, під’єднану до власного сокета, отримує її на локальній швидкості. Ядро, що читає пам’ять, під’єднану до іншого сокета, має перетнути міжз’єднання між сокетами, і цей доступ повільніший. Така компоновка є неоднорідним доступом до пам’яті: кожне ядро може дістатися до всієї пам’яті, але не за однакову ціну. Кожен сокет зі своєю пам’яттю є вузлом NUMA.
Чому перетин межі коштує більше, ніж здається
Віддалений доступ до пам’яті займає приблизно в 1,7 раза більше часу, ніж локальний. Уже саме це є великим штрафом для всього, що часто торкається пам’яті, тобто майже для всього. І це ще не весь рахунок:
- Когерентність кешів. Два сокети мають тримати свої кеші узгодженими. Коли потоки з обох боків працюють з тими самими даними, рядки кешу стрибають через міжз’єднання, і кожен стрибок є зупинкою.
- Насичення міжз’єднання. Канал між сокетами має скінченну пропускну здатність. Зайнята VM, розмазана по обох сокетах, конкурує за нього з усім іншим на хості.
- Планування. VM, чиї vCPU не можуть усі одночасно працювати на одному сокеті, чекає, доки hypervisor знайде місце на обох, і її потоки потрапляють туди, де є місце, часто далеко від своїх даних.
Ці ефекти не додаються, а множаться. Навантаження, яке гарно масштабувалося б усередині одного сокета, може втратити велику частку своєї пропускної здатності, щойно розтягнеться на два, і додані вами ядра платять за цей привілей.
Прочитайте топологію свого хоста
Перш ніж підбирати будь-який розмір, подивіться, що хост насправді має:
lscpu | grep -E 'Socket|Core|Thread|NUMA'
numactl --hardware
Типовий вивід на двосокетному хості:
Thread(s) per core: 2
Core(s) per socket: 18
Socket(s): 2
NUMA node(s): 2
NUMA node0 CPU(s): 0-17,36-53
NUMA node1 CPU(s): 18-35,54-71
Числа, які мають значення, це ядра на сокет і пам’ять на вузол, яку numactl --hardware друкує разом з таблицею відстаней. Відстані показують, наскільки дорожчим є віддалений доступ порівняно з локальним саме на цій машині.
Підбирайте розмір за фізичними ядрами, а не за hyper-threads.
Два правила підбору розміру
З топології випливають два правила, які легко сформулювати і які варто ніколи не порушувати:
- VM ніколи не отримує більше vCPU, ніж один сокет має фізичних ядер. Залиште запас для самого hypervisor, тож на вісімнадцятиядерному сокеті розумною стелею є пара ядер нижче цього.
- VM ніколи не отримує більше пам’яті, ніж обслуговує один вузол NUMA. Інакше частина її пам’яті є віддаленою за побудовою, що б не робив планувальник.
VM, яка вкладається в обидва правила, може жити повністю всередині одного вузла, і хороший hypervisor її там утримає.
Перевірте, де насправді живе працююча VM
На хості KVM ви можете побачити, як пам’ять гостя розподілена між вузлами:
numastat -c qemu-kvm
virsh vcpuinfo <vm-name>
virsh numatune <vm-name>
Якщо numastat показує велику VM зі значною пам’яттю на обох вузлах, вона платить штраф віддаленого доступу на частині своєї роботи. Усередині гостя з Linux lscpu і numactl --hardware показують топологію, яку hypervisor йому представляє.
Коли одного сокета справді не вистачає
Деякі навантаження справді потребують більше, ніж може дати один сокет. Є кращі відповіді, ніж одна гігантська VM:
- Масштабуйте вшир. Дві VM, кожна з яких вкладається в один вузол, за балансувальником або як члени кластера, зазвичай перемагають одну VM, що охоплює обидва.
- Спочатку усуньте вузьке місце. VM, якій нібито потрібно більше ядер, часто чекає на storage, блокування або один зайнятий потік. Додавання ядер навантаженню, яке не може їх використати, не змінює нічого, крім штрафу NUMA.
- Якщо вона мусить бути великою, зробіть її обізнаною про NUMA. Представте гостю віртуальну топологію, яка віддзеркалює хост, два віртуальні вузли, що відповідають двом фізичним, і закріпіть кожен віртуальний вузол за його фізичним. Операційна система і застосунок, які знають про NUMA, тоді можуть тримати потоки біля їхніх даних. Корпоративні менеджери KVM, серед них OLVM, надають для цього закріплення NUMA.
Більше все одно не означає лінійно швидше
Навіть усередині одного сокета подвоєння vCPU рідко подвоює пропускну здатність. Частини кожного навантаження є послідовними, блокування стають зайнятішими з множенням потоків, кеші спільні, а hypervisor потребує більше зусиль, щоб спланувати широку VM. Подвоєння зазвичай варте лише частки подвоєння, а за межею сокета може піти в мінус. Чесний спосіб підібрати розмір полягає в тому, щоб запустити справжнє навантаження у двох розмірах і виміряти. Менша VM перемагає частіше, ніж люди очікують.
Як це робимо ми
У наших private cloud на OLVM і на кожній іншій платформі, яку ми експлуатуємо, обидва правила застосовуються до кожної VM: ніколи не більше vCPU, ніж один фізичний сокет має ядер, ніколи не більше пам’яті, ніж обслуговує один вузол NUMA, з розміром, підібраним за реальною топологією хоста. Ми підбираємо розмір точно під те, чого потребує навантаження, замість округлювати вгору.
Якщо розмір ваших VM підбирали навздогад, або вони просто росли по одному запиту за раз, ми радо подивимося на топологію і цифри разом з вами.
Related (rows[1])
Пов’язане: Private cloud на KVM з OLVM · Інженерія продуктивності та потужності · Виділена хмара на Apache CloudStack, за вашою специфікацією
Опубліковано 10 вересня 2026 р..
Усі нотатки з практики