Implementación de Aplicación Consciente de NUMA

Las arquitecturas de Acceso a Memoria No Uniforme (NUMA) dominan los sistemas de alto rendimiento modernos, donde la latencia de memoria varía significativamente según la ubicación del acceso. Las aplicaciones que no son conscientes de la topología de NUMA sufren degradación de rendimiento a través de sanciones de acceso a memoria remota. Esta guía cubre entendimiento de arquitectura NUMA, detección de topología e implementación de aplicaciones con conciencia de NUMA para rendimiento óptimo.

Tabla de Contenidos

  1. Descripción General de Arquitectura NUMA
  2. Detección de Topología de Hardware
  3. Fijación de CPU Consciente de NUMA
  4. Configuración de Política de Memoria
  5. Optimización de NUMA en Base de Datos
  6. Monitoreo de Rendimiento de NUMA
  7. Conclusión

Descripción General de Arquitectura NUMA

Entender NUMA

Los servidores de múltiples sockets modernos emplean NUMA donde:

  • Cada socket tiene memoria y CPUs locales
  • Las CPUs acceden a memoria local más rápido que remota
  • Latencia de acceso a memoria 2-3x más alta para acceso remoto
  • Las aplicaciones deben tener en cuenta la topología

El no optimizar causa:

  • Ancho de banda de memoria reducido
  • Mayor varianza de latencia
  • Eficiencia de caché deficiente
  • Rendimiento de CPU desperdiciado

Detección de Topología de Hardware

Detectar Configuración de NUMA

# Listar nodos NUMA
numactl --hardware

# Ejemplo de salida:
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 6 7
# node 0 size: 32768 MB
# node 0 free: 28900 MB
# node 1 cpus: 8 9 10 11 12 13 14 15
# node 1 size: 32768 MB
# node 1 free: 29100 MB

# Topología detallada
lscpu --all

# Asignación de nodo NUMA
cat /proc/cpuinfo | grep -E "processor|physical id|core id"

# Información de memoria por nodo
cat /proc/meminfo | grep -i numa

Visualizar Topología

# Las herramientas hwloc proporcionan topología detallada
sudo apt-get install -y hwloc

# Pantalla de topología visual
lstopo

# Resumen de texto
lstopo-no-graphics

# Más detalles
hwloc-info

# Exportar topología
lstopo topology.xml
hwloc-info --input topology.xml

Fijación de CPU Consciente de NUMA

Afinidad de CPU de Proceso

# Verificar asignación actual de CPU
taskset -pc $$

# Ejemplo: Fijar proceso único a nodo NUMA 0
numactl --cpunodebind=0 --membind=0 ./application

# Parámetros:
# --cpunodebind: CPUs a usar
# --membind: Nodos de memoria a usar

# Fijar a CPUs específicas
numactl -C 0,1,2,3 ./application

# Fijar a nodo 1
numactl --cpunodebind=1 --membind=1 ./application

Fijación de Aplicación Multi-hilo

# Configurar script de fijación para app multi-hilo
cat > run_numa_app.sh <<'EOF'
#!/bin/bash
APP=$1
NUM_THREADS=$(nproc)

# Determinar nodos NUMA
NUMA_NODES=$(numactl --hardware | grep "^node" | awk '{print $2}' | tr -d ':' | sort -u)
NODE_COUNT=$(echo "$NUMA_NODES" | wc -l)

# Distribuir hilos en nodos NUMA
for thread in $(seq 0 $((NUM_THREADS-1))); do
  node=$((thread % NODE_COUNT))
  cpu=$(lscpu -J | jq -r ".cpuinfo[] | select(.\"node-id\"==$node) | .\"cpu-index\"" | head -1)
  taskset -cp $cpu $((thread + 1))
done

# Ejecutar aplicación
numactl --cpunodebind=0,1 --membind=0,1 $APP
EOF

chmod +x run_numa_app.sh
./run_numa_app.sh ./myapp

Fijación de Hilo Intercalado

# Fijar hilos a nodos NUMA alternos para balance
cat > interleaved_pin.sh <<'EOF'
#!/bin/bash
APP=$1

# Para sistema de 2 nodos, intercalar hilos
# Hilo 0,2,4,... -> Nodo 0
# Hilo 1,3,5,... -> Nodo 1

export OMP_NUM_THREADS=16
export OMP_PLACES="{0,2,4,6,8,10,12,14},{1,3,5,7,9,11,13,15}"
export OMP_PROC_BIND=close

$APP
EOF

chmod +x interleaved_pin.sh
./interleaved_pin.sh

Configuración de Política de Memoria

Vinculación de Memoria de numactl

# Asignar memoria desde nodo específico
numactl --membind=0 ./application

# Asignación local solo (fallar si memoria local insuficiente)
numactl --localalloc ./application

# Asignación intercalada en nodos
numactl --interleave=all ./application

# Nodo preferido con respaldo
numactl --preferred=0 ./application

# Múltiples nodos de memoria
numactl --membind=0,1 ./application

Verificación de Política de Memoria

# Verificar vinculación de memoria del proceso
numactl -s
numastat -p $$

# Colocación de memoria por proceso
cat /proc/self/numa_maps

# Estadísticas de memoria detalladas
numastat

# La salida muestra:
# Uso de memoria por nodo
# Actividad de migración de página
# Patrones de acceso remoto

Páginas Enormes Transparentes y NUMA

# Verificar estado de THP
cat /sys/kernel/mm/transparent_hugepage/enabled

# Habilitar THP
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled

# Configuración de THP específica de NUMA
cat /sys/kernel/mm/transparent_hugepage/numa_stat

# Monitorear asignación de THP
grep thp_fault /proc/meminfo

Optimización de NUMA en Base de Datos

Configuración de NUMA en MySQL

# Configurar MySQL para operación consciente de NUMA
# En /etc/mysql/mysql.conf.d/mysqld.cnf:

cat <<'EOF' | sudo tee -a /etc/mysql/mysql.conf.d/mysqld.cnf

# Optimización de NUMA
numactl --cpunodebind=0 --membind=0

# Tamaño de pool de búfer (por nodo)
innodb_buffer_pool_size = 16G

# Uso de memoria por hilo
sort_buffer_size = 2M
read_rnd_buffer_size = 2M

# Asignación de hilo a NUMA
innodb_numa_interleave = ON
EOF

# Reiniciar MySQL
sudo systemctl restart mysql

# Iniciar MySQL con conciencia de NUMA
numactl --cpunodebind=0,1 --membind=0,1 mysqld_safe

Configuración de NUMA en PostgreSQL

# Detección automática de NUMA de PostgreSQL
# Configurar en postgresql.conf:

cat <<'EOF' | sudo tee -a /etc/postgresql/*/main/postgresql.conf

# Memoria de trabajo por operación
work_mem = 4MB

# Búferes compartidos (usualmente 25% de RAM)
shared_buffers = 8GB

# Tamaño de caché efectivo
effective_cache_size = 32GB
EOF

# Iniciar PostgreSQL con conciencia de NUMA
sudo -u postgres numactl --localalloc /usr/lib/postgresql/*/bin/postgres \
  -D /var/lib/postgresql/*/main \
  -c config_file=/etc/postgresql/*/main/postgresql.conf

Monitoreo de Rendimiento de NUMA

Estadísticas de NUMA

# Estadísticas generales de NUMA
numastat

# Estadísticas de NUMA por proceso
numastat -p $(pgrep mysql)

# Distribución de asignación de memoria
cat /proc/self/numa_maps

# Monitorear acceso a memoria remota
watch -n 1 'numastat'

# Rastrear migración
while true; do
  clear
  cat /proc/*/numa_maps | grep 'bind:' | wc -l
  sleep 1
done

Monitoreo de Rendimiento

# Monitorear migración de página
perf stat -e numa_hit,numa_miss ./application

# Conteo de eventos NUMA detallado
perf record -e cycles,instructions,dTLB-loads,dTLB-load-misses,LLC-loads,LLC-load-misses -g ./application
perf report

# Rendimiento de NUMA en tiempo real
nstat -a | grep numa

# Verificar latencia de memoria
# Usando lmbench
lmbench -c 0 -S

Detectar Problemas de NUMA

# Acceso a memoria remota alto indica baja conciencia de NUMA
numastat | grep remote

# Verificar migración de memoria
cat /proc/vmstat | grep numa_pte_updates

# Monitorear migración de tarea entre nodos NUMA
watch -n 1 'cat /proc/*/stat | awk "{print \$39}" | sort | uniq -c'

# Identificar presión de memoria entre nodos
cat /proc/sys/kernel/numa_stat

Conclusión

La implementación de aplicación consciente de NUMA transforma el rendimiento en sistemas multi-socket modernos. Al alinear afinidad de CPU y memoria con topología de hardware, las organizaciones eliminan sanciones de latencia de acceso a memoria y logran utilización óptima. Entender herramientas numactl, políticas de memoria y ajuste específico de topología permite a equipos de despliegue realizar el potencial completo de hardware de gama alta. Combinado con herramientas de monitoreo, la optimización de NUMA se convierte en una práctica medible y repetible que produce mejoras significativas de rendimiento para aplicaciones sensibles a latencia.