NUMA (Non-Uniform Memory Access — Неоднородный доступ к памяти)
Тема NUMA (Non-Uniform Memory Access — Неоднородный доступ к памяти) критически важна для понимания производительности современных серверов, баз данных и высоконагруженных систем. Непонимание принципов NUMA часто приводит к тому, что мощное «железо» работает медленнее, чем ожидается.
1. Введение: Что такое NUMA?
NUMA (Non-Uniform Memory Access) — это архитектура компьютерной памяти, используемая в многопроцессорных системах, где время доступа к памяти зависит от того, где расположена память относительно процессора, который к ней обращается.
Простыми словами: Память, которая «ближе» к процессору, работает быстрее, чем память, которая «далеко».
Сравнение с UMA
Чтобы понять NUMA, нужно вспомнить его противоположность — UMA (Uniform Memory Access):
- UMA (Старая архитектура): Все процессоры подключены к одной общей шине памяти. Доступ к любой ячейке памяти занимает одинаковое время для любого ядра.
- Проблема: При увеличении количества процессоров шина становится «узким горлышком». Возникают конфликты за доступ к памяти.
- NUMA (Современная архитектура): Каждый процессор (или группа ядер) имеет свой локальный контроллер памяти и свою локальную память.
- Преимущество: Масштабируемость. Каждый процессор обслуживает свою память независимо.
- Нюанс: Если процессору нужна память, которая физически находится у соседа (удаленная память), доступ будет медленнее.
2. Как это работает «под капотом»
В современных серверах (Intel Xeon, AMD EPYC) контроллеры памяти встроены непосредственно в процессор.
Структура узла (Node)
Система делится на NUMA-узлы. Обычно один узел = один физический процессор (сокет) + память, подключенная к нему.
Локальная память (Local Memory):
- Память, подключенная напрямую к данному процессору. Доступ быстрый (низкая латентность, высокая пропускная способность).
Удаленная память (Remote Memory):
- Память, подключенная к другому процессору. Чтобы получить к ней доступ, запрос идет через межпроцессорную шину (Intel QPI/UPI или AMD Infinity Fabric). Это добавляет задержку.
Пример задержек (латентности)
- Доступ к локальной памяти: ~100 нс.
- Доступ к удаленной памяти: ~150–200 нс (и выше, в зависимости от топологии).
- Разница может достигать 30–50%., что критично для баз данных и научных вычислений.
3. Влияние NUMA на производительность
Если операционная система или приложение не учитывают NUMA, могут возникнуть следующие проблемы:
- Cross-Node Memory Access: Поток выполняется на Ядре 1 (Узел 0), а выделяет память на Узле 1. Каждое обращение к этой памяти идет через межпроцессорную шину.
- Перегрузка шины (Interconnect Saturation): Если много потоков постоянно читают чужую память, шина между процессорами забивается, замедляя работу всей системы.
- False Sharing (Ложное разделение): Два ядра из разных узлов модифицируют переменные, находящиеся в одной кэш-линии. Это вызывает постоянную синхронизацию кэшей между узлами.
Когда NUMA важен?
- Базы данных (PostgreSQL, Oracle, MS SQL).
- Высоконагруженные веб-серверы.
- Научные вычисления (HPC).
- Обычные офисные ПК (OS сама хорошо справляется).
- Системы с одним процессором (там NUMA нет или он не влияет).
4. Диагностика и мониторинг (Linux)
Большинство серверов работают на Linux. Вот набор инструментов для анализа.
1. Просмотр топологии NUMA
- Команда
numactlпоказывает, как видят систему ядро и железо.
numactl --hardware
# или сокращенно
numactl -H
Вывод:
available: 2 nodes (0-1)— сколько узлов.node 0 cpus:— какие ядра принадлежат узлу 0.node 0 size:— сколько памяти локально.node 0 free:— сколько свободно.node distances:— матрица задержек. (10 — локально, 20+ — удаленно).
2. Просмотр распределения памяти
Команда vmstat может показать активность NUMA (в новых версиях):
vmstat -w -n 1
Смотрите на колонки free, si, so (свопинг), но для NUMA важнее специализированные утилиты.
Утилита numastat показывает статистику распределения памяти по узлам:
numastat
numa_hit: Успешные выделения локальной памяти (хорошо).numa_miss: Выделения удаленной памяти (плохо, если много).numa_foreign: Выделения на этом узле для процесса, бегущего на другом (плохо).
3. Анализ производительности
Инструмент perf может показать события, связанные с памятью:
perf stat -e memory_bandwidth:local_bandwidth.used,mem_inst_retired.all_loads -a sleep 5
(Поддержка событий зависит от процессора).
5. Стратегии оптимизации
Оптимизация происходит на трех уровнях: BIOS, ОС и Приложение.
Уровень 1: BIOS / UEFI
В настройках сервера часто есть опция Node Interleaving.
- Disable (Рекомендуется для производительности): Включает режим NUMA. ОС видит узлы и может оптимизировать.
- Enable: Принудительно делает память однородной (UMA). ОС видит одну большую кучу памяти.
- Зачем включать? Если приложение вообще не умеет работать с NUMA и начинает глючить, или для совместимости со старым ПО. Но производительность может упасть на 10–20%.
Уровень 2: Настройка ОС (Linux)
Политики выделения памяти
Вы можете управлять поведением через numactl.
Interleave (Чередование):
- Память распределяется циклически по всем узлам. Хорошо, если приложение использует много потоков на всех ядрах равномерно.
numactl --interleave=all ./my_application
Bind (Привязка):
- Жесткая привязка процесса к узлу. Процесс использует только локальную память и ядра этого узла. Лучшая производительность для изолированных задач.
numactl --cpunodebind=0 --membind=0 ./my_application
Prefer (Предпочтение):
- Попытаться выделить локально, если нет места — взять удаленную.
numactl --preferred=0 ./my_application
Отключение прозрачной огромных страниц (THP)
Иногда THP (Transparent Huge Pages) конфликтует с NUMA, вызывая задержки. Для баз данных (например, MongoDB, Redis, Oracle) часто рекомендуют отключать THP.
echo never > /sys/kernel/mm/transparent_hugepage/enabled
Уровень 3: Настройка приложения
- Affinity (Аффинность): Привязка потоков приложения к конкретным ядрам CPU.
- Локальность данных: Архитектура приложения должна стремиться к тому, чтобы данные обрабатывались тем же ядром, которое их создало или загрузило.
- Разделение баз данных: Если у вас 2 сокета, можно запустить два экземпляра БД, каждый на своем узле NUMA, разделив нагрузку.
6. NUMA в виртуализации (vNUMA)
Если вы используете виртуальные машины (VMware, KVM, Hyper-V), важно настроить vNUMA.
- Проблема: Гипервизор может распределить виртуальные ядра VM по разным физическим процессорам хоста, а память выделить из одного узла. Это убьет производительность внутри VM.
- Решение:
- Включить поддержку vNUMA в настройках VM.
- Размер VM не должен превышать размер физического NUMA-узла хоста (если возможно).
- Использовать резервирование памяти (Memory Reservation), чтобы гипержор не свопил память VM.
Пример для KVM/QEMU (в XML конфигурации):
<cpu mode='host-passthrough' check='none'>
<topology sockets='1' cores='8' threads='1'/>
<numa>
<cell id='0' cpus='0-7' memory='16777216' memAccess='readonly'/>
</numa>
</cpu>
7. Чек-лист: Что делать прямо сейчас?
Если вы администрируете сервер, вот пошаговый план действий:
Проверьте топологию:
- Выполните
numactl -H. Убедитесь, что система видит несколько узлов (если у вас многопроцессорный сервер).
Проверьте балансировку:
- Выполните
numastat -m. Если видите многоnuma_missилиnuma_foreignдля важных процессов, значит, есть перекос.
Проверьте BIOS:
- Убедитесь, что Node Interleaving выключен (Disabled), чтобы ОС могла управлять NUMA осознанно.
Настройте критичные сервисы:
- Для СУБД (PostgreSQL, MySQL) используйте привязку к ядрам и памяти через
numactl.
Пример для запуска PostgreSQL:
numactl --cpunodebind=0 --membind=0 pg_ctl start
(Если у вас 2 узла, можно запустить инстансы на обоих или разделить процессы).
Мониторинг:
- Добавьте сбор метрик
numastatв вашу систему мониторинга (Zabbix, Prometheus), чтобы видеть динамику удаленного доступа к памяти.
8. Распространенные мифы
- Миф: «NUMA всегда нужно отключать».
- Реальность: Отключать (включать Interleaving) нужно только если приложение ведет себя нестабильно. Для производительности NUMA должен быть включен.
- Миф: «ОС сама все оптимизирует».
- Реальность: Современные ядра (Linux 4.x+, Windows Server 2016+) умные, но они не знают бизнес-логику вашего приложения. Для высоконагруженных систем ручная настройка дает прирост 10–30%.
- Миф: «NUMA нужен только для 4+ сокетных систем».
- Реальность: Даже в двухпроцессорных системах (2 Socket) разница между локальной и удаленной памятью существенна.
Заключение
Архитектура NUMA — это плата за масштабируемость. Она дает возможность устанавливать в сервер десятки ядер и терабайты памяти, но требует внимательного отношения к расположению данных и потоков.
Главное правило:
- Держите вычисления (CPU) и данные (Memory) как можно ближе друг к другу в пределах одного NUMA-узла.