Сокети як стратегія: чому платформи зараз важливіші за процесори

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

Хтось розбудить вас о 2:13 ранку з повідомленням «CPU лише на 35%», але при цьому API таймаутиться, база даних «випадково повільна», а затримки сховища виглядають як сейсмограф. Ви дивитиметеся на дашборди, що запевняють, що все гаразд, тоді як клієнти кажуть протилежне.

Ось сучасна пастка продуктивності: ми досі купуємо сервери, ніби це конкурс «хто швидший CPU», але більшість продакшн-аварій і уповільнень — це проблеми платформи: сокети, канали пам’яті, лінії PCIe, топологія NUMA і шлях, яким I/O доходить до кремнію.

Неприємна правда: платформа — це й є комп’ютер

Ми любимо говорити про CPU, бо специфікації CPU акуратні: кількість ядер, GHz, розміри кешу. Платформи — брудні: кількість сокетів, домени NUMA, канали пам’яті, покоління DDR і правила заповнення DIMM, покоління PCIe і маршрування ліній, налаштування BIOS, прошивка, IOMMU, маршрутизація переривань і все більше прискорювачів.

У 2026 році сам CPU рідко є лімітуючим реагентом. Ваші вузькі місця в системі знаходяться на шляхах між CPU і всім іншим:

  • Пропускна здатність і затримка пам’яті (канали, ранги, швидкість і чи виконуються потоки «близько» до їхньої пам’яті).
  • Топологія I/O (лінії PCIe, комутатори, бівіркація, де розташовані NVMe і NIC і як вони діляться uplink).
  • Міжсокетна шина (штрафи за віддалений доступ до пам’яті та трафік кохерентності кеша між сокетами).
  • Розміщення переривань і черг (пакети та завершення, що обробляються не тими ядрами).
  • Енергопостачання і терміка (поведінка boost, стійкі частоти та різниця між маркетинговим TDP і реальністю).

Купувати «більше CPU» — це як додати більше кас, коли вхід у магазин — одна вузька двері. Ви можете найняти стільки касирів, скільки завгодно; клієнти все одно не зможуть зайти.

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

Цитата, яка варта липкої записки

Парафразована ідея (John Ousterhout): «Система швидка, коли ви усуваєте один великий вузький момент; багато дрібних оптимізацій мало що змінюють.»

Ось у чому суть. Знайдіть великий вузький момент. І сьогодні цей вузький момент часто — топологія платформи, а не пропуск інструкцій.

Цікаві факти та історія, що пояснюють цей безлад

Кілька контекстних пунктів, які роблять сучасну ідею «сокети як стратегія» менш змовницькою і більше фізикою та економікою:

  1. Раніше «Northbridge» був окремим чипом. Контролери пам’яті і корені PCIe жили поза CPU; весь сервер можна було задушити на одному спільному лінку чипсету.
  2. Інтегровані контролери пам’яті змінили все. Коли пам’ять перемістилася на пакет CPU, її продуктивність стала тісно пов’язана з вибором сокета і правилами заповнення DIMM.
  3. NUMA «реальна» вже десятиліттями. Багатосокетні сервери завжди мали нерівномірний доступ до пам’яті, але штраф став помітніший, коли кількість ядер зросла, а робочі навантаження — більш паралельні.
  4. PCIe замінив спільні шини неспроста. Індустрія залишила спільні паралельні шини, бо конкуренція вимагала точково-точкових зв’язків і масштабованих ліній.
  5. Віртуалізація перетворила топологію на політику ПЗ. Гіпервізори можуть приховувати або показувати NUMA, прикріпляти vCPU і розміщувати пам’ять — іноді геніально, іноді катастрофічно.
  6. NVMe зробив сховище «сусідом CPU». I/O зберігання перемістився з HBA-черг і прошивки у прямі PCIe-пристрої з глибокими чергами, збільшивши навантаження на переривання, кеш і пропускну здатність пам’яті.
  7. RDMA і kernel-bypass мережі зробили NIC частиною платформи. Коли мережевий стек переходить у user space або NIC робить оффлоади, розміщення черг і локальність PCIe стають особливостями продуктивності.
  8. Моделі ліцензування «озброїли» сокети. Деяке корпоративне ПЗ ліцензується за сокетом, за ядром або за «одиницею ємності», тому рішення про платформу набувають фінансового сенсу, а не тільки технічного.
  9. Міри безпеки змінили вартість деякої роботи на CPU. Для певних навантажень системні виклики і контекстні перемикання стали дорожчими, що підвищило відносну важливість мінімізації I/O-накладних витрат і між-NUMA-трафіку.

Це не дрібниці. Вони пояснюють, чому «просто купити швидший CPU» дедалі частіше не працює.

Що вам дає «сокет» насправді (і яку ціну ви платите)

Кількість сокетів — це рішення про топологію

Сокет — це фізичний пакет CPU, так. Але операційно це також пакет контролерів пам’яті, коренів PCIe та точок підключення шини. Додавання другого сокета може дати більше пам’яті й пропускної здатності, та більше I/O-з’єднань — залежно від платформи. Це також додає можливість віддаленого доступу до пам’яті й накладні витрати на узгодження між сокетами.

У одно-сокетній системі «щасливий шлях» простий:

  • Вся пам’ять «локальна».
  • Більшість PCIe-пристроїв за один проміжок від CPU.
  • Помилки планувальника караються менше.

У двосокетній системі вам доведеться заробити продуктивність:

  • Ваші потоки повинні працювати на сокеті, що володіє їхніми алокаціями пам’яті.
  • NIC і NVMe — на тому ж сокеті, що й найактивніші ядра, які їх обробляють.
  • Ваше навантаження має або масштабуватися чисто через NUMA-вузли, або бути закріпленим і ізольованим.

Сухе правило: багато ПЗ не «масштабується через сокети». Воно масштабується по ядрах, поки міжсокетний трафік не стає податком, на який ви не розрахували.

Канали пам’яті: тихий обмежувач продуктивності

Кількість ядер продає сервери. Канали пам’яті їх живлять.

Платформа з більшою кількістю каналів пам’яті на сокет може годувати більше ядер до того, як вони почнуть голодувати. При навантаженнях, інтенсивних по пам’яті (аналітика, кеш-шари, деякі бази даних, JVM-пам’ять під тиском, великі in-memory індекси), пропускна здатність пам’яті часто є стелею. Ви можете купити CPU з більшою кількістю ядер і спостерігати, як пропуск залишається на місці, бо ядра чекають пам’ять.

Неправильне заповнення DIMM може зменшити пропускну здатність. Багато платформ вимагають збалансованого заповнення по каналах. Мішання швидкостей або ранґів може привести до пониження частоти для всіх модулів. Тому вибір платформи включає «нудні» питання: скільки каналів, які типи DIMM і які правила заповнення.

Лінії PCIe: бюджет I/O, який ви не можете перевищити

Кожен NVMe-диск, NIC, GPU, DPU чи HBA споживає лінії PCIe і/або ділить uplink за комутаторами. Фізично сервер може мати вісім відсіків NVMe, але електрично вони можуть ділити менше uplink-ів, ніж ви думаєте.

Це поширений виробничий сюрприз: сервер має достатньо відсіків, але не достатньо ліній. Тоді ви дізнаєтесь, що означає «x4 до backplane через uplink комутатора» на піку.

Жарт 1/2: планування ліній PCIe схоже на організацію шафи — ігноруй її довго, і ти опинишся в темряві з кабелями, які не пам’ятаєш, що купував.

NUMA: не баг, а податок реальності

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

Штрафи NUMA проявляються як:

  • Зростання хвостових затримок, коли «гаряче» ядро звертається до віддаленої пам’яті.
  • Зниження пропускної здатності, коли кеші та міжз’єднання насичуються.
  • Графіки з «але CPU не завантажений», бо ядра застопорені, а не заплановані.

Для стеків зберігання та мережі NUMA взаємодіє з перериваннями, DMA і розміщенням черг. NIC на сокеті 0, що доставляє переривання ядрам на сокеті 1 — це регрес продуктивності, який не виправиш оптимізмом.

Сокети як корпоративна стратегія (так, серйозно)

У корпоративному середовищі сокети також є:

  • налаштуванням ліцензування (ліцензії за сокет підштовхують до меншої кількості більших сокетів; ліцензії за ядро змінюють інцентиви).
  • операційними важелями (менше сокетів спрощує планування потужностей і зменшує «випадкові» варіації продуктивності через NUMA).
  • важелем ризику (зрілість платформи, стабільність прошивки та ланцюг постачання запасних частин).

Коли ви стандартизуєте платформу, ви берете на себе її особливості: значення BIOS за замовчуванням, експозицію NUMA, карту PCIe і графік оновлення прошивки. Це зобов’язання триває довше за одне покоління CPU.

Режими відмов: як платформи створюють «загадкову повільність»

1) Брехня «CPU простоює»: застряглі ядра та приховані очікування

Використання CPU вимірює запланований час, а не корисний прогрес. Ядро може бути «зайняте» або «простоювати», тоді як ваше навантаження чекає на пам’ять, I/O, блокування або віддалений доступ NUMA. Платформи впливають на ці очікування:

  • Віддалений доступ до пам’яті збільшує latency та накладні витрати на кохерентність.
  • Недостатня пропускна здатність пам’яті створює затримки на багатьох ядрах одночасно.
  • Змагання на PCIe збільшує затримки завершення I/O і підвищує хвостові затримки.

2) Пристрої I/O конкурують за той самий корінь (root complex)

Якщо ваш NIC і NVMe-пристрої підключені через один PCIe-комутатор uplink, вони ділять пропускну здатність і можуть конкурувати в чергах завершень. Це помітно, коли трафікові шаблони збігаються: великі реплікаційні сплески плюс інтенсивні локальні NVMe-читання; вікна бекапів плюс піки інжесту; вузол Kubernetes, що виконує все одночасно, бо «там є ядра».

3) Шторми переривань на неправильних ядрах

Мережа і NVMe залежать від переривань і/або polling. Якщо переривання потрапляють на невелику групу ядер або, ще гірше, на ядра далеко від NUMA-вузла пристрою, ви отримуєте:

  • Високу активність softirq або ksoftirqd.
  • Втрати пакетів і повторні передачі під навантаженням.
  • Збільшену затримку при «відсутності очевидної загрузки CPU».

4) Збої масштабування на двосокетних системах, які виглядають як баги в додатках

Деякі навантаження добре масштабуються від 1 до N ядер в межах сокета, але потім вичерпуються при роботі через сокети. Симптоми:

  • Пропускна здатність плато приблизно на рівні «одного сокета».
  • Хвостова затримка погіршується зі збільшенням кількості потоків.
  • Метрики блокувань зростають, але справжньою причиною є відскакування кеш-рядків між сокетами.

5) Оновлення пам’яті, що потихеньку знижують продуктивність

Додавання DIMM може змусити систему працювати на нижчій швидкості або у іншому режимі інтерліву. Це не теорія; це поширена регресія в продакшні. Оновлення пам’яті слід розглядати як зміну продуктивності, а не просто як зміну ємності.

6) «Та ж модель CPU» не означає «така сама платформа»

Різні моделі серверів по-різному маршрутизують PCIe, постачаються з різними BIOS-значеннями і показують різну поведінку NUMA. Якщо ви вважаєте, що можна перемістити навантаження між «еквівалентними» серверами і отримати ідентичну продуктивність, ви дуже швидко дізнаєтесь про топологію у найгірший можливий час.

Швидкий план діагностики (перше/друге/третє)

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

Перше: вирішіть, чи ви bound по обчисленнях, пам’яті чи I/O

  • Перевірте load average проти runnable tasks, CPU steal і iowait.
  • Перевірте проксі-навантаження пропускної здатності пам’яті (cache misses, stalls) і підкачку.
  • Перевірте затримки сховища і глибини черг; перевірте втрати і повторні передачі в мережі.

Друге: промапте топологію (NUMA + PCIe) і перевірте, чи вона відповідає розміщенню вашого навантаження

  • Визначте NUMA-вузли та списки CPU.
  • Прив’яжіть NIC і NVMe-пристрої до NUMA-вузлів.
  • Перевірте, куди потрапляють переривання і де працюють ваші процеси.

Третє: підтвердіть, що платформа вас не тротлить

  • Перевірте поведінку частоти CPU під навантаженням.
  • Перевірте power caps, теплове тротлінг і налаштування прошивки (C-states, P-states, turbo limits).
  • Підтвердіть, що пам’ять працює на очікуваній швидкості і в очікуваній конфігурації каналів.

Якщо пройти ці три етапи, зазвичай ви знаходите вузьке місце за менше ніж 30 хвилин. Якщо їх пропустити, можна витратити три дні на «оптимізацію» неправильного шару.

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

Ось запускаємі Linux-завдання, які я використовую для діагностики вузьких місць платформи. Кожне містить: команду, приклади виводу, що це означає, і рішення, яке це дає.

Завдання 1: Визначити сокети, NUMA-вузли та топологію ядер

cr0x@server:~$ lscpu
Architecture:                         x86_64
CPU(s):                               64
Thread(s) per core:                   2
Core(s) per socket:                   16
Socket(s):                            2
NUMA node(s):                         2
NUMA node0 CPU(s):                    0-15,32-47
NUMA node1 CPU(s):                    16-31,48-63

Що це означає: Двосокетна система, два NUMA-вузли. CPU розділено; гіпертредінг інтерлює ядра.

Рішення: Якщо чутливість до затримки важлива — розгляньте пінування навантаження в межах одного NUMA-вузла або забезпечте, щоб алокації пам’яті слідували за розміщенням CPU.

Завдання 2: Перевірити розподіл пам’яті по NUMA і чи голодує якийсь вузол

cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
node 0 size: 257540 MB
node 0 free: 11844 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
node 1 size: 257676 MB
node 1 free: 182990 MB

Що це означає: Вузол 0 майже заповнений, а вузол 1 — здебільшого вільний. Класичний сценарій віддалених алокацій пам’яті і хвостової затримки.

Рішення: Прикріпіть навантаження до вузла 1, перебалансуйте сервіси або встановіть політику пам’яті (наприклад bind/membind/interleave) залежно від характеру навантаження.

Завдання 3: Перевірити, чи ядро проводить час у очікуванні I/O

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
 2  0      0 892312  91340 8123432   0    0   120   340 4200 9800 18  4 76  2  0
 3  1      0 882104  91340 8126120   0    0  2140  1210 6100 12000 16  5 60 19  0
 2  1      0 879220  91340 8127001   0    0  1980  1400 5900 11700 15  5 62 18  0

Що це означає: «wa» (iowait) підскакує до ~19%, і з’являються заблоковані процеси («b»). Ймовірно, шлях сховища або його насичення.

Рішення: Перейдіть до перевірки латентності по пристрою (iostat), глибин черг і мапінгу NVMe/NIC.

Завдання 4: Визначити затримки сховища і тиск на черги

cr0x@server:~$ iostat -x 1 3
Device            r/s   w/s   rkB/s   wkB/s  await  svctm  %util
nvme0n1         4200  1100  82000   34000   3.20   0.18  92.5
nvme1n1         4100  1200  80000   36000   3.40   0.19  94.1

Що це означає: Висока завантаженість і зростаючий await вказують на підхід до насичення. Низький svctm натякає, що пристрій в порядку; проблема — черги.

Рішення: Зменшити паралелізм, розподілити I/O по пристроях, перевірити, чи обидва пристрої ділять PCIe-uplink, або перемістити навантаження на додаткові диски/контролери.

Завдання 5: Підтвердити NVMe-пристрої і їх ширину/швидкість лінка PCIe

cr0x@server:~$ sudo nvme list
Node             SN                   Model                                    Namespace Usage                      Format           FW Rev
/dev/nvme0n1     S6X...               Samsung SSD 980 PRO 2TB                  1         2.00  TB /   2.00  TB      512   B +  0 B   5B2QGXA7
/dev/nvme1n1     S6Y...               Samsung SSD 980 PRO 2TB                  1         2.00  TB /   2.00  TB      512   B +  0 B   5B2QGXA7
cr0x@server:~$ sudo lspci -s 5e:00.0 -vv | egrep -i "LnkCap|LnkSta"
LnkCap: Port #0, Speed 16GT/s, Width x4
LnkSta: Speed 8GT/s, Width x4

Що це означає: Пристрій підтримує PCIe Gen4 (16GT/s), але працює на Gen3 (8GT/s). Це питання платформи/прошивки/слота, а не «повільного NVMe».

Рішення: Перевірити налаштування PCIe покоління в BIOS, сумісність riser, проводку слота і чи не примушує комутатор/backplane працювати в Gen3.

Завдання 6: Замапити PCIe-пристрої на NUMA-вузли

cr0x@server:~$ for d in /sys/bus/pci/devices/*; do \
  dev=$(basename "$d"); \
  numa=$(cat "$d/numa_node" 2>/dev/null); \
  class=$(cat "$d/class" 2>/dev/null); \
  if [ "$numa" != "-1" ]; then echo "$dev numa=$numa class=$class"; fi; \
done | head
0000:3b:00.0 numa=0 class=0x010802
0000:3c:00.0 numa=0 class=0x020000
0000:af:00.0 numa=1 class=0x010802
0000:b0:00.0 numa=1 class=0x020000

Що це означає: NVMe і NIC приєднані до обох NUMA-вузлів. Добре — якщо ви розміщуєте навантаження відповідно.

Рішення: Розміщуйте сервіси з інтенсивним зберіганням поруч зі своїми NVMe, а мережеві сервіси — поруч з NIC, або тримайте хост «одно-NUMA» для затратно-чутливих додатків.

Завдання 7: Перевірити, де працює ваш процес (CPU affinity)

cr0x@server:~$ ps -o pid,psr,comm -p 21488
  PID PSR COMMAND
21488  52 postgres

Що це означає: Процес наразі на CPU 52, який (з lscpu) належить до NUMA-вузла 1.

Рішення: Переконайтеся, що його алокації пам’яті та I/O-пристрої також на вузлі 1. Якщо ні — прив’яжіть його або перемістіть пристрої/IRQ.

Завдання 8: Перевірити розміщення пам’яті процесу по NUMA

cr0x@server:~$ sudo numastat -p 21488
Per-node process memory usage (in MBs) for PID 21488 (postgres)
Node 0          18240.50
Node 1           2201.75
Total           20442.25

Що це означає: Процес працює на вузлі 1, але більшість пам’яті знаходиться на вузлі 0. Це віддалений доступ до пам’яті і штраф за затримку.

Рішення: Перезапустіть з правильною NUMA-політикою (bind CPU + memory), відрегулюйте розміщення сервісу або використайте інтерлів для навантажень на пропускну здатність.

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

cr0x@server:~$ cat /proc/interrupts | egrep "nvme|mlx|eth" | head
  142:  1982341   10234      0      0  IR-PCI-MSI 524288-edge  nvme0q0
  143:  2059933   10111      0      0  IR-PCI-MSI 524289-edge  nvme0q1
  192:   982341  110993  809221  774112 IR-PCI-MSI 1048576-edge  mlx5_comp0
  193:   100112  989231  802331  790002 IR-PCI-MSI 1048577-edge  mlx5_comp1

Що це означає: Черги NVMe б’ють переважно по групах CPU0/CPU1 (перші колонки). Завершення NIC розподілені рівномірніше.

Рішення: Налаштуйте афінність IRQ для NVMe/NIC-черг, щоб розподілити навантаження і вирівняти їх по NUMA. Якщо IRQ скупчуються на кількох CPU, отримаєте softirq-змагання і хвилі затримок.

Завдання 10: Підтвердити поведінку частоти CPU і тротлінг

cr0x@server:~$ sudo turbostat --Summary --quiet --show Busy%,Bzy_MHz,TSC_MHz,PkgTmp,PkgWatt -i 2 -n 2
Busy%   Bzy_MHz  TSC_MHz  PkgTmp  PkgWatt
42.31   2498     2500     86      205.4
44.02   2299     2500     89      205.0

Що це означає: Busy frequency падає при зростанні температури пакета. Можливі обмеження по потужності/терміці, що виглядає як «CPU став повільнішим».

Рішення: Перевірте power caps, охолодження, профіль живлення BIOS і стійкі ліміти boost. Не «оптимізуйте код», поки платформа не стабільна.

Завдання 11: Виявити втрати мережі і повторні передачі (платформа може бути причиною)

cr0x@server:~$ ip -s link show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    RX:  bytes  packets  errors  dropped  missed  mcast
    9812331123  9923123  0       18422    0       223
    TX:  bytes  packets  errors  dropped  carrier collsns
    8123341123  8123311  0       0        0       0

Що це означає: RX drops. Це може бути перевантаження кільця/черги, проблеми афінності IRQ/CPU або contention на PCIe — не тільки «мережа».

Рішення: Перевірте RSS/кількість черг, афінність IRQ, статистику драйвера NIC і чи не ділить NIC PCIe-пропуск з важким NVMe.

Завдання 12: Підтвердити кількість черг NIC і розподіл RSS

cr0x@server:~$ ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:             32
TX:             32
Other:          0
Combined:       0
Current hardware settings:
RX:             8
TX:             8
Other:          0
Combined:       0

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

Рішення: Збільшити кількість черг (обережно), потім вирівняти IRQ до локальних NUMA-CPU. Більше черг без афінності може бути шкідливим.

Завдання 13: Перевірити налаштування черги блочного шару (NVMe)

cr0x@server:~$ cat /sys/block/nvme0n1/queue/nr_requests
128

Що це означає: Глибина блочної черги може обмежувати паралелізм для завдань на пропускну здатність — або навмисно мала для затратно-чутливих навантажень.

Рішення: Для пакетної обробки розгляньте збільшення. Для чутливих до затримки робочих навантажень залишайте обережну налаштування і спочатку виправляйте топологію.

Завдання 14: Визначити, чи відбувається підкачка або агресивний reclaim

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           503Gi       412Gi        11Gi       1.2Gi        80Gi        63Gi
Swap:           16Gi       2.0Gi        14Gi

Що це означає: Є використання swap. Не завжди катастрофа, але для чутливих до затримки навантажень це червоний прапорець; також це взаємодіє з NUMA-небалансом.

Рішення: Визначити сервіс, що споживає пам’ять, виправити витоки, обмежити кеші або перемістити навантаження. Підкачка часто — питання розміру платформи, а не просто налаштування.

Завдання 15: Перевірити натяки на між-NUMA трафік через scheduler domains

cr0x@server:~$ cat /proc/sys/kernel/numa_balancing
1

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

Рішення: Для критичних до затримки систем з явним пінуванням розгляньте відключення і власне керування розміщенням. Для змішаних навантажень можна залишити ввімкненим і вимірювати.

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

Міні-історія 1: Інцидент через неправильне припущення

Команда мігрувала високонавантажений API-шар з старішої двосокетної платформи на «новішу, швидшу» двосокетну платформу. Та сама сім’я CPU, вищі частоти, більше ядер. Все виглядало як чиста перемога. Навантажувальні тести пройшли. Вікно змін було спокійним. А потім настала понеділкова ранкова зміна.

Хвостова затримка стрибнула. Не середня — лише 99-й перцентиль. Залежності API загорілися: таймаути до Redis, спорадичні затримки бази даних і періодичні втрати пакетів на балансувальнику. Використання CPU ніколи не перевищувало 50%, що зробило всіх підозрілими щодо шару додатку. Люди почали звинувачувати «недавнє деплоймент», яке ніяк не було причетне.

Неправильне припущення було тонким: «двосокетний — значить двосокетний». На нових серверах NIC і NVMe-пристрій були на сокеті 0, але контейнерний рантайм запустив найактивніші поди по обидва сокети. Переривання оброблялися переважно CPU на сокеті 0, тоді як значна частина мережевої обробки була на сокеті 1. Пакети переходили сокети, алокації пам’яті відскакували, і невелике змагання перетворилося на фабрику хвостової затримки.

Коли вони закріпили мережево-активні поди до NUMA-вузла NIC, вирівняли афінність IRQ і припинили «розмазування» гарячих потоків планувальником по сокетах, проблема зникла. Обладнання не було повільнішим. Платформа була іншою, і система платила податок топології за кожен запит.

Урок: ніколи не сприймайте «однакові сокети й ядра» як «однакова продуктивність». Ставте мапінг платформи як вимогу до розгортання, як файрвол чи TLS-сертифікати.

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

Команда зберігання захотіла більше пропускної здатності від NVMe-ноду з навантаженням пошуку. Хтось помітив, що CPU використано помірно, і вирішив, що система «недовикористана». План: підвищити паралелізм. Збільшити глибини I/O-черг, підняти кількість робітників додатку і підняти кількість черг NIC «під кількість ядер».

В бенчмарку це спрацювало. Завжди так і буває. За синтетичним стабільним навантаженням пропуск зріс.

А потім з’явився продакшн: сплески, змішані шаблони читань/записів, промахи кешу і періодичні фонві завдання по компресії. Хвостова затримка подвоїлася. Платформа потрапила в режим, де переривання і завершення конкурували за кеш і пропускну здатність пам’яті. Більші глибини черг посилили чергування, перетворивши мікроспеки на видимі користувачеві затримки.

Підступна частина була в обсервабельності. Середня затримка не виглядала критичною. CPU все ще не був під завантаженням. Але шлях завершення став хаотичним: більше черг — більше переривань, більше відскакувань кеш-рядків і більше між-NUMA трафіку, бо додаткові робітники не були закріплені. «Оптимізація» збільшила contention більше, ніж корисну роботу.

Виправлення не вимагало повного відмотування. Вони залишили помірний ріст паралелізму, а потім зробили нудну роботу: вирівняли черги з локальними NUMA-ядрами, обмежили глибини черг, щоб захистити затримку, і винесли компактацію на виділений набір CPU. Пропуск залишився хорошим. Хвостова затримка перестала лякати людей на викликах.

Урок: більше паралелізму — не те саме, що краща продуктивність. На сучасних платформах паралелізм може бути власною DDoS-атакою на ієрархію пам’яті.

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

Група інфраструктури стандартизувала серверну платформу для свого флоту баз даних. І не тільки модель CPU — SKU платформи, налаштування BIOS, версії прошивки, схема заповнення DIMM, використання слотів PCIe і документована мапа NIC/NVMe до NUMA-вузлів. Це було настільки нудно, що майже церемоніально.

Через шість місяців постачальник привіз партію запасних материнських плат під час дефіциту. Замінники були «еквівалентні», але мали інші значення BIOS за замовчуванням і дещо інше маршрутування PCIe. У кількох хостів почалися інтермітентні затримки реплікації і випадкові піки часу запису.

Оскільки команда мала базову конфігурацію платформи, вони швидко помітили проблему. Порівняли проблемні хости з еталоном: розміщення пристроїв по NUMA, швидкість PCIe-лінків, розподіл переривань і профіль живлення BIOS. Різниці були очевидні. Вони виправили налаштування BIOS, перемістили NIC у призначений слот і знову застосували політику афінності IRQ. Проблему вирішили до того, як вона стала інцидентом.

Практика, що їх врятувала, не була магією. Це було ставлення конфігурації платформи як коду: еталон, diff і відновлюваний відомий-гарний стан. Більшість команд цього не роблять, бо це не гламурно. Більшість команд також більше часу проводять у вогні.

Урок: Стандартизація здається повільною, поки вона не знадобиться. Тоді вона швидша за героїчну рятувальну роботу.

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

1) Симптом: CPU < 50%, але затримка жахлива

Корінь: Затримки пам’яті, віддалений доступ NUMA або чергування I/O. Використання CPU не показує застряглі цикли.

Виправлення: Перевірте NUMA-розміщення пам’яті (numastat), латентність сховища (iostat -x) і розподіл переривань. Закріпіть гарячі сервіси до NUMA-вузла і вирівняйте пристрої.

2) Симптом: продуктивність погіршилася після додавання RAM

Корінь: Заповнення DIMM спричинило нижчу швидкість пам’яті або незбалансовані канали; змінилася латентність/пропускна здатність пам’яті.

Виправлення: Підтвердіть швидкість пам’яті в BIOS/прошивці, забезпечте збалансоване заповнення каналів, уникайте змішаних типів DIMM. Розглядайте апгрейди пам’яті як зміни продуктивності і тестуйте знову.

3) Симптом: пропускна здатність NVMe нижча за очікування для «Gen4» дисків

Корінь: Лінк тренувався на Gen3 або x2; неправильний слот, riser або обмеження backplane.

Виправлення: Підтвердіть статус з lspci -vv; відрегулюйте налаштування PCIe в BIOS; перемістіть пристрій у слот, що підключений до CPU.

4) Симптом: мережеві втрати під час інтенсивних операцій зі сховищем

Корінь: NIC і NVMe ділять PCIe-uplink/root complex; трафік завершень конкурує; переривання потрапляють на перевантажені ядра.

Виправлення: Замапте топологію PCIe, перемістіть один пристрій на інший сокет/root complex якщо можливо; налаштуйте афінність IRQ; збільшуйте черги тільки після правильного розміщення.

5) Симптом: двосокетний сервер повільніший за одно-сокетний для того самого сервісу

Корінь: Міжсокетний доступ до пам’яті і відскакування кеш-рядків; планувальник розмазує потоки; домінують віддалені алокації.

Виправлення: Обмежте сервіс одним сокетом; виділіть локальну пам’ять; відокремте «шумних сусідів»; перегляньте, чи потрібен вам взагалі двосокет.

6) Симптом: «шумні сусіди» мікросервісів попри достаток ядер

Корінь: Спільні ресурси платформи: contention в LLC, насичення пропускної пам’яті, спільні PCIe-uplink, тиск переривань.

Виправлення: Використовуйте CPU sets і NUMA-усвідомлене розміщення; резервуйте хости для пам’яті-інтенсивних робіт; розміщуйте I/O-важкі поди на хостах з чистою картою PCIe.

7) Симптом: бенчмарки чудові; хвостова затримка в продакшні погана

Корінь: Бенчмарки — це стабільний режим; продакшн — сплески. Чергування + переривання + GC/компактація підсилюють сплески.

Виправлення: Тестуйте зі сплесками, обмежуйте глибини черг, ізолюйте фоні роботи і віддавайте перевагу передбачуваному розміщенню над максимумом паралелізму.

Жарт 2/2: Якщо ваш план — «додати тредів, поки не стане швидко», вітаю — ви винайшли thundering herd, тепер з PCIe.

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

Чекліст вибору платформи (перед покупкою або стандартизацією)

  1. Визначте очікуваний вузький момент: пропускна пам’яті, pps мережі, затримка сховища, пропуск GPU, або змішане навантаження.
  2. Обирайте кількість сокетів навмисно: одно-сокет для передбачуваної затримки; двосокет — коли вам дійсно потрібні додаткові пам’ять/I/O і ваше ПЗ NUMA-усвідомлене.
  3. Підтвердіть потреби каналів пам’яті: потрібна пропускна здатність, правила заповнення DIMM і очікувана швидкість при повному заповненні.
  4. Рахуйте лінії PCIe як гроші: NIC, NVMe, GPU/DPU, HBA; припускайте, що зрештою ви використаєте кожну лінію, за яку заплатили.
  5. Попросіть карту слотів PCIe: які слоти приєднані до якого CPU/root complex; куди йдуть uplink backplane.
  6. Плануйте переривання і черги: достатньо ядер біля NIC/NVMe; уникайте примушення всього I/O на один сокет.
  7. Врахуйте вплив ліцензування: зміни per-socket/per-core можуть змінити «оптимальний» вибір сокета.
  8. Стандартизуйте BIOS і прошивку: профіль живлення, C-states, покоління PCIe, SR-IOV і експозиція NUMA.

Чекліст розгортання (перед тим як навантаження потрапить на хост)

  1. Зафіксуйте виводи lscpu і numactl --hardware як базове становище хоста.
  2. Замапте NIC/NVMe NUMA-вузли через /sys/bus/pci/devices/*/numa_node.
  3. Підтвердіть ширини і швидкості PCIe для критичних пристроїв.
  4. Встановіть політику афінності IRQ (або переконайтеся, що дефолти дистрибутива відповідають вашому задуму).
  5. Вирішіть розміщення: один NUMA-вузол на сервіс (затримка) проти інтерліву (пропускна здатність).
  6. Протренуйте з навантаженням зі сплесками і вмикайте фонві задачі (компактації, бекапи).

Чекліст інциденту (що робити під тиском)

  1. Перевірте, чи вузьке місце — I/O, пам’ять чи тротлінг частоти CPU, перш ніж чіпати конфіг додатка.
  2. Підтвердіть, чи хвостова затримка корелює з NUMA-небалансом, втратами або чергуванням сховища.
  3. Якщо multi-socket: тимчасово обмежте навантаження одним сокетом як міра пом’якшення (не остаточне рішення).
  4. Зменшіть паралелізм, якщо проблема — чергування; не «додавайте тредів» у шторм.
  5. Зробіть знімки топології і розподілу IRQ до і після змін, щоб уникнути плацебо-виправлень.

FAQ

1) Чи зазвичай краще одно-сокетні сервери зараз?

Для багатьох затратно-чутливих сервісів — так: простіша NUMA, менше міжсокетних сюрпризів і зазвичай вистачає ядер. Двосокетні чудові, коли вам справді потрібно більше пам’яті, пропускної здатності або I/O-ліній і ваше ПЗ коректно розміщене.

2) Якщо використання CPU низьке, чому мій сервіс повільний?

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

3) Який найшвидший спосіб виявити проблему NUMA?

Порівняйте розміщення CPU і пам’яті для процесу. Якщо процес працює на вузлі 1, а більшість його пам’яті на вузлі 0 — ви, ймовірно, знайшли джерело хвостової затримки. Використовуйте ps разом з numastat -p.

4) Чи варто відключати автоматичне балансування NUMA?

Іноді. Якщо ви явно піните CPU і пам’ять для передбачуваної затримки, автоматичне балансування може працювати проти вас, мігруючи сторінки. Для змішаних навантажень або загально-призначених серверів воно може допомогти. Вимірюйте; не робіть за звичкою.

5) Більше черг NIC завжди покращує продуктивність, правда?

Ні. Більше черг може підвищити кількість переривань і хаос у кеші, і може розподілити роботу по сокетах, якщо не керувати афінністю. Збільшуйте черги тільки після підтвердження коректної афінності IRQ і розміщення CPU.

6) Як зрозуміти, чи NVMe електрично обмежено платформою?

Перевірте ширину і швидкість PCIe-лінка через lspci -vv. Якщо LnkCap показує Gen4 x4, але LnkSta — Gen3 або вужче, ви втрачаєте продуктивність через обмеження слота/backplane/BIOS.

7) Чому бенчмарки виглядають добре, а в продакшні — погано?

Бенчмарки контрольовані. Продакшн має сплески, змішані роботи, фонові завдання, GC і «шумних сусідів». Вони підсилюють чергування і топологічні помилки. Завжди тестуйте зі сплесками і реальними фоновими задачами.

8) Чи завжди двосокетне гірше для баз даних?

Ні. Бази даних можуть добре масштабуватися на двосокетних машинах, якщо налаштовані з урахуванням NUMA, правильно розміщено пам’ять і локальний I/O. Небезпечний режим — «залишити все за замовчуванням», коли потоки, пам’ять і переривання вільно переміщаються.

9) Як сокети пов’язані зі схемою зберігання конкретно?

Шляхи зберігання використовують DMA і черги завершень. Якщо NVMe-пристрої на одному сокеті, а потоки зберігання — на іншому, ви платите за віддалені пам’яті і переходи шини на кожній I/O-операції. Вирівняйте стек: пристрій, IRQ і потоки на одному NUMA-вузлі.

10) Яка одна платформична звичка зменшує інциденти?

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

Наступні кроки, які можна зробити цього тижня

Якщо хочете менше загадкових проблем з продуктивністю і менше дискусій о 2 ранку про графіки CPU, зробіть це по порядку:

  1. Зробіть інвентаризацію топології по всьому флоту: сокети, NUMA-вузли і локальність пристроїв. Заносьте це до картки хоста.
  2. Визначте політику розміщення за замовчуванням: одно-NUMA для рівнів з низькою затримкою; інтерлів для шарів з високою пропускною здатністю. Нехай це буде навмисно, а не випадково.
  3. Стандартизуйте налаштування BIOS/прошивки для профілів живлення і покоління PCIe. «Значення заводу» — не стратегія надійності.
  4. Створіть рукопис інцидентів з швидким планом діагностики вище. Покладіть туди команди. Нехай він буде виконуваним у стресі.
  5. Проведіть один контрольований експеримент: прив’яжіть критичний сервіс до одного сокета і виміряйте хвостову затримку. Якщо стало краще — ви отримали практичну дію щодо вашої платформи.

Головна думка не в тому, що «CPU не мають значення». Вони мають. Але зараз виграшний хід — сприймати сокет і платформу навколо нього як одиницю стратегії. Купуйте топологію навмисно. Керуйте нею, як маєте на увазі.

← Попередня
Ubuntu 24.04: коли GRO/LRO/TSO оффлоуди ламають систему — як протестувати і безпечно відключити
Наступна →
MySQL vs SQLite: випадок «безкоштовної швидкості» — коли файлова БД перемагає сервер

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