Controladores de memoria integrados: el cambio que aún sientes hoy

¿Te fue útil?

Tu aplicación no se volvió más lenta. Tus suposiciones sí. Si alguna vez has visto una actualización “simple” de CPU empeorar la latencia de una base de datos, o un conjunto de máquinas virtuales donde cada host parece tener una personalidad distinta, te has cruzado con el controlador de memoria integrado (IMC) en su hábitat natural: moldeando el rendimiento en silencio mientras todos discuten el porcentaje de CPU.

El paso de un controlador de memoria en el northbridge a un IMC se vendió como velocidad. Lo entregó. También cambió los modos de fallo, los controles de ajuste y las maneras en que puedes engañarte con los benchmarks. Esto no es nostalgia. Es la razón por la que tus gráficas de producción todavía se ven así.

Qué cambió realmente: del northbridge al IMC

Hace tiempo, la CPU no hablaba con la DRAM directamente. Hablaba con un componente del chipset (el “northbridge”) a través de un front-side bus. El northbridge luego hablaba con la memoria. Ese diseño tenía una crueldad simple: todos los núcleos compartían la misma ruta hacia la RAM, y la latencia de memoria era en gran medida “un número” para todo el socket.

Luego los fabricantes comenzaron a mover el controlador de memoria al paquete de la CPU. Ahora la CPU contiene la lógica que maneja los DIMMs: entrenamiento, timings, refresh, direccionamiento, todo. Esto hizo tres cosas importantes que todavía importan en producción:

  • Latencia menor y más predecible (menos saltos, menos contención en un bus compartido).
  • Más ancho de banda mediante múltiples canales de memoria directamente desde la CPU, escalando por socket.
  • La localidad se volvió un concepto de primera clase: en sistemas multi-socket, cada socket tiene su propia memoria adjunta, y la memoria “remota” es otra bestia.

Desde la perspectiva SRE, el cambio más importante no fue “se volvió más rápido.” Fue esto: la memoria pasó a formar parte de la personalidad de la CPU. Dos servidores con el mismo SKU de CPU pueden comportarse distinto si los DIMMs están poblados de forma diferente, si el intercalado del BIOS difiere, si el clustering NUMA está activado o si la memoria de un socket está degradada.

Y sí: el IMC también acercó ciertos problemas a tu pager. Cuando un canal de memoria se pone inestable, no es solo “algún DIMM.” Puede manifestarse como tormentas de errores corregidos, throttling o un host que parece bien hasta que está bajo carga real. Ya no puedes seguir culpando “al chipset”. La CPU es el chipset ahora, al menos para memoria.

Hechos interesantes y contexto histórico

Aquí hay puntos concretos que vale la pena mantener en tu modelo mental. No son trivialidades; palancas que cambiaron diseños y operaciones.

  1. AMD popularizó los IMC temprano en servidores x86-64 (era Opteron), haciendo práctico NUMA antes de que muchos equipos tuvieran madurez en herramientas.
  2. La generación Nehalem de Intel introdujo IMC en su línea de servidores, y la era del front-side bus prácticamente terminó para el escalado serio multiprocesador.
  3. QPI/UPI y HyperTransport/Infinity Fabric de AMD existen principalmente para mover tráfico de coherencia de caché y acceso remoto a memoria entre sockets una vez que cada socket posee su memoria.
  4. La memoria multi-canal se convirtió en la perilla de rendimiento por defecto: “más GHz” dejó de ser tan efectivo como “más canales poblados correctamente”.
  5. La consciencia NUMA escaló en la pila: planificadores del kernel, allocators, JVMs, bases de datos y hypervisores tuvieron que aprender dónde vive la memoria.
  6. El manejo de errores ECC se volvió más matizado: los IMC registran errores por canal/rank, y el firmware puede tomar acciones como retiro de páginas o deshabilitar un canal.
  7. La frecuencia de memoria a menudo se reduce según la población: añadir más DIMMs por canal puede forzar al IMC a bajar la velocidad para mantener la integridad de la señal.
  8. El “interleaving” se convirtió en una elección estratégica: puede suavizar el ancho de banda, pero también borrar la localidad, dependiendo de cómo se configure.
  9. IMC integrado + alto conteo de núcleos hizo del “ancho de banda por núcleo” un limitador real: más núcleos pueden significar menos memoria por núcleo y más contención.

Latencia, ancho de banda y por qué la “velocidad de CPU” dejó de ser toda la historia

Cuando la gente dice “el IMC hizo la memoria más rápida”, generalmente están comprimiendo dos historias diferentes: latencia y ancho de banda. En producción, confundirlas es cómo terminas ajustando lo equivocado.

Latencia: el impuesto oculto en cada fallo de caché

Las cachés de CPU son rápidas pero pequeñas. Tu carga de trabajo es una negociación con la caché. Si tu conjunto de trabajo cabe, pareces un héroe. Si no cabe, pagas la latencia de DRAM. Los IMC reducen el coste base de alcanzar DRAM comparado con un diseño northbridge, pero también introducen una realidad más dura: en sistemas multi-socket, parte de la DRAM está “cerca” (local) y otra parte está “lejos” (remota).

El acceso remoto a memoria no solo es más lento; es variable. Depende de la contención en el interconector, tráfico de snoop y qué más esté haciendo el sistema. Esa variabilidad es veneno para la latencia cola. Si persigues p99, no solo quieres memoria rápida. Quieres accesos a memoria consistentes.

Ancho de banda: la manguera está por socket, no por rack

Un IMC típicamente expone múltiples canales de memoria. Si los pueblas correctamente obtienes ancho de banda. Si los pueblas perezosamente, obtienes un servidor que parece “bien” bajo carga ligera y luego se desmorona cuando lo presionas.

Los problemas de ancho de banda son sigilosos porque la utilización de CPU puede estar baja mientras el rendimiento es horrible. Estás bloqueado, no ocupado. Por eso “la CPU está al 35%” no es consuelo; es una pista.

Dos reglas cortas que ahorran tiempo

  • Si tu latencia es mala, probablemente estás peleando con la localidad o fallos de caché. Añadir hilos normalmente lo empeora.
  • Si tu rendimiento es pobre, podrías estar limitado por ancho de banda. Añadir canales de memoria o corregir colocación puede ser una ganancia mayor que optimizar código.

Una cita que mantengo en la pared mental, porque es operativamente verdadera: “La propiedad más importante de un programa es si es correcto.” — Donald Knuth. El rendimiento es un segundo cercano, pero la corrección incluye comportamiento predecible bajo carga, que es donde aparecen las realidades IMC/NUMA.

NUMA: la característica que usas por accidente

NUMA (Non-Uniform Memory Access) es la consecuencia natural de los IMC en diseños multi-socket. Cada socket tiene sus propios controladores de memoria y DIMMs adjuntos. Cuando un núcleo en el socket 0 lee memoria adjunta al socket 1, atraviesa un interconector. Eso es memoria remota.

En teoría, NUMA es simple: mantén los hilos cerca de su memoria. En la práctica, es como los arreglos de asientos en una boda. Solo está calmado hasta que llega el primo que se niega a sentarse donde le asignaron.

Modos de fallo NUMA que realmente ves

  • Saltos de latencia tras escalar: añades más hilos de trabajo, se expanden por sockets, las asignaciones de memoria se quedan en el nodo original y ahora la mitad de tus accesos son remotos.
  • Sorpresa en virtualización: asignas una VM de 64 vCPU con 512 GB RAM y el hipervisor la coloca repartida en nodos de forma que el acceso remoto sea el predeterminado.
  • “Un host es más lento”: mismo modelo de CPU, pero diferente población de DIMMs o ajustes de clustering NUMA en BIOS. Felicidades, construiste un clúster heterogéneo sin querer.

Broma corta #1: NUMA significa “Non-Uniform Memory Access”, pero en producción a menudo se lee como “Now U Must Analyze” (Ahora debes analizar).

Interleaving: un cuchillo que corta en ambas direcciones

El interleaving reparte direcciones de memoria entre canales (y a veces entre sockets) para aumentar el paralelismo y suavizar el ancho de banda. Eso es bueno para cargas tolerantes a la latencia y hambrientas de ancho de banda. Puede ser terrible para cargas sensibles a la latencia que dependen de la localidad, porque garantiza tráfico remoto.

No trates “interleaving: habilitado” como una virtud por defecto. Trátalo como una hipótesis que debes validar con tu carga de trabajo.

Almacenamiento e IMC: por qué tu “cuello de botella de disco” suele ser memoria

Voy a molestar a la gente de almacenamiento (lo soy): muchos “problemas de rendimiento de almacenamiento” son problemas de memoria con mejor marketing. Las pilas de almacenamiento modernas consumen mucha memoria: page cache, ARC, caches de metadata, planificadores IO, cifrado, compresión, checksums, buffers de replicación y caché del lado cliente en la propia aplicación.

Con IMC, el ancho de banda y la localidad de memoria determinan qué tan rápido tu sistema puede mover datos a través de la CPU, no solo hacia y desde el disco. NVMe lo dejó dolorosamente obvio: la latencia de almacenamiento bajó lo suficiente como para que los stalls de memoria y la sobrecarga del lado CPU se volviesen el cuello de botella.

Lugares donde IMC/NUMA perjudican sistemas pesados en almacenamiento

  • Canales de checksum y compresión se vuelven ejercicios de ancho de banda de memoria si escaneas bloques grandes.
  • Convergencia red + almacenamiento (p. ej., NVMe/TCP, iSCSI, pilas RDMA) puede terminar con colas RX/TX ancladas a núcleos en un socket mientras las asignaciones de memoria vienen de otro.
  • ZFS ARC y el page cache de Linux pueden volverse pesados en accesos remotos si tus hilos de IO y asignaciones de memoria no están alineados.

Cuando alguien dice, “Los discos están lentos”, pregunta: “¿O somos lentos alimentando a los discos?” Los IMC hicieron esa pregunta más frecuente.

Guía rápida de diagnóstico

Este es el orden que uso cuando el negocio grita y las gráficas mienten. El objetivo es decidir rápido: cómputo CPU, latencia de memoria, ancho de banda de memoria o IO/red. Luego profundizas.

Primero: confirma que no es saturación obvia de IO

  1. Revisa la cola de ejecución y el IO wait. Si la CPU está inactiva pero el load average es alto, estás bloqueado en algún sitio.
  2. Revisa la utilización del dispositivo y colas. Si un NVMe está al 100% con colas profundas, probablemente sea presión real de IO.

Segundo: identifica stalls de memoria y problemas de localidad

  1. Revisa la topología NUMA y las asignaciones. Si la mayor parte de la memoria está asignada en el nodo 0 pero los hilos corren en ambos sockets, pagas penalizaciones remotas.
  2. Revisa contadores de ancho de banda de memoria (las herramientas del proveedor ayudan, pero puedes llegar lejos con perf y herramientas estándar de Linux).

Tercero: verifica población de DIMMs y frecuencia

  1. Comprueba que todos los canales estén poblados como se pretende. Canales faltantes = ancho de banda perdido.
  2. Verifica la velocidad real de memoria. La etiqueta del DIMM es aspiracional; el IMC establece la realidad.

Cuarto: actúa con el menor radio de impacto

  • Prefiere pinning y cambios de política (numactl, systemd CPUAffinity/NUMAPolicy) sobre cambios en BIOS durante un incidente.
  • Cambia una cosa, mide p95/p99 y revierte si la cola empeoró.

Tareas prácticas: comandos, salidas, decisiones (12+)

Estas son realistas en un servidor Linux típico. Existen herramientas específicas de proveedor, pero puedes diagnosticar mucho con utilidades base. Cada tarea incluye: comando, qué significa la salida y qué decisión tomar.

Task 1: Ver la topología NUMA rápidamente

cr0x@server:~$ lscpu | egrep -i 'Socket|NUMA|Core|Thread|Model name'
Model name:                           Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz
Thread(s) per core:                   2
Core(s) per socket:                   20
Socket(s):                            2
NUMA node(s):                         2
NUMA node0 CPU(s):                    0-19,40-59
NUMA node1 CPU(s):                    20-39,60-79

Significado: Dos nodos NUMA; los IDs de CPU mapean a sockets/nodos. Este mapeo es tu vocabulario de pinning.

Decisión: Si la carga es sensible a la latencia, planifica mantener sus hilos más calientes y la memoria en un nodo cuando sea posible (o alinear shards por nodo).

Task 2: Comprobar cómo está distribuida la memoria entre nodos NUMA

cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
node 0 size: 192000 MB
node 0 free: 15000 MB
node 1 cpus: 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
node 1 size: 192000 MB
node 1 free: 160000 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

Significado: El nodo 0 está mayormente lleno mientras que el nodo 1 está casi vacío. Eso es un signo clásico de asignación sesgada o un proceso anclado al nodo 0.

Decisión: Si tus hilos corren en ambos nodos pero la memoria está sesgada, aplica una política NUMA (interleave o bind) por servicio, o corrige la afinidad de CPU para hacer que la asignación coincida.

Task 3: Identificar procesos que causan tráfico de memoria remoto (pistas NUMA)

cr0x@server:~$ numastat -p 12345
Per-node process memory usage (in MBs) for PID 12345 (myservice)
Node 0          Node 1           Total
--------------- --------------- ---------------
Huge            0.00            0.00            0.00
Heap         98000.50         1200.25        99200.75
Stack           50.10           48.90           99.00
Private      1020.00          900.00         1920.00
--------------- --------------- ---------------
Total        100070.60        2149.15       102219.75

Significado: El heap del proceso está abrumadoramente en el nodo 0. Si sus hilos están divididos entre nodos, ocurrirán lecturas remotas.

Decisión: Para servicios sensibles a la latencia, ancla CPUs y memoria al nodo 0 (o mueve el proceso) en lugar de dejarlo flotar.

Task 4: Verificar velocidad real de memoria y población

cr0x@server:~$ sudo dmidecode -t memory | egrep -i 'Locator:|Size:|Speed:|Configured Memory Speed:'
Locator: DIMM_A1
Size: 32 GB
Speed: 3200 MT/s
Configured Memory Speed: 2933 MT/s
Locator: DIMM_B1
Size: 32 GB
Speed: 3200 MT/s
Configured Memory Speed: 2933 MT/s

Significado: DIMMs clasificados para 3200 MT/s están funcionando a 2933 MT/s. Eso puede ser normal dependiendo de la generación de CPU y de DIMMs-por-canal.

Decisión: Si el ancho de banda es un problema, revisa las reglas de población del BIOS y si puedes reducir DIMMs por canal o usar configuraciones soportadas para subir la frecuencia.

Task 5: Confirmar que todos los canales de memoria están activos (sanity rápida)

cr0x@server:~$ sudo lshw -class memory | egrep -i 'bank:|size:|clock:'
       bank:0
       size: 32GiB
       clock: 2933MHz
       bank:1
       size: 32GiB
       clock: 2933MHz

Significado: Al menos puedes ver múltiples bancos poblados. Esto no es un mapa completo de canales, pero captura la clase de errores “faltan la mitad de los DIMMs”.

Decisión: Si esperabas más módulos y no los ves, deja de afinar software y abre un ticket de hardware.

Task 6: Detectar presión de memoria vs caché (¿es realmente memoria?)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           376Gi       210Gi        12Gi       2.0Gi       154Gi       160Gi
Swap:            8Gi       0.0Gi        8Gi

Significado: “available” está saludable. Eso sugiere que no estás thrashing puramente por falta de RAM; los stalls podrían ser por latencia/ancho de banda/localidad en su lugar.

Decisión: No añadas swap o RAM a ciegas. Investiga localidad, ancho de banda y stalls de CPU.

Task 7: Vigilar fallos mayores y CPU steal (revisión de VM)

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 123456  10240 987654    0    0     0     8 3200 9000 35 10 55  0  0
 4  0      0 122900  10240 987620    0    0     0    16 3300 9500 38 12 50  0  0

Significado: No hay swapping (si/so cero), IO wait bajo. Esto apunta lejos del paging y hacia stalls de cómputo/memoria.

Decisión: Pasa a contadores perf y comprobaciones de asignación NUMA.

Task 8: Revisar asignación de memoria por nodo en el kernel (vista macro)

cr0x@server:~$ cat /proc/zoneinfo | egrep -n 'Node 0, zone|Node 1, zone|present|managed'
14:Node 0, zone      Normal
22:  present  50331648
23:  managed  49283072
420:Node 1, zone      Normal
428:  present  50331648
429:  managed  49283072

Significado: Ambos nodos tienen zonas de memoria; no hay un nodo evidentemente offline. Esto ayuda a descartar “el nodo falta” por una mala configuración.

Decisión: Si un nodo tuviera dramáticamente menos memoria gestionada, sospecha ajustes de BIOS (mirroring de memoria, sparing) o problemas de hardware.

Task 9: Ver si el balanceo NUMA automático está habilitado (y decidir si confiar en él)

cr0x@server:~$ cat /proc/sys/kernel/numa_balancing
1

Significado: El balanceo NUMA del kernel está activado. Puede ayudar cargas de propósito general, y también puede introducir overhead o jitter en servicios sensibles a la latencia.

Decisión: Para cargas críticas de cola, haz pruebas con esto desactivado por host o por servicio (existen controles de cgroup en algunas configuraciones); no lo cambies en toda la flota en horas de producción.

Task 10: Encontrar stalls de CPU evidentes (vista perf a alto nivel)

cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-misses,stalled-cycles-frontend,stalled-cycles-backend -I 1000 sleep 5
#           time             counts unit events
     1.000167156      3,201,234,567      cycles
     1.000167156      1,120,456,789      instructions
     1.000167156         34,567,890      cache-misses
     1.000167156      1,050,123,456      stalled-cycles-frontend
     1.000167156      1,980,234,567      stalled-cycles-backend

Significado: Los stalls de backend son enormes en relación con los cycles. Eso suele indicar límites de latencia/ancho de banda de memoria, no falta de CPU.

Decisión: Deja de añadir hilos. Investiga localidad, estructuras de datos amigables con caché y saturación de ancho de banda de memoria.

Task 11: Confirmar que IRQ y colas NIC no están anclando todo a un socket

cr0x@server:~$ cat /proc/interrupts | head -n 5
           CPU0       CPU1       CPU2       CPU3
  24:   1234567          0          0          0   IO-APIC  24-fasteoi   eth0-TxRx-0
  25:         0    9876543          0          0   IO-APIC  25-fasteoi   eth0-TxRx-1
  26:         0          0    7654321          0   IO-APIC  26-fasteoi   eth0-TxRx-2

Significado: Los interrupts están distribuidos entre CPUs, al menos en este fragmento. Si vieras todos los IRQs en CPUs del nodo 0 solamente, esperarías tráfico de memoria cruzado para apps con mucho uso de red.

Decisión: Si los IRQs están sesgados, usa tuning de irqbalance o afinidad manual para alinear las colas NIC con el socket que ejecuta la app.

Task 12: Inspeccionar afinidad de CPU y política NUMA por proceso

cr0x@server:~$ taskset -pc 12345
pid 12345's current affinity list: 0-79

Significado: El proceso puede correr en cualquier sitio. Eso está bien para servicios de throughput; arriesgado para los sensibles a latencia que asignan memoria temprano y luego migran.

Decisión: Considera anclar a un nodo (o usar cpusets) para mantener la programación y la asignación alineadas.

Task 13: Anclar un servicio a un nodo NUMA (experimento controlado)

cr0x@server:~$ sudo systemctl stop myservice
cr0x@server:~$ sudo numactl --cpunodebind=0 --membind=0 /usr/local/bin/myservice --config /etc/myservice/config.yaml
...service starts...

Significado: Has forzado ejecución y asignación de memoria al nodo 0. Esto es una prueba para ver si la memoria remota te estaba perjudicando.

Decisión: Si la latencia p99 mejora y el throughput se mantiene aceptable, hazlo permanente vía la unidad systemd (con política explícita de CPU/memoria) o configuración del orquestador.

Task 14: Comparar la política “interleave memory” (cuando quieres suavizar ancho de banda)

cr0x@server:~$ sudo numactl --interleave=all /usr/local/bin/batch-job --input /data/scan.dat
...job runs...

Significado: Las asignaciones de memoria se reparten entre nodos, lo que puede aumentar el ancho de banda agregado y reducir hotspots.

Decisión: Usa esto para cargas batch/analíticas que priorizan throughput sobre latencia cola. No lo uses para servicios sensibles a p99 sin medición.

Task 15: Comprobar correcciones ECC (alerta temprana para “lentitud misteriosa”)

cr0x@server:~$ sudo dmesg -T | egrep -i 'EDAC|ecc|corrected|uncorrected' | tail -n 5
[Mon Jan  8 10:42:12 2026] EDAC MC0: 1 CE memory read error on CPU_SrcID#0_Ha#0_Chan#1_DIMM#0 (channel:1 slot:0 page:0x12345 offset:0x0 grain:32 syndrome:0x0)
[Mon Jan  8 10:42:13 2026] EDAC MC0: 1 CE memory read error on CPU_SrcID#0_Ha#0_Chan#1_DIMM#0 (channel:1 slot:0 page:0x12346 offset:0x0 grain:32 syndrome:0x0)

Significado: Errores corregidos están ocurriendo en un canal/DIMM específico. El sistema está “bien”, hasta que no lo está. Algunas plataformas además reducirán velocidad o retirarán páginas.

Decisión: Si los errores corregidos aumentan en tendencia, programa el reemplazo del DIMM. No esperes a un error no corregido para hacerlo.

Task 16: Validar localidad de memoria desde un proceso en ejecución (vista rápida)

cr0x@server:~$ grep -E 'Cpus_allowed_list|Mems_allowed_list' /proc/12345/status
Cpus_allowed_list:	0-79
Mems_allowed_list:	0-1

Significado: Sin restricciones: el proceso puede asignar de cualquier nodo y correr en cualquier CPU. Flexibilidad no es lo mismo que rendimiento.

Decisión: Para latencia estable y baja, ajusta esto mediante cgroups cpuset o wrappers numactl para reducir el churn entre nodos.

Tres mini-historias corporativas desde el frente

1) Incidente causado por una suposición errónea: “Toda la RAM está a la misma distancia”

El equipo estaba migrando una API sensible a la latencia de servidores antiguos de un solo socket a flamantes máquinas de dos sockets. Mismo kernel, mismas imágenes de contenedor, mismo balanceador de carga. Los nuevos hosts tenían más cores, más RAM y la clase de hoja de especificaciones que hace que compras y aprovisionamiento se sientan como ingeniería de rendimiento.

En pocas horas, la latencia p99 subió. No poco. Suficiente para que las reintentos se apilaran y los servicios aguas arriba amplificaran el problema. La utilización de CPU parecía correcta, los discos aburridos y las gráficas de red la usual mentira: “todo está verde.” La gente discutía sobre garbage collection y pools de hilos porque eso es lo que se hace cuando no se ve el cuello de botella.

La suposición errónea fue simple: trataron la memoria como un pool uniforme. La aplicación arrancó en un socket, asignó la mayor parte del heap localmente, luego escaló hilos de trabajo a ambos sockets. La mitad de los trabajadores ahora consumía memoria remota. El throughput estaba bien, la latencia cola no.

La solución no fue exótica. Anclaron el proceso a un nodo NUMA y redujeron ligeramente el número de workers. La latencia se estabilizó de inmediato. Luego re-arquitectaron el servicio para shardear por nodo y ejecutar dos instancias por host, cada una ligada localmente. El hardware no mejoró; el modelo mental sí.

2) Optimización que salió mal: “Intercalar todo por justicia”

Otra compañía operaba un clúster de cargas mixtas: algo de analytics batch, servicios interactivos al usuario y algunos sistemas stateful que todos fingían que eran stateless. Alguien activó un interleaving agresivo en BIOS en un conjunto de hosts porque “mejora el ancho de banda” y “hace el uso de memoria justo”. Ambas cosas son verdad del mismo modo que “conducir más rápido reduce el tiempo de viaje” es verdad.

Los jobs batch mejoraron. Los servicios interactivos se volvieron raros. No consistentemente más lentos—peores. Se volvieron espigados. El p50 casi no cambió, pero el p99 desarrolló picos. Los ingenieros persiguieron los picos por los sospechosos habituales: paradas de GC, vecinos ruidosos, upgrades de kernel, un despliegue específico. Nada se reproducía confiablemente porque el disparador era: “cada vez que el servicio tocaba memoria que ahora tenía que rebotar entre sockets”.

Eventualmente alguien comparó dos hosts lado a lado y notó la diferencia en BIOS. Revirtieron el interleaving solo en la capa de latencia, dejándolo activado en la capa batch. La lección no fue “el interleaving es malo.” La lección fue: deja de aplicar optimizaciones de throughput a cargas de cola de latencia y luego asombrarte cuando tus SLOs se quejan.

También añadieron una verificación preflight en el aprovisionamiento: registrar ajustes NUMA y de intercalado en los hechos del host, y bloquear la admisión si no coincide con el rol. Política aburrida, gran impacto.

3) Práctica aburrida pero correcta que salvó el día: “Población uniforme de DIMM y validación del host”

Esta es poco glamorosa, por eso funcionó. Un equipo de plataforma tenía una regla: cada clase de servidor debe tener población de DIMM idéntica entre canales, y cada host debe pasar un script de validación de topología hardware antes de unirse a un pool. Sin excepciones de “lo arreglamos después”.

Procurement intentó sustituir DIMMs durante una escasez de suministro. Misma capacidad, diferentes ranks y velocidades. Arrancaría, claro. Pero también forzaría el downclock en ese socket y cambiaría el envelope de rendimiento. El script de validación marcó la discrepancia: la velocidad de memoria configurada no coincidía con el perfil esperado y un canal mostró un conteo anormal de errores durante el burn-in.

Ese host nunca llegó a la capa de base de datos de producción. En cambio fue desviado a un pool de dev donde la variación de rendimiento es una molestia, no un evento de ingresos. Más tarde, un lote similar de DIMMs mostró errores corregidos elevados en otra región. Porque el equipo tenía datos de validación base, pudieron correlacionar el problema rápidamente y reemplazar módulos preventivamente.

Sin magia. Solo consistencia, medición y la disciplina de tratar la topología hardware como parte del contrato del software.

Errores comunes: síntomas → causa raíz → solución

Si los IMC y NUMA tuvieran un don, es que hacen caras las suposiciones equivocadas. Aquí patrones que veo repetidamente, en lenguaje que entiende tu pager.

1) Síntoma: la latencia p99 empeora tras una actualización a “servidor más grande”

  • Causa raíz: Los hilos se dispersan entre sockets; las asignaciones de memoria permanecieron locales en un socket; el acceso remoto se volvió común.
  • Solución: Ejecuta una instancia por nodo NUMA, o ancla CPUs/memoria con cpusets/numactl. Verifica con numastat y contadores perf de stalls.

2) Síntoma: el throughput se estanca pronto; la utilización de CPU parece baja

  • Causa raíz: Saturación de ancho de banda de memoria o stalls de backend; los cores esperan por memoria.
  • Solución: Reduce concurrencia, mejora localidad de datos, asegura que todos los canales estén poblados y valida que la frecuencia de memoria no se haya downclockeado inesperadamente.

3) Síntoma: un host es consistentemente más lento que sus pares

  • Causa raíz: Desajuste en la población de DIMM, diferentes ajustes BIOS de interleaving/clustering, o un canal de memoria degradado.
  • Solución: Compara dmidecode configured speed, topología numactl, logs dmesg EDAC. Aísla el host hasta que coincida con el perfil del pool.

4) Síntoma: latencia espigada bajo carga intensiva de red

  • Causa raíz: Interrupts NIC y hilos workers en un socket mientras buffers/asignaciones están en otro; picos de tráfico cruzado entre nodos.
  • Solución: Alinea la afinidad de IRQ y el pinning de procesos al mismo nodo NUMA; asegura que las colas RSS se distribuyan correctamente.

5) Síntoma: “Añadimos RAM y se puso más lento”

  • Causa raíz: Añadir DIMMs incrementó DIMMs-por-canal y forzó downclock de memoria; o cambió la mezcla de ranks afectando timings.
  • Solución: Revisa la velocidad de memoria configurada; sigue la guía de población de la plataforma; prefiere DIMMs de mayor capacidad y menos por canal si la velocidad importa.

6) Síntoma: latencia bien en staging, peor en producción

  • Causa raíz: Staging es single-socket o topología NUMA menor; producción es multi-socket con comportamiento de memoria remota.
  • Solución: Prueba en una topología representativa. Si no puedes, aplica pinning y limita el tamaño de la instancia para que quepa en un nodo.

7) Síntoma: reinicios aleatorios o eventos “MCE”, precedidos por ralentizaciones sutiles

  • Causa raíz: Errores ECC escalando; retiro de páginas; inestabilidad de canal de memoria interactuando con comportamiento del IMC.
  • Solución: Monitorea logs EDAC/MCE y tasas de error; reemplaza DIMMs proactivamente; no trates los errores corregidos como “inofensivos”.

Broma corta #2: ECC es como el cinturón de seguridad—solo lo notas cuando está haciendo su trabajo, y de cualquier forma arruina tu día.

Listas de verificación / plan paso a paso

Lista: incorporar una nueva clase de servidor en un pool sensible a latencia

  1. Topología base: registra la salida de lscpu y numactl --hardware por clase de host.
  2. Validar población de DIMM: asegura que todos los canales estén poblados como se diseñó; no “hosts mitad configurados”.
  3. Confirmar velocidad de memoria configurada: captura dmidecode -t memory y compara con lo esperado.
  4. Comprobar consistencia de políticas BIOS: interleaving, clustering NUMA, modo power/performance—mantenlo uniforme para el pool.
  5. Burn-in con contadores: ejecuta una carga que fuerce ancho de banda y vigila errores ECC corregidos.
  6. Registrar hechos del host: almacena topología y firmas BIOS; bloquea admisión si hay discrepancia.

Lista: respuesta a incidentes cuando la latencia se dispara

  1. Confirmar alcance: ¿un host, una AZ o toda la flota?
  2. Revisar sesgo NUMA: numactl --hardware, numastat -p para el mayor ofensor.
  3. Revisar stalls: perf stat para stalls de backend y cache misses.
  4. Revisar logs ECC: salida EDAC de dmesg por tormentas de errores corregidos.
  5. Mitigar de forma segura: ancla el servicio a un nodo, reduce concurrencia o mueve tráfico fuera del host.
  6. Sólo entonces tocar BIOS: programa los cambios; no experimentes en caliente a menos que disfrutes escribir postmortems.

Plan paso a paso: afinar un servicio para NUMA sin romperlo

  1. Medir línea base: captura p50/p95/p99, throughput, utilización CPU y uso de memoria.
  2. Mapear hilos a CPUs: identifica dónde corren los hilos y dónde se asigna la memoria (taskset + numastat).
  3. Elegir estrategia:
    • Latencia primero: ejecuta una instancia por nodo NUMA, ancla memoria y CPUs localmente.
    • Throughput primero: intercale memoria, distribuye IRQs, acepta algo de tráfico remoto.
  4. Aplicar mínimamente: empieza con un wrapper numactl o afinidad systemd; evita cambios en BIOS al principio.
  5. Volver a medir: especialmente p99 y jitter. Si p50 mejora pero p99 empeora, no ganaste.
  6. Fijar la política: codifícala en el despliegue (unidad systemd, Kubernetes CPU manager, configuración de hypervisor).
  7. Guardarraíl: alerta sobre desequilibrio NUMA y correcciones ECC; aisla hosts que se desvíen.

Preguntas frecuentes

1) ¿Un controlador de memoria integrado siempre es más rápido?

Latencia menor y mayor ancho de banda son típicos, sí. Pero “más rápido” se vuelve condicional: la memoria local es rápida; la memoria remota es más lenta; la configuración determina si te beneficias.

2) ¿Por qué mi p99 empeoró al pasar de 1-socket a 2-socket?

NUMA. Probablemente tu carga empezó a usar memoria remota. La solución es mantener hilos y memoria locales (pinning, shardear o una instancia por nodo).

3) ¿Debo activar el interleaving de memoria?

Para trabajo batch intensivo en throughput, a menudo sí. Para servicios sensibles a la latencia, puede aumentar accesos remotos y jitter. Mide con tu carga; no confíes en el folklore.

4) ¿Cómo sé si estoy limitado por ancho de banda o por latencia?

Estar limitado por ancho de banda suele mostrar un plateau de throughput con utilización de CPU relativamente baja y muchos stalls de backend; estar limitado por latencia muestra sensibilidad del p99 a accesos remotos y fallos de caché. Usa contadores perf de stalls más comprobaciones de asignación NUMA.

5) ¿Por qué añadir DIMMs a veces reduce la velocidad de memoria?

Más DIMMs por canal incrementa la carga eléctrica. El IMC puede bajar la frecuencia para mantener estabilidad. Eso puede reducir ancho de banda y aumentar un poco la latencia, dependiendo de la generación y timings.

6) ¿El balanceo NUMA automático del kernel es suficiente?

Ayuda cargas genéricas, pero no sustituye la colocación intencional en sistemas críticos de latencia. También puede añadir overhead e impredecibilidad. Trátalo como una herramienta, no como una garantía.

7) ¿Cómo se relaciona esto con la virtualización?

Las VMs pueden abarcar nodos NUMA. Si la colocación de vCPU y memoria no se alinea, la memoria remota será la predeterminada. Dimensiona VMs para que quepan en un nodo cuando sea posible, o usa reglas de colocación NUMA-aware.

8) ¿Qué es lo más práctico para empezar con una base de datos?

Manténla local. Ejecuta una instancia ligada a un nodo (si cabe), o múltiples instancias/shards por nodo. Luego valida con numastat y métricas de latencia.

9) ¿Los IMC cambian la fiabilidad, no solo el rendimiento?

Cambian la observabilidad y el manejo. ECC, retiro de páginas y degradación de canales se manifiestan a través del reporte del IMC. Debes monitorizar errores corregidos y tomar tendencias en serio.

10) ¿Y los sistemas de un solo socket—me importa?

Sí, pero de forma distinta. Aún te importan los canales de memoria, la población de DIMM y la velocidad configurada. La complejidad NUMA es menor, pero los efectos de ancho de banda y downclocking siguen mordiendo.

Próximos pasos prácticos

Si operas sistemas en producción, las acciones no son filosóficas:

  1. Estandariza la topología hardware por pool: misma población de DIMM, mismas políticas BIOS de memoria, mismos ajustes NUMA.
  2. Haz visible NUMA: paneles para uso de memoria por nodo, indicadores de accesos locales vs remotos (aunque aproximados) y señales de stalls de backend.
  3. Elige una estrategia de colocación por carga:
    • Capa de latencia: anclar y shardear por nodo, limitar tamaño de instancia, evitar el interleaving “útil”.
    • Capa batch: intercalar donde ayude, perseguir throughput, aceptar variabilidad.
  4. Operativiza ECC: alerta en tasas de errores corregidos, aisla hosts con picos, reemplaza DIMMs antes de que el IMC escale la situación por ti.
  5. Prueba en topología representativa: un staging de un solo socket es una mentira agradable. Seguirá mintiendo hasta que producción te lo enseñe.

El cambio al IMC no fue solo un cambio de arquitectura. Fue un cambio de contrato entre software y hardware. Si todavía tratas la memoria como un pool plano y uniforme, estás ejecutando el modelo mental de ayer en las máquinas de hoy—y las máquinas felizmente te cobrarán intereses.

← Anterior
Pentium FDIV: el error matemático que avergonzó a Intel a nivel mundial
Siguiente →
Cuando las herramientas “limpieza” causaron desorden: utilidades confiables que fallaron

Deja un comentario