Налаштування NUMA Awareness для багатопроцесорних серверів

Сервер із двома процесорами не видає й половини можливого, і причина зазвичай у тому, як пам'ять розподіляється між вузлами. Ми налаштовуємо NUMA awareness під ключ: від схеми топології до регламенту для вашої команди. Час відгуку вирівнюється, а частину задач знімаємо без заміни обладнання.
Часті запитання

NUMA awareness: чому сервер втрачає продуктивність

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

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

Підступність у тому, що операційна система за замовчуванням розподіляє пам'ять «за справедливістю»: вузол сторінки визначається першим зверненням, а планувальник має право перенести процес на інше ядро.

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

Шукають причину в коді застосунку, у диску або в мережі, хоча винуватець — топологія пам'яті, про яку заздалегідь ніхто не домовлявся.

NUMA awareness — це узгоджене налаштування планувальника, засобів віртуалізації та самого застосунку, за якого обчислення тримаються поруч зі своїми даними.

Потоки закріплюються за вузлами, пам'ять виділяється на потрібному вузлі, а міжпроцесорний трафік стає усвідомленим винятком, а не постійним фоном.

Окрема частина роботи — міграція сторінок: автоматичне вирівнювання іноді допомагає, а іноді нескінченно ганяє одні й ті самі сторінки між вузлами, з'їдаючи саме той ресурс, який намагалися заощадити.

Далі розбираємо, як ми знімаємо реальну топологію конкретного сервера і що змінюємо в конфігурації, щоб відгук перестав залежати від того, на який вузол потрапив потік.

Ефект від налаштування розміщення пам'яті на багатопроцесорному сервері

Ефект від налаштування видно не за середнім часом відгуку, а за його розкидом і за поведінкою сервісу на піку.

Поки процеси та їхні сторінки пам'яті розподілені по всіх вузлах, частина запитів іде на віддалений контролер пам'яті — середнє залишається прийнятним, тому проблему довго не помічають, а хвіст розподілу видає її відразу. Нижче — три зрізи, за якими ми вимірюємо результат.

Розкид часу відгуку

Поки застосунок отримує пам'ять з будь-якого вузла, затримка запиту залежить від того, куди його поклав планувальник, — звідси рваний хвіст розподілу і 95-й перцентиль, який у рази вищий за медіану.

Прив'язка процесів до вузлів і політика виділення локальних сторінок стискають розкид: запити, які обслуговувалися в 3–5 разів довше за інші, укладаються у вузький коридор, а сусідні заміри перестають розходитися.

Стабільність і рівномірність потоків під навантаженням

Потоки розкладаються по ядрах усередині вузла, і кожен працює зі своєю локальною пам'яттю — конкуренція за міжсокетну шину перестає бути вузьким місцем.

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

Усунений клас відмов

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

Частка таких відмов падає до нуля, а топологію та політики ми фіксуємо в конфігурації — рекомендації виробника платформи (Intel) щодо розміщення пам'яті по вузлах переносимо в шаблони, щоб передбачуваність відгуку зберігалася при зростанні навантаження і додаванні нових служб.

Підходи до NUMA awareness: порівняння режимів і обмежень

На багатопроцесорному сервері звернення до пам'яті свого сокета дешевше, ніж до пам'яті сусіднього, і різниця проявляється на хвостових затримках, а не на середньому відгуку. Хто ділить сторінки між вузлами — ядро з автоматичним розміщенням чи ви вручну — визначається політикою. Реальних варіантів чотири, від повної відмови від налаштування до жорсткої прив'язки.

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

Варіант Коли підходить Обмеження
Без налаштування, автоматичне розміщення Стенди та сервіси без вимог до хвоста затримок Балансувальник переносить сторінки під навантаженням; відгук плаває тим сильніше, чим більше потоків
М'яка перевага вузла Великий спільний робочий набір, потоки розподілені по всіх сокетах Локальність не гарантована: частина звернень усе одно йде на віддалений вузол
Прив'язка процесів до вузлів СУБД, брокери, кеші з рівним відгуком Потрібні заміри топології та супровід; при перекосі потоків частина ядер простоює
Жорстка прив'язка процесу і пам'яті Виділений сервер під один сервіс, чутливий до затримок Немає запасного шляху: вузол заповнений — розміщення відхиляється, перенесення на інше залізо ламає конфігурацію

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

Порядок впровадження: від схеми топології до передачі регламенту

Впровадження йде поетапно: кожна зміна підтверджується заміром і залишається оборотною. Починаємо з аудиту топології — без карти вузлів, ядер і прив'язки пристроїв введення-виведення будь-яка правка перетворюється на вгадування.

Далі переносимо висновки в схему розміщення, налаштовуємо, перевіряємо під навантаженням і передаємо регламент експлуатації.

  1. Аудит топології. Збираємо карту вузлів і ядер, зв'язків між ними, прив'язки мережевих карт і дискових контролерів. Фіксуємо базові заміри: затримку доступу до локальної та віддаленої пам'яті, розподіл потоків по вузлах.

  2. Схема розміщення. Визначаємо, які сервіси прив'язуємо до вузлів жорстко, які отримують лише перевагу, а які залишаємо вільними. Окремо проговорюємо, де потрібна ізоляція — інакше м'яка прив'язка непомітно стане жорсткою.

  3. Налаштування. Переносимо схему в конфігурацію: правила прив'язки процесів і обробки переривань, розподіл пам'яті, параметри планувальника. Кожна зміна — окремим кроком із точкою відкату.

  4. Навантажувальна перевірка. Ганяємо профіль, близький до робочого: змішані запити, пікові сплески, тривалий фон. Дивимося не лише на середню пропускну здатність, а й на хвостові затримки.

  5. Розбір відхилень. Звіряємося з базовими замірами і пояснюємо кожну розбіжність: де спрацювала локальність, де прив'язка звузила ресурси. Спірні правила прибираємо, а не залишаємо «на всяк випадок».

  6. Регламент експлуатації. Передаємо карту вузлів, чинні правила прив'язки, список того, що не можна змінювати без повторного заміру, і порядок перевірки після оновлення ядра або заміни заліза.

Що замовник отримує на руки: схеми, профілі, регламенти

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

  • Карта топології. Схема розміщення вузлів пам'яті та ядер: видно, які ядра ходять за пам'яттю в сусідній вузол і де втрачається пропускна здатність. За нею планується посадка нових сервісів без повторних замірів.
  • Профілі прив'язки. Готові набори для бази даних, кешу і балансувальника: які процеси закріплені за якими ядрами і за якої кількості потоків. Профіль застосовується шаблоном, а не збирається заново.
  • Політики доступу. Хто має право змінювати прив'язку, які правки потребують прогону на стенді і що заборонено чіпати на бойовому сервері. Закриває відмови виду «поправили ядра руками».
  • Документація по серверу. Паспорт вузла: стан до і після налаштування, реперні заміри по пам'яті та затримках, зв'язок конфігурації зі складом заліза і режимами системи.
  • Регламент обслуговування. Порядок планових робіт: як зберегти і відновити профілі, що перевіряти після заміни заліза, порядок приймання нового сервера в той самий профіль.
  • Скрипти перевірки. Автотест, що прив'язка застосувалася і тримається після перезапуску сервісів, зі звітом на одну сторінку для чергового інженера.

Кейс: як прибрали деградацію відгуку на серверах з двома процесорами

Клієнт — сервіс, де на кожному сервері стоїть по два процесори, а база працює під управлінням PostgreSQL.

На піковому навантаженні окремі запити стали відповідати в рази довше за інші: процеси ходили за даними в пам'ять сусіднього процесора, і черга на обслуговування зростала швидше, ніж саме навантаження.

Що показали заміри

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

При виході на пік сервіс не сповільнювався рівномірно, а починав скидати частину запитів через перевищення часу очікування — тобто деградація відгуку виглядала як обрив, а не як плавне зниження.

Що змінили і що отримали

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

Частка віддалених звернень упала до 3-4%, розкид часу відгуку скоротився приблизно в шість разів, а пікова пропускна здатність зросла майже вдвічі — без заміни обладнання.

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

Що робити, якщо сервер уже налаштований вручну і без документації?

Успадкована конфігурація майже завжди багатошарова: частина прив'язок задана параметрами завантаження ядра, частина — в юнітах systemd, частина — командами, які колись виконали вручну і не зафіксували.

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

Зріз робиться до будь-яких правок і зберігається як еталон для порівняння: відомості про топологію і поточні ваги вузлів, список прив'язок по процесах, налаштування груп процесорів, параметри завантаження ядра.

Поруч — метрики локальності: частка звернень до «чужого» вузла і час виконання задач, які в локальну пам'ять не потрапляють. Без цього еталона потім неможливо довести, що стало краще, а не просто інакше.

Окремо розбираємо, що саме компенсувала ручна настройка. Часто вона лікувала симптом — перекіс переривань на один вузол або невдалий вибір вузла планувальником — і при знятті прив'язок дефект повертається.

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

Зворотна сумісність тримається на порядку робіт: змінюємо по одному шару, починаючи з найменш пов'язаного з навантаженням, і зберігаємо готовий відкат до знятого зрізу.

Першим іде пілотний вузол, де порівнюються ті самі метрики локальності та затримки під контрольним навантаженням. Лише після стійкого результату правило переноситься на інші машини.

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

Технічні сумніви: сумісність, ризики, обмеження

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

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

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

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

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

Кожна зміна оформлюється оборотно: профіль вимикається однією правкою конфігурації, і сервіс повертається до попереднього режиму без перезбирання середовища.

Чи сумісно це з віртуалізацією та контейнерами?

Так, за умови, що топологія не прихована. У контейнерах прив'язка задається на рівні менеджера ресурсів і успадковується процесами; у віртуалках — лише якщо гостю видно окремі вузли.

Якщо топологія прихована, метод не застосовуємо і говоримо про це прямо, а не зображаємо налаштування.

Чи доведеться зупиняти працюючі сервіси?

Не завжди. Зміни політики на рівні ядра підхоплюють нові процеси, і сервіс продовжує працювати, поступово переходячи на нову схему. Перезапуск потрібен там, де управління пам'яттю йде через менеджер ресурсів — такі роботи плануємо у вікно обслуговування.

Як відкочуються зміни, якщо стане гірше?

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

Перед виведенням на весь кластер ми залишаємо еталонні заміри, щоб порівняти поведінку після відкату і переконатися, що повернулися рівно до початкового стану.

Обговоримо задачу: надішліть схему серверів і профіль навантаження

Щоб зрозуміти, що дасть NUMA awareness саме на ваших серверах, не потрібен доступ до робочого контуру — достатньо вихідних даних про залізо і профіль навантаження. Чим повніша картина, тим менше припущень у розрахунку і тим реальніші терміни робіт. Ось що корисно додати до опису задачі.

  • вивід lscpu і numactl --hardware з кожного сервера — топологія вузлів, розкладка пам'яті по каналах, відстані між вузлами;
  • схема розміщення сервісів: що живе в контейнерах, що у віртуальних машинах, де увімкнена міграція і як налаштовані ліміти;
  • профіль навантаження — які процеси чутливі до затримки, як навантаження змінюється по годинах, де піки і як довго тримаються;
  • поточні прив'язки і параметри ядра: unit-файли з CPUAffinity, політики NUMA-балансування, налаштування планувальника;
  • метрики під навантаженням: промахи локальної пам'яті, частка звернень до віддаленого вузла, час відгуку сервісу на піку;
  • опис інцидентів: у який момент відгук зростає, що вже пробували і що дало ефект;
  • вікна обслуговування і вимоги до відкату — чи можна перезавантажувати вузли, чи є гарячий резерв.

Надсилайте те, що є: повний набір не обов'язковий, відсутнє ми перелічимо окремо і підкажемо, як зняти.

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

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    816
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1141