NUMA-aware приложения — как писать софт с учётом NUMA

6 минут чтения
Средний рейтинг статьи — 4.6

На современных серверах с большим количеством 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 аналитика

Для простых приложений разница может быть незаметной.

Практические советы

  1. Всегда проверяйте topology
    • сколько NUMA-узлов
  2. Используйте first-touch правильно
    • инициализация = место использования
  3. Минимизируйте shared memory
  4. Следите за миграцией потоков
  5. Тестируйте под нагрузкой
    • NUMA-эффекты проявляются не сразу

Итог

NUMA — это не просто особенность железа, а важный фактор производительности.

Если приложение не учитывает NUMA:

  • растёт latency
  • падает throughput

NUMA-aware подход позволяет:

  • улучшить locality
  • снизить задержки
  • эффективнее использовать ресурсы сервера

На больших системах это может давать кратный прирост производительности без изменения логики приложения.

6 минут чтения
Средний рейтинг статьи — 4.6

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний