На многопроцессорном сервере память физически распределена между сокетами, и обращение ядра к чужому банку занимает в полтора-два раза больше времени, чем к своему. Linux пытается исправить ситуацию автоматически: подсистема NUMA balancing наблюдает, какие потоки к каким страницам обращаются, и двигает страницы ближе к их потребителям. Идея привлекательна в теории, но механика ценой в самих переносах, подсказках от ошибок страниц и периодическом сканировании делает её нетривиальной. На неудачной нагрузке авто балансировка сама становится источником всплесков, и опытный инженер обязан знать, когда ей доверять, как её наблюдать и чем заменить.

Механика NUMA balancing изнутри

Работа механизма начинается с намеренного вреда: ядро периодически помечает часть адресного пространства как недоступную, вызывая искусственные ошибки страниц при следующих обращениях. По ним оно узнаёт, какой узел читает или пишет страницу прямо сейчас. Накопленная статистика для каждой страницы и каждого потока привязывается к узлам, и когда данные и их потребитель расходятся по разным узлам достаточно долго, страница ставится в очередь на миграцию.

Переезд страницы это дорогое событие. Требуется выделить новую физическую страницу на целевом узле, скопировать содержимое, обновить таблицы трансляции, инвалидировать кэши трансляции на всех причастных ядрах и сохранить корректность в лице конкурентных доступов. Всё это может занимать десятки микросекунд, в течение которых обращения к странице маскируются или ждут. Баланс между ценой переноса и будущей выгодой ядро решает эвристически, и его эвристика не всегда совпадает с интересами конкретного приложения.

Управляющие ручки живут в /proc/sys/kernel. numa_balancing включает или выключает механизм целиком, numa_balancing_scan_delay_ms, numa_balancing_scan_size_mb, numa_balancing_scan_period_min_ms и numa_balancing_scan_period_max_ms управляют агрессивностью сканирования и его периодичностью. Эти диаметры напрямую определяют, как быстро система реагирует на изменения паттернов и сколько процессорного времени уходит на само наблюдение.

Хорошее понимание механики предохраняет от главного заблуждения: NUMA balancing не приносит ничего бесплатно. Он тратит такты на сканирование и копирование, чтобы потом сэкономить такты на удалённых доступах. Вопрос всегда в соотношении этих чисел, и разные нагрузки отвечают на него по разному.

Нагрузки, где автоматика помогает, и где она мешает

Долго живущие приложения со стабильной привязкой между потоками и данными это идеальные клиенты автоматики. Серверная СУБД с ровной загрузкой, аналитический процессор, который часами перемалывает одни структуры получают от балансировки медленный, но неуклонный выигрыш: страницы стягиваются к читателям, и удалённых доступов становится всё меньше. Темп улучшения измеряется минутами, и терпение окупается.

Обратная ситуация с короткими эпизодическими процессами. Контейнерная платформа с высоким оборотом процессов платит за сканирование и переносы, которые не успевают окупиться до завершения задачи. Для таких нагрузок авто механика чаще выключается, а размещение задаётся осознанно при запуске каждого инстанса.

Особый чёрный список это приложения, самостоятельно управляющие привязкой через mbind или mempolicy. Ручные политики и ядерная автоматика при одновременной работе конкурируют, и результат непредсказуем: ядро может перетаскивать страницы, которые приложение явно закрепило дистрибутивно. Правило золотое: либо приложение владеет размещением, либо ядро, но не оба сразу.

Многопоточные вычисления на разделяемых массивах образуют третий случай. Когда сто потоков с разных узлов пишут в пересекающиеся области, балансировщик получает противоречивые сигналы и может зайти в цикл бесплодных переносов. Симптом классический: высокая системная загрузка при умеренной полезной работе, и диагностика начинается именно с проверки статистики миграций.

Наблюдение за поведением механизма

Видимость обеспечивают три источника. Счётчики /proc/vmstat с семейством numa_pages_migrated, numa_pte_updates, numa_hint_faults показывают темпы сканирования и переносов на уровне системы. Статистика на процесс в /proc/pid/numa_maps раскладывает страницы по узлам, показывая, где живут данные конкретного приложения. И аппаратные счётчики PMU, измеряющие долю удалённых обращений к памяти, дают конечный критерий эффективности: загружаться можно много, а удалённых обращений не убавиться.

Практичная диагностическая последовательность построена так:

  1. Зафиксировать текущий профиль удалённых обращений perf stat по событию, отражающему обращения к чужим узлам, на представительной нагрузке;
  2. Проверить темпы numa_pages_migrated и numa_hint_faults в vmstat на том же интервале;
  3. Сопоставить: высокий темп переносов при неиссякающей доле удалённых обращений означает, что эвристика гонится за убегающей целью;
  4. Решить, чинить ли под настройки агрессивности, выключением или архитектурой приложения.

Эта цепочка защищает от обеих крайностей: от слепого доверия механизму и от его панического отключения при первых непонятных цифрах.

Отдельно смотрятся полные счётчики узлов в в /proc показывают, как растёт numa_hit и miss в разрезе узлов, и длительный мониторинг этих рядов рисует динамику, скрытую за мгновенными снимками.

Настройка агрессивности и выборочное применение

Когда автоматику решают оставить, но укоротить, первым делом режут объём сканирования. Уменьшение numa_balancing_scan_size_mb снижает число искусственных ошибок в единицу времени, а увеличение периода реже будит потоки. Такие параметры подбираются на канареечной машине, потому что общеприменимых цифр не существует: они зависят от размеров рабочих множеств, частоты смены фаз нагрузки и чувствительности к задержкам.

Точечная альтернатива это управление через mbind и set_mempolicy внутри самого приложения. Критичные структуры располагают interleave по узлам для равномерного доступа со всех сокетов, приватные области каждого потока привязывают к его узлу, общие редкие структуры живут на узле обладателя. Ручная планировка требует больше исходной работы, зато исключает периодическое сканирование как класс.

Гибридный подход для платформ оркестрации это настройка через cpuset cgroups. Каждому контейнеру приписывают узел и его память, балансировка внутри таких границ становится почти безопасной, а межконтейнерные миграции страниц исключаются конструктивно. На больших машинах это сейчас самая надёжная схема соединения гибкости и предсказуемости.

Совсем выключать механизм стоит лишь после замеров. Отключение простым echo в sysctl естественный шаг отладки, но решение обитать с ним должно опираться на сравнение полного профиля системы с автоматикой и без, а не на единичной вспышке задержек.

Большие страницы и их взаимодействие с миграцией

Huge pages смягчают проблему и одновременно её обостряют. Они радикально уменьшают число страниц и стоимость сканирования, и их собственные версии правил миграции работают, но перенос большой страницы это куда более дорогая операция, чем перенос четырёх килобайт. При отсутствии свободных непрерывных областей ядро может вовсе отложить перенос, и статистика покажет странное равновесие: механизм активен, переездов мало, удалённые обращения не исчезают.

Разумная стратегия на серверах с huge pages это комбинация интерливного размещения на старте и минимальной автоматики впоследствии. База данных, отображающая своё пространство с interleave по всем узлам, гарантирует равномерную дистанцию от любого запроса и снимает вопрос миграций в принципе, ценой отказа от преимуществ локальности. Для действительно разделяемых всеми структур это честный выбор.

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

Виртуализация, контейнеры и многоуровневая иерархия памяти

Гипервизоры добавляют второй слой адресации, и гостевая проверка узлов уже не видит реального железа. Виртуальная машина считает свою память плоской, в то время как хост распределяет её страницы по узлам своим усмотрением. Правильно сконфигурированная система передаёт гостю виртуальную топологию, соответствующую физической, и гостевые планировщики размещают потоки в согласии с хостом. Неправильно сконфигурированная создаёт двойную игру в угадайку, где обе системы принимают неосведомлённые решения.

Привязка виртуальных процессоров к реальным ядрам сокета вместе с закреплением памяти гостя на том же узле является стандартом для телекома и других чувствительных к задержкам нагрузок. Такая конфигурация намеренно жертвует гибкостью живой миграции ради предсказуемости, и это один из редких случаев, когда жёсткая статичность оправдана в динамическом мире.

Контейнеры разделяют ядро и полагаются на cgroups. Механика cpuset давно умеет ограничивать узлы памяти и процессора контейнера, и аккуратное назначение этих масок заменяет бо́льшую часть автоматики честным планом. Платформы оркестрации высокого уровня уже умеют собирать политики в профили, и задача администратора сводится к выбору профиля, соответствующего характеру нагрузки, вместо ручной писанины чисел.

Современные системы добавляют уровни между быстрым локальным DRAM и медленным расширением: постоянная память, расширители на последовательных шинах, пулы второго уровня. NUMA абстракция естественно покрывает эту иерархию, и роли узлов становятся дифференцированными: быстрые, средние, медленные. Подсистема балансировки учится учитывать не только расстояние между сокетами, но и вертикальное положение страницы, и в ядре появляются механизмы движения горячих страниц вверх и холодных вниз.

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

Прогнозируемое развитие принесёт более точные сигналы от оборудования о температуре страниц, и задача ядра упростится, но не исчезнет. Понимание принципов миграции, заложенное сейчас на обычной двухсокетной машине, останется применимым и в грядущих многоуровневых конфигурациях, где отличие будет лишь в числе ступеней.

Методика оценки результата и удержание стабильности

Оценка любой настройки подсистемы делается по конечной метрике приложения плюс метрикам самой механики. К основным цифрам времени ответа добавляются доля удалённых обращений и суммарное время, украденное собственно балансировкой. Конфигурация, при которой система стала быстрее за счёт тихого удвоения системной загрузки, часто проигрывает на масштабе парка, где электроэнергия и охлаждение считаются в деньгах.

Стабильность поддерживается контрольными прогонами после каждого существенного обновления: ядро, микропрограмма, параметры кластера, состав нагрузки. NUMA поведение хрупко от перестановки любого из этих факторов, и культура измерений превращает хрупкость в управляемое свойство.

Вывод остаётся единым. Миграция страниц это инструмент, и инструмент прекрасный там, где паттерны доступа стабильны, и раздражающий там, где они хаотичны. Инженер, владеющий диагностикой и обеими стратегиями, автоматической и ручной, получает лучшую из доступных производительностей памяти независимо от того, какую нагрузку принесёт завтрашний день.