--- title: "Cómo fijar aplicaciones a nodos NUMA con numactl y systemd" description: "Detecta la topología NUMA de un servidor Ubuntu 24.04, mide el coste del acceso a memoria remota y fija un servicio a un nodo con numactl y systemd." category: "Rendimiento y Benchmarking" language: es source: https://cubepath.com/es/docs/rendimiento-y-benchmarking/implementacion-de-aplicacion-consciente-de-numa updated: 2026-09-25 --- # Cómo fijar aplicaciones a nodos NUMA con numactl y systemd En un servidor con varios sockets (o con procesadores que dividen internamente la memoria, como algunos AMD EPYC), cada grupo de núcleos tiene su propia memoria local. Ese grupo es un nodo NUMA (Non-Uniform Memory Access): leer memoria del propio nodo es rápido, y leerla de otro nodo pasa por el enlace entre sockets y es más lento. En este tutorial detectarás la topología NUMA de un servidor con Ubuntu 24.04, medirás la diferencia entre memoria local y remota, y fijarás un servicio a un nodo concreto con `numactl` y con las opciones nativas de systemd. ## Requisitos previos - Un servidor dedicado con Ubuntu 24.04 LTS y dos o más nodos NUMA, por ejemplo un servidor bare metal de doble socket de CubePath. - Un usuario no root con privilegios `sudo`. - Un servicio que quieras fijar. El ejemplo usa Redis, pero el procedimiento es el mismo para cualquier unidad de systemd (MySQL, PostgreSQL, una aplicación Java o Go). > Nota: la mayoría de VPS exponen un único nodo NUMA a la máquina virtual, porque el hipervisor ya coloca sus vCPU y su memoria. Si el paso 1 muestra `available: 1 nodes`, no hay nada que fijar y esta guía no aporta mejora. ## Paso 1: Detectar la topología NUMA Instala `numactl`, que incluye también la herramienta `numastat`: ```bash sudo apt update sudo apt install numactl ``` Muestra los nodos, sus CPU y su memoria: ```bash numactl --hardware ``` ```output available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23 node 0 size: 64232 MB node 0 free: 58110 MB node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31 node 1 size: 64507 MB node 1 free: 60943 MB node distances: node 0 1 0: 10 21 1: 21 10 ``` La tabla `node distances` indica el coste relativo de acceso: 10 es el acceso local y 21 significa que ir al otro nodo cuesta aproximadamente el doble. Fíjate en que las CPU de cada nodo no son siempre consecutivas: aquí los hilos de hyperthreading (16 a 31) se reparten entre ambos nodos, por lo que conviene fijar por nodo y no por números de CPU escritos a mano. `lscpu` ofrece el mismo resumen en formato compacto: ```bash lscpu | grep -i numa ``` ```output NUMA node(s): 2 NUMA node0 CPU(s): 0-7,16-23 NUMA node1 CPU(s): 8-15,24-31 ``` ## Paso 2: Medir el coste de la memoria remota Antes de fijar nada, comprueba cuánto pierdes con la memoria remota en este servidor. `sysbench` incluye una prueba de ancho de banda de memoria: ```bash sudo apt install sysbench ``` Ejecuta la prueba con las CPU y la memoria en el mismo nodo: ```bash numactl --cpunodebind=0 --membind=0 sysbench memory --memory-block-size=1M --memory-total-size=50G --threads=8 run | grep transferred ``` ```output 51200.00 MiB transferred (21430.12 MiB/sec) ``` Ahora repítela con las CPU en el nodo 0 y la memoria en el nodo 1: ```bash numactl --cpunodebind=0 --membind=1 sysbench memory --memory-block-size=1M --memory-total-size=50G --threads=8 run | grep transferred ``` ```output 51200.00 MiB transferred (12877.54 MiB/sec) ``` La diferencia exacta depende del procesador y de la configuración de la BIOS, pero en servidores de doble socket es habitual ver entre un 30 % y un 50 % menos de ancho de banda en remoto, además de mayor latencia. Esa es la penalización que evitas al fijar un servicio a un nodo. ## Paso 3: Entender las políticas de numactl `numactl` lanza un programa con una política de CPU y memoria. Las opciones que usarás son: | Opción | Efecto | |---|---| | `--cpunodebind=N` | El proceso solo se ejecuta en las CPU del nodo N. | | `--membind=N` | La memoria solo se asigna en el nodo N. Si se agota, el proceso sufre el OOM killer en lugar de usar el otro nodo. | | `--preferred=N` | Asigna en el nodo N mientras haya memoria libre y después usa el resto. | | `--interleave=all` | Reparte las páginas entre todos los nodos por turnos. Útil para procesos que ocupan más memoria de la que tiene un nodo. | | `--localalloc` | Asigna siempre en el nodo de la CPU que pide la memoria (el comportamiento por defecto del kernel). | Puedes comprobar la política resultante lanzando el propio `numactl --show` con esas opciones: ```bash numactl --cpunodebind=1 --preferred=1 numactl --show ``` ```output policy: preferred preferred node: 1 physcpubind: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31 cpubind: 1 nodebind: 1 membind: 0 1 ``` Una regla práctica para elegir: - **Servicio que cabe en un nodo** (una instancia de Redis, un worker de aplicación): `--cpunodebind` más `--preferred` en el mismo nodo. Obtienes memoria local sin riesgo de OOM si el nodo se llena. - **Servicio que necesita más memoria de la que tiene un nodo** (una base de datos grande con un buffer pool enorme): `--interleave=all`. No evita el acceso remoto, pero reparte el ancho de banda y evita que un nodo se quede sin memoria mientras el otro está libre. - **Varias instancias del mismo servicio**: una instancia por nodo, cada una fijada a su nodo. ## Paso 4: Fijar un servicio con systemd Para servicios gestionados por systemd no hace falta envolver `ExecStart` con `numactl`. systemd 255, incluido en Ubuntu 24.04, admite directamente `CPUAffinity=`, `NUMAPolicy=` y `NUMAMask=`. Instala Redis como ejemplo: ```bash sudo apt install redis-server ``` Crea un archivo de override para la unidad. `systemctl edit` abre un editor y guarda el resultado en `/etc/systemd/system/redis-server.service.d/override.conf`: ```bash sudo systemctl edit redis-server ``` Añade este bloque entre las líneas de comentario indicadas por el editor. Usa las CPU del nodo 0 que mostró `lscpu` en el paso 1: ```ini [Service] CPUAffinity=0-7 16-23 NUMAPolicy=preferred NUMAMask=0 ``` `NUMAPolicy` acepta `default`, `preferred`, `bind`, `interleave` y `local`, con el mismo significado que las opciones de `numactl`. `NUMAMask` indica los nodos a los que se aplica. Guarda, cierra el editor y reinicia el servicio para que arranque con la nueva política: ```bash sudo systemctl restart redis-server ``` Comprueba la afinidad de CPU del proceso: ```bash taskset -cp "$(pidof redis-server)" ``` ```output pid 4182's current affinity list: 0-7,16-23 ``` ## Paso 5: Verificar dónde está la memoria `numastat -p` muestra en qué nodo reside la memoria de un proceso. Llena Redis con datos de prueba para que la memoria crezca. `redis-benchmark`, incluido en el paquete `redis-tools`, escribe hasta un millón de claves aleatorias de 100 bytes: ```bash redis-benchmark -t set -n 1000000 -r 1000000 -d 100 -q ``` ```output SET: 98231.34 requests per second, p50=0.263 msec ``` > Nota: si el servidor no es de pruebas, borra después esas claves (todas empiezan por `key:`) o usa una base de datos de Redis aparte con la opción `--dbnum`. Ahora consulta la distribución por nodo: ```bash numastat -p redis-server ``` ```output Per-node process memory usage (in MBs) for PID 4182 (redis-server) Node 0 Node 1 Total --------------- --------------- --------------- Huge 0.00 0.00 0.00 Heap 112.45 0.00 112.45 Stack 0.03 0.00 0.03 Private 6.61 0.08 6.69 ---------------- --------------- --------------- --------------- Total 119.09 0.08 119.17 ``` Casi toda la memoria está en el nodo 0, que es lo que buscabas. Para ver los contadores globales del sistema, ejecuta `numastat` sin argumentos: ```bash numastat ``` ```output node0 node1 numa_hit 8123456 7012345 numa_miss 1520 2210 numa_foreign 2210 1520 interleave_hit 21034 20988 local_node 8120001 7009876 other_node 4975 4679 ``` `numa_miss` y `other_node` cuentan asignaciones que acabaron en un nodo distinto del deseado. Si crecen rápido respecto a `numa_hit`, algún proceso está usando memoria remota con frecuencia. ## Paso 6: Revisar el balanceo automático del kernel El kernel incluye balanceo NUMA automático: mueve páginas y tareas hacia el nodo donde más se usan. Está activo por defecto: ```bash sysctl kernel.numa_balancing ``` ```output kernel.numa_balancing = 1 ``` Para la mayoría de cargas conviene dejarlo activado, porque ayuda a los procesos que no has fijado. Si has fijado a mano todos los servicios importantes y ves consumo de CPU en migraciones (líneas `numa_pages_migrated` en `/proc/vmstat` que crecen sin parar), puedes probar a desactivarlo con un archivo en `/etc/sysctl.d/` y medir de nuevo antes de dejarlo así. Deja también `vm.zone_reclaim_mode` en su valor por defecto, `0`. Con otros valores el kernel prefiere liberar caché del nodo local antes que usar memoria libre del otro nodo, lo que suele perjudicar a servidores de archivos y bases de datos. ## Solución de problemas **`numactl --hardware` muestra `No NUMA available on this system`**: el kernel no ve nodos NUMA. En un VPS es lo normal. En un servidor físico, revisa en la BIOS opciones como "Node Interleaving" (debe estar desactivada para exponer NUMA) o "NUMA nodes per socket" en AMD. **El servicio muere con `Out of memory` tras fijarlo**: usaste `NUMAPolicy=bind` (o `--membind`) y el nodo se quedó sin memoria. Cambia a `preferred` o a `interleave`. **`taskset` sigue mostrando todas las CPU**: el override no se aplicó. Comprueba con `systemctl cat redis-server` que aparece el bloque `[Service]` con tus opciones y reinicia el servicio (un `reload` no cambia la afinidad). ## Conclusión Has identificado los nodos NUMA del servidor, medido la penalización de la memoria remota y fijado un servicio a un nodo con opciones nativas de systemd, verificando la ubicación de su memoria con `numastat`. El mismo patrón sirve para repartir varias instancias de un servicio, una por nodo. Como siguientes pasos, puedes combinar la afinidad NUMA con el ajuste del gobernador de frecuencia de la CPU, revisar la configuración de páginas enormes de tu base de datos o fijar también las interrupciones de la tarjeta de red al mismo nodo que la aplicación que más tráfico recibe.