NUMA-aware приложения — как писать софт с учётом NUMA
На современных серверах с большим количеством CPU и памяти всё чаще используется архитектура NUMA (Non-Uniform Memory Access). Она позволяет масштабировать систему, но добавляет нюанс: доступ к памяти становится неравномерным по времени.
Если приложение это игнорирует, появляются лишние задержки, падение throughput и нестабильная производительность. Поэтому для high-load систем важно писать NUMA-aware софт.
Что такое NUMA
В NUMA-системе процессоры и память разбиты на узлы (NUMA nodes).
У каждого CPU есть:
- «локальная» память (быстрый доступ)
- «удалённая» память (медленный доступ через interconnect)
Разница в latency может быть существенной — иногда в 1.5–3 раза.
Ключевая идея:
- доступ к локальной памяти быстрее
- доступ к памяти другого узла дороже
Почему это важно
Если поток выполняется на одном CPU, а данные лежат в памяти другого NUMA-узла:
- увеличивается latency
- падает cache locality
- растёт нагрузка на межсоединение (QPI/Infinity Fabric)
В результате даже мощный сервер может работать хуже, чем ожидается.
Как ОС работает с NUMA
Linux старается учитывать NUMA автоматически:
- размещает память рядом с CPU (first-touch policy)
- мигрирует страницы при необходимости
- балансирует нагрузку
Но это «best effort», а не гарантия.
Если приложение активно аллоцирует память и мигрирует между CPU — оптимизации ядра могут не помочь.
First-touch policy
Важный момент: память привязывается к NUMA-узлу при первом доступе.
Это значит:
- поток, который первый записал в память, определяет её расположение
Практический вывод:
- инициализацию данных нужно делать в том же потоке, где они будут использоваться
Иначе данные окажутся «не там».
Основные проблемы NUMA
1. Remote memory access
Доступ к удалённой памяти увеличивает latency.
2. False sharing между узлами
Если разные CPU активно работают с одними данными:
- растёт traffic
- падает производительность
3. Thread migration
Если поток переезжает между CPU:
- теряется cache locality
- память может оказаться удалённой
Как писать NUMA-aware приложения
Привязка потоков (CPU affinity)
Закрепляйте потоки за конкретными CPU:
- sched_setaffinity
- taskset
Это уменьшает миграции и сохраняет locality.
Привязка памяти (memory binding)
Используйте:
- numactl
- libnuma
Пример:
- выделять память на том же узле, где работает поток
Data partitioning
Разделяйте данные по узлам:
- shard’инг
- локальные очереди
- per-thread структуры
Каждый поток работает со «своими» данными.
Избегайте shared state
Общие структуры данных между узлами:
- создают contention
- увеличивают latency
Лучше:
- lock-free
- message passing
Локальные аллокаторы
Используйте аллокаторы, учитывающие NUMA:
- jemalloc
- tcmalloc (с настройками)
Они уменьшают cross-node аллокации.
Практика: архитектурные подходы
1. Thread-per-core
Каждый поток закреплён за CPU:
- минимальные миграции
- хорошая locality
2. Sharded services
Сервис делится на независимые части:
- каждый shard живёт на своём NUMA-узле
3. Actor model
Компоненты общаются через сообщения:
- меньше shared state
- лучше масштабируемость
Диагностика NUMA-проблем
Полезные инструменты:
- numactl --hardware
- numastat
- perf
- hwloc
Что смотреть:
- remote memory access
- page migrations
- imbalance между узлами
Когда это критично
NUMA-awareness особенно важен:
- базы данных
- in-memory кеши
- high-frequency trading
- real-time аналитика
Для простых приложений разница может быть незаметной.
Практические советы
- Всегда проверяйте topology
- сколько NUMA-узлов
- Используйте first-touch правильно
- инициализация = место использования
- Минимизируйте shared memory
- Следите за миграцией потоков
- Тестируйте под нагрузкой
- NUMA-эффекты проявляются не сразу
Итог
NUMA — это не просто особенность железа, а важный фактор производительности.
Если приложение не учитывает NUMA:
- растёт latency
- падает throughput
NUMA-aware подход позволяет:
- улучшить locality
- снизить задержки
- эффективнее использовать ресурсы сервера
На больших системах это может давать кратный прирост производительности без изменения логики приложения.
Настроить мониторинг за 30 секунд
Надежные оповещения о даунтаймах. Без ложных срабатываний