Tormentas de IRQ y latencia extraña en Debian 13: revisa irqbalance y corrige interrupciones

¿Te fue útil?

Todo parece “bien” hasta que deja de estarlo. Las gráficas de latencia se vuelven dentadas. Una base de datos que llevaba meses aburrida empieza a tartamudear cada 30–90 segundos. El p99 se dispara mientras el “idle” del CPU sigue diciendo que está relajado. Entonces miras un núcleo atado al 100% en ksoftirqd y te das cuenta de que no estás persiguiendo un problema de la aplicación.

En Debian 13, las tormentas de IRQ y el desequilibrio de interrupciones pueden parecer actividad paranormal: los paquetes llegan, los discos completan, pero tu planificador y las colas están perdiendo la batalla. La solución no es “reinicia y reza”. Es medir interrupciones seriamente, revisar irqbalance y tomar decisiones intencionales sobre afinidad, número de colas y offloads.

Guía rápida de diagnóstico

Si estás de guardia y el pager está sonando, necesitas una secuencia que acote la búsqueda en minutos. No empieces por “afinar”. Empieza por demostrar a dónde se va el tiempo.

Primero: confirma si las interrupciones/softirqs son el cuello de botella

  • Comprueba si un CPU está al máximo y no es userland.
  • Revisa las tasas de softirq y los mayores culpables (NET_RX, BLOCK, TIMER).
  • Comprueba si las interrupciones se están acumulando en CPU0 (clásico) o en un único nodo NUMA.

Segundo: identifica qué dispositivo genera la presión

  • Mapea las IRQ calientes a dispositivos (IRQ de colas NIC, vectores MSI-X de NVMe, líneas HBA).
  • Correlaciona con el momento de la carga (picos de red, flush de almacenamiento, snapshot, backup).
  • Verifica si el dispositivo tiene suficientes colas y si se están usando.

Tercero: decide entre “dejar que irqbalance lo haga” vs “fijarlo deliberadamente”

  • Si es un servidor de propósito general con cargas cambiantes: prefiere irqbalance con valores sensatos.
  • Si es un sistema sensible a latencia, con CPUs fijados (DPDK, casi-realtime, trading, audio, telco): deshabilita irqbalance y fija interrupciones con intención.
  • Si usas aislamiento de CPU (isolcpus, nohz_full): las interrupciones deben mantenerse fuera de los núcleos aislados, o habrás montado ruedas de carrito en un coche de carreras.

Cuarto: verifica que mejoraste la métrica correcta

  • Mide la latencia end-to-end p95/p99, no solo “el CPU se ve mejor”.
  • Confirma que las IRQ calientes están repartidas (o fijadas correctamente) y que las tasas son estables.
  • Vigila regresiones: pérdida de paquetes, retransmisiones aumentadas, mayor tasa de interrupciones por coalescing deshabilitado.

Qué estás viendo realmente (tormentas IRQ, softirqs y latencia)

Una “tormenta de IRQ” normalmente no es una tormenta eléctrica literal. Es el kernel siendo interrumpido con tanta frecuencia —o manejando tanto trabajo diferido— que el trabajo real no puede ejecutarse con fluidez. Los síntomas a menudo parecen rarezas de la aplicación: timeouts, IO detenido, pequeños “picos” y jitter que no encaja con el CPU medio o el rendimiento.

En Linux moderno, las interrupciones hardware (top halves) se mantienen cortas. El trabajo pesado se difiere a softirqs (bottom halves) y a kthreads como ksoftirqd/N. Si la carga de softirq es alta, el kernel puede procesarlos en el contexto de la tarea interrumpida (rápido, bueno para throughput) o en ksoftirqd (preemptable, pero puede retrasarse). Si ves ksoftirqd dominando un CPU, vas retrasado.

El desequilibrio de interrupciones es la versión más silenciosa de las tormentas. La tasa total de interrupciones podría estar bien, pero si se concentra en un CPU (a menudo CPU0), ese núcleo se convierte en tu cuello de botella de facto. El planificador puede mostrar mucho tiempo idle en otros núcleos, lo que conduce al clásico diagnóstico erróneo: “el CPU está bien”. No lo está. Un núcleo está en llamas mientras los demás están de brunch.

Dos patrones comunes:

  • Sobrecarga en CPU0: muchos drivers por defecto usan la cola 0 o un único vector salvo que RSS/MSI-X y afinidad estén configurados. Los valores predeterminados de afinidad en arranque también pueden sesgar hacia CPU0.
  • Desajuste NUMA: las interrupciones se ejecutan en CPUs lejos de la localidad de memoria del dispositivo PCIe. Eso añade latencia y consume ancho de banda de interconexión. Es la muerte por mil fallos de caché.

También está la trampa de la “moderación de interrupciones”: si el coalescing es demasiado agresivo, reduces la tasa de interrupciones pero aumentas la latencia porque la NIC espera más antes de interrumpir. Muy poco coalescing y puedes fundir un núcleo con interrupciones. Afinar es un trade-off: decides cuánto jitter puedes tolerar para evitar la sobrecarga.

Una cita que debe resonar en tu cabeza mientras trabajas: La esperanza no es una estrategia. — General Gordon R. Sullivan

Broma #1: Las tormentas de interrupciones son como las reuniones—si tienes demasiadas, nada más se hace.

Hechos interesantes y contexto (por qué sigue ocurriendo)

  • 1) “irqbalance” existe porque el SMP hizo dolorosa la ruta ingenua de interrupciones. Los primeros sistemas multiprocesador Linux solían dirigir las interrupciones al CPU de arranque, produciendo puntos calientes en CPU0 que parecían “Linux va lento”.
  • 2) MSI-X cambió las reglas. Message Signaled Interrupts (y MSI-X) permiten que los dispositivos generen interrupciones mediante mensajes en memoria y soporten muchos vectores—perfecto para NICs multi-cola y NVMe.
  • 3) NAPI se inventó para detener el livelock en recepción de paquetes. La red en Linux pasó a un modelo de sondeo que mitiga interrupciones porque el RX puramente basado en interrupciones podía colapsar bajo altas tasas de paquetes.
  • 4) Los softirqs son por-CPU por diseño. Eso es bueno para la localidad de caché, pero también significa que “un CPU ahogado en NET_RX” puede dejar sin CPU a ese núcleo aunque otros estén ociosos.
  • 5) “Tormenta de IRQ” solía significar hardware roto más a menudo. Hoy con frecuencia es colas/coalescing mal ajustados o cambios en la carga (por ejemplo, un servicio nuevo enviando paquetes de 64 bytes a un millón por segundo).
  • 6) El rendimiento de NVMe viene con volumen de interrupciones por completado. Muchas IO pequeñas a alto IOPS pueden generar una alta tasa de eventos de completado; los vectores MSI-X y el mapeo de colas importan.
  • 7) Las funciones de aislamiento de CPU hicieron la ubicación de interrupciones más importante. isolcpus y nohz_full pueden mejorar latencia, pero solo si mantienes las interrupciones y tareas del kernel fuera de los núcleos aislados.
  • 8) Los offloads modernos de NIC no siempre son tus amigos. GRO/LRO/TSO pueden reducir CPU pero aumentar latencia o jitter, y algunas cargas (pequeñas RPC) odian su comportamiento de buffering.
  • 9) “irqbalance” ha madurado hasta ser una política, no magia. Toma decisiones basadas en heurísticas de carga. Esas heurísticas pueden estar equivocadas para sistemas especializados.

Herramientas y principios: cómo Debian 13 maneja las interrupciones

Debian 13 es simplemente Linux con opiniones y empaquetado. El kernel usa /proc/interrupts como la verdad cruda. Herramientas como irqbalance aplican políticas. Los drivers exponen perillas a través de /sys y ethtool. Tu trabajo es decidir qué significa “bueno” para tu carga y luego aplicarlo.

Interrupciones hard vs softirqs: lo que importa para la latencia

El contexto de las interrupciones hard está muy restringido. La mayor parte del trabajo se pasa a softirqs. Cuando la carga de softirq es alta, el kernel puede ejecutar el procesamiento de softirq en el contexto de la tarea interrumpida (rápido) o en ksoftirqd (preemtable, pero puede atrasarse). Si ves ksoftirqd dominando un CPU, vas por detrás.

Afinidad: “qué CPU maneja esta interrupción?”

Cada IRQ tiene una máscara de afinidad. Para MSI-X, cada vector es efectivamente su propia IRQ y puede balancearse entre CPUs. Para interrupciones basadas en líneas legacy, las opciones son limitadas y el compartir puede volverse feo.

Colas: dispositivos multi-cola y por qué una sola cola es una tragedia

Para NICs, multi-queue + RSS permiten que flujos entrantes hagan hash entre colas RX, cada una con su propio vector de interrupción. Para dispositivos de bloque, blk-mq mapea colas de IO a CPUs. Esto no es solo throughput. También es una historia de latencia: reduce la contención de locks y mantiene la ruta caliente local.

NUMA: no pagues por la distancia si no es necesario

En sistemas multi-socket, colocar las interrupciones de la NIC en CPUs locales al root complex PCIe de la NIC reduce tráfico cruzado entre nodos. Debian no adivinará tu topología correctamente siempre. Necesitas comprobarlo.

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

Estos son los movimientos que arreglan incidentes reales. Cada tarea incluye: un comando, qué significa la salida y la decisión que tomas a partir de ella. Ejecútalos como root cuando sea necesario.

Task 1: Confirma que el síntoma es presión de interrupciones/softirq

cr0x@server:~$ top -H -b -n 1 | head -n 25
top - 10:11:12 up 12 days,  3:44,  1 user,  load average: 6.14, 5.98, 5.22
Threads:  421 total,   3 running, 418 sleeping,   0 stopped,   0 zombie
%Cpu(s):  8.0 us,  2.1 sy,  0.0 ni, 78.4 id,  0.0 wa,  0.0 hi, 11.5 si,  0.0 st
PID   USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  32  root      20   0       0      0      0 R  96.7   0.0  78:22.41 ksoftirqd/3

Qué significa: Un alto si (softirq) más un hilo ksoftirqd/N caliente es una pistola humeante. Tu tiempo CPU se está gastando en trabajo de interrupciones diferidas, comúnmente en red.

Decisión: Pasa inmediatamente a medir softirqs y mapear interrupciones a dispositivos. No afines la aplicación aún.

Task 2: Ver qué clases de softirq están calientes

cr0x@server:~$ cat /proc/softirqs
                    CPU0       CPU1       CPU2       CPU3
          HI:          0          0          0          0
       TIMER:   10234567    9123456    9234567    9012345
      NET_TX:       884      12933      11822      11540
      NET_RX:  90345678   1245678   1300045   1219990
       BLOCK:    112233     110998     111102     109876
    IRQ_POLL:          0          0          0          0
     TASKLET:      3333       2888       3011       2999
       SCHED:    400000     398000     402000     399000
     HRTIMER:       222       211        219        210
         RCU:    600000     590000     610000     605000

Qué significa: NET_RX masivamente sesgado a CPU0 grita “el procesamiento RX está concentrado”. Esto a menudo se correlaciona con la interrupción de la cola RX cayendo en CPU0.

Decisión: Inspecciona /proc/interrupts y la configuración de colas de la NIC. Tu objetivo es distribuir las interrupciones de las colas RX o habilitar/verificar RSS.

Task 3: Identifica líneas IRQ calientes y si están desequilibradas

cr0x@server:~$ awk 'NR==1 || /eth0|nvme|i915|virtio|mlx|ixgbe|enp/ {print}' /proc/interrupts
            CPU0       CPU1       CPU2       CPU3
  35:   81234567     120332     110221     118877   PCI-MSI 524288-edge      eth0-TxRx-0
  36:      1200   22100333     118900     119010   PCI-MSI 524289-edge      eth0-TxRx-1
  37:      1100     111200   20300111     119100   PCI-MSI 524290-edge      eth0-TxRx-2
  38:       900     112300     120010   19899110   PCI-MSI 524291-edge      eth0-TxRx-3
  92:    900000     910000     905000     899000   PCI-MSI 1048576-edge      nvme0q0
  93:    120000     118000     119000     121000   PCI-MSI 1048577-edge      nvme0q1

Qué significa: Aquí la IRQ de la cola 0 de la NIC está absurdamente caliente en CPU0. Otras colas se ven más saludables. Esto es un desequilibrio clásico.

Decisión: Arregla la afinidad del vector caliente y confirma que RSS distribuye flujos. Si la cola 0 es legítimamente la más ocupada por hashing, aumenta colas o ajusta la indirection de RSS.

Task 4: Confirma si irqbalance se está ejecutando y qué hace

cr0x@server:~$ systemctl status irqbalance --no-pager
● irqbalance.service - irqbalance daemon
     Loaded: loaded (/lib/systemd/system/irqbalance.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-29 08:12:01 UTC; 1 day 02:00 ago
   Main PID: 812 (irqbalance)
      Tasks: 1 (limit: 38121)
     Memory: 3.8M
        CPU: 21min 33.120s

Qué significa: irqbalance está en ejecución, así que o (a) no puede mover esa IRQ (algunas son fijas/no movibles), (b) está configurado para evitar ciertos CPUs, o (c) está tomando una mala decisión para tu carga.

Decisión: Revisa la configuración de irqbalance y las máscaras de afinidad de IRQ. Decide si afinarlas o sobreescribir IRQs específicas manualmente.

Task 5: Comprueba la máscara de afinidad actual para una IRQ concreta

cr0x@server:~$ cat /proc/irq/35/smp_affinity_list
0

Qué significa: La IRQ 35 está fijada solo al CPU0. irqbalance no puede ayudar si algo (o alguien) la ha fijado.

Decisión: Si no es intencional, cámbiala. Si CPU0 está reservado para housekeeping y quieres eso, fija la IRQ en otro sitio.

Task 6: Mueve una IRQ caliente a otro CPU (prueba rápida)

cr0x@server:~$ echo 2 > /proc/irq/35/smp_affinity
cr0x@server:~$ cat /proc/irq/35/smp_affinity_list
1

Qué significa: La IRQ ahora está dirigida al CPU1 (bit de máscara 1). Es un instrumento contundente pero excelente para probar causalidad.

Decisión: Si la latencia mejora inmediatamente y ksoftirqd se calma, has confirmado que la colocación de interrupciones es el problema. Luego aplica la solución duradera (mapeo consciente de colas, política de irqbalance, colocación NUMA-aware).

Task 7: Identifica la NIC, el driver y la ubicación del bus (pistas NUMA)

cr0x@server:~$ ethtool -i eth0
driver: ixgbe
version: 6.1.0
firmware-version: 0x800003e2
bus-info: 0000:3b:00.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: yes

Qué significa: bus-info es la dirección PCI. Puedes mapear eso a un nodo NUMA y a la localidad de CPUs.

Decisión: Mantén las interrupciones en CPUs locales al nodo NUMA de la NIC siempre que sea posible.

Task 8: Encuentra el nodo NUMA del dispositivo y la lista de CPUs locales

cr0x@server:~$ cat /sys/bus/pci/devices/0000:3b:00.0/numa_node
1
cr0x@server:~$ cat /sys/devices/system/node/node1/cpulist
16-31

Qué significa: La NIC está conectada al nodo NUMA 1, CPUs locales 16–31. Si tus IRQs están en CPU0–3, estás pagando costes entre nodos.

Decisión: Coloca las IRQ de la NIC en CPUs 16–31, y considera ejecutar cargas intensivas en NIC en esos CPUs también.

Task 9: Comprueba el recuento de colas/canales de la NIC (¿tienes suficientes colas?)

cr0x@server:~$ ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:	16
TX:	16
Other:	0
Combined:	16
Current hardware settings:
RX:	0
TX:	0
Other:	0
Combined:	4

Qué significa: La NIC soporta hasta 16 canales combinados pero actualmente usa 4. Si tienes muchos núcleos y altas tasas de paquetes, 4 colas pueden ser un cuello de botella.

Decisión: Aumenta los canales combinados si la carga lo beneficia y el sistema tiene CPU para soportarlo. Pero no te excedas; más colas pueden significar más overhead y peor localidad de caché.

Task 10: Aumenta las colas combinadas de la NIC (con cuidado) y vuelve a comprobar interrupciones

cr0x@server:~$ sudo ethtool -L eth0 combined 8
cr0x@server:~$ ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX:	16
TX:	16
Other:	0
Combined:	16
Current hardware settings:
RX:	0
TX:	0
Other:	0
Combined:	8

Qué significa: Ahora tienes 8 pares de colas. Eso debería crear más vectores MSI-X y distribuir mejor el trabajo—si RSS está configurado y tus flujos hacen buen hash.

Decisión: Revisa /proc/interrupts tras 30–60 segundos bajo carga. Si una cola sigue dominando, tu hashing/indirection o patrón de tráfico puede ser la limitación.

Task 11: Inspecciona la indirection y la clave de hash RSS (¿se está distribuyendo el tráfico?)

cr0x@server:~$ ethtool -x eth0 | head -n 25
RX flow hash indirection table for eth0 with 8 RX ring(s):
    0: 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
   16: 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
   32: 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
   48: 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
RSS hash key:
6d:5a:2c:...:91

Qué significa: La tabla parece distribuida uniformemente. Eso es bueno. Si no lo está, o todo son ceros, efectivamente eres single-queue.

Decisión: Si la distribución es pobre, ajusta la tabla de indirection (avanzado) o corrige ajustes de driver/firmware. Si tu carga es un único flujo (un gran stream TCP), RSS no ayudará mucho; necesitas shardear la aplicación o cambiar el transporte.

Task 12: Comprueba y ajusta el coalescing de interrupciones (compensación latencia vs CPU)

cr0x@server:~$ ethtool -c eth0
Coalesce parameters for eth0:
Adaptive RX: on  TX: on
rx-usecs: 50
rx-frames: 0
tx-usecs: 50
tx-frames: 0

Qué significa: El coalescing adaptativo está activado; la NIC/driver cambiará el coalescing según el tráfico. Eso suele ser adecuado para uso general, pero puede introducir variación en la latencia.

Decisión: Para cargas con cola de cola estricta, considera desactivar el coalescing adaptativo y usar valores fijos conservadores. Valida con mediciones reales de latencia, no con sensaciones.

Task 13: Identifica paquetes descartados o desbordes de backlog (síntoma de sobrecarga NET_RX)

cr0x@server:~$ nstat | egrep 'TcpExtListenOverflows|IpInDiscards|UdpInErrors|TcpExtTCPBacklogDrop'
TcpExtTCPBacklogDrop          123
IpInDiscards                  456

Qué significa: Estás descartando paquetes en la pila. Esto puede ocurrir cuando el procesamiento de softirq no da abasto, o cuando la aplicación no puede drenar aceptaciones/lecturas lo suficientemente rápido.

Decisión: Arregla la distribución de interrupciones primero. Luego considera ajustar backlog de sockets y capacidad de la aplicación. No tapes un punto caliente de IRQ con buffers más grandes a menos que disfrutes de fallos retardados.

Task 14: Busca mensajes “IRQ nadie se hizo cargo” y advertencias del kernel

cr0x@server:~$ journalctl -k -b | egrep -i 'irq|nobody cared|soft lockup|hard lockup' | tail -n 20
Dec 30 09:55:18 server kernel: irq 35: nobody cared (try booting with the "irqpoll" option)
Dec 30 09:55:18 server kernel: Disabling IRQ #35

Qué significa: Esto es serio. Una IRQ se volvió tan ruidosa o mal comportada que el kernel decidió que estaba rota y la deshabilitó. Eso puede dejar tu NIC o almacenamiento fuera de línea en cámara lenta.

Decisión: Trátalo primero como problema de driver/firmware/hardware. Revisa ajustes de BIOS, actualiza firmware, verifica estabilidad de MSI/MSI-X y examina si los parámetros de moderación de interrupciones son patológicos.

Task 15: Verifica que el mapeo de colas/IRQ de NVMe sea sensato

cr0x@server:~$ ls -1 /proc/interrupts | head -n 0
cr0x@server:~$ grep -E 'nvme[0-9]q' /proc/interrupts | head -n 10
 92:    900000     910000     905000     899000   PCI-MSI 1048576-edge      nvme0q0
 93:    120000     118000     119000     121000   PCI-MSI 1048577-edge      nvme0q1
 94:    119000     121000     118000     120000   PCI-MSI 1048578-edge      nvme0q2

Qué significa: Existen múltiples colas NVMe y las interrupciones están relativamente repartidas. Si ves solo nvme0q0 caliente y otras inactivas, el dispositivo podría estar limitado a una cola o la carga es efectivamente single-threaded.

Decisión: Si las interrupciones NVMe están concentradas, revisa nvme_core.default_ps_max_latency_us, parámetros del driver y si la capa de bloque está mapeando colas a CPUs como se espera.

Task 16: Observa la tasa por IRQ en el tiempo (no solo totales)

cr0x@server:~$ for i in 1 2 3; do date; grep -E 'eth0-TxRx-0|eth0-TxRx-1|eth0-TxRx-2|eth0-TxRx-3' /proc/interrupts; sleep 1; done
Tue Dec 30 10:10:01 UTC 2025
 35:   81234567     120332     110221     118877   PCI-MSI 524288-edge      eth0-TxRx-0
 36:      1200   22100333     118900     119010   PCI-MSI 524289-edge      eth0-TxRx-1
Tue Dec 30 10:10:02 UTC 2025
 35:   81310222     120350     110240     118899   PCI-MSI 524288-edge      eth0-TxRx-0
 36:      1210   22155880     118920     119030   PCI-MSI 524289-edge      eth0-TxRx-1
Tue Dec 30 10:10:03 UTC 2025
 35:   81389001     120360     110255     118920   PCI-MSI 524288-edge      eth0-TxRx-0
 36:      1220   22210210     118940     119050   PCI-MSI 524289-edge      eth0-TxRx-1

Qué significa: Puedes ver visualmente deltas por segundo. Si una IRQ incrementa mucho más rápido que las demás, ese es tu punto caliente. Los totales ocultan cambios de tasa.

Decisión: Apunta a la IRQ de alta tasa para balanceo/fijado y confirma que la distribución mejora bajo carga representativa.

irqbalance en Debian 13: verifica, afina y cuándo deshabilitar

irqbalance suele ser o bien ciegamente confiado o ciegamente culpado. Ninguno de los dos comportamientos es adulto. Trátalo como cualquier otra automatización: es un motor de políticas con valores por defecto ajustados para “servidores típicos”. Si tu servidor no es típico, seguirá siendo tratado como típico a menos que intervengas.

Qué hace bien irqbalance

  • Reparte IRQs entre CPUs para que CPU0 no sea el sufrido designado.
  • Responde a cargas cambiantes sin que edites máscaras de afinidad a las 3 a.m.
  • Funciona razonablemente con dispositivos MSI-X multi-cola.

Qué hace mal irqbalance (o no puede hacer)

  • Configuraciones de aislamiento críticas para latencia: Puede mover interrupciones a CPUs que pretendías mantener “limpios” a menos que se configure con cuidado.
  • Colocación sensible a NUMA: Puede no mantener las interrupciones del dispositivo locales al nodo PCIe como desees.
  • Interrupciones no movibles: Algunas IRQs están efectivamente fijadas o gestionadas de formas que irqbalance no puede cambiar.

Verifica la configuración de irqbalance

cr0x@server:~$ grep -v '^\s*#' /etc/default/irqbalance | sed '/^\s*$/d'
ENABLED="1"
OPTIONS=""

Qué significa: Configuración por defecto. Sin máscaras de CPU vetadas, sin comportamiento especial.

Decisión: Si ves pinning de IRQ de todas formas, algo más está estableciendo afinidad (scripts de driver, tuning personalizado, hooks del runtime de contenedores o restos de “optimización” antigua).

Comprueba qué CPUs están online y si has aislado algunos

cr0x@server:~$ lscpu | egrep 'CPU\(s\)|On-line CPU|NUMA node'
CPU(s):                               32
On-line CPU(s) list:                  0-31
NUMA node(s):                         2
NUMA node0 CPU(s):                    0-15
NUMA node1 CPU(s):                    16-31

Qué significa: Topología limpia. Si además usas parámetros de kernel de aislamiento, revisa la cmdline del kernel.

Decisión: Si aíslas CPUs (por ejemplo 4–31) para cargas, debes vetar esos CPUs de interrupciones usando opciones de irqbalance o afinidad manual.

Inspecciona la línea de comandos del kernel para parámetros relacionados con aislamiento

cr0x@server:~$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-6.12.0 root=/dev/mapper/vg0-root ro quiet isolcpus=4-31 nohz_full=4-31 rcu_nocbs=4-31

Qué significa: Has declarado CPUs 4–31 como especiales. Bien. Ahora mantén las interrupciones fuera de ellas, o hiciste una promesa que el kernel romperá por ti.

Decisión: Configura irqbalance para evitar 4–31, o desactívalo y establece máscaras explícitas persistentes para todas las IRQ relevantes.

Vetar CPUs en irqbalance (patrón típico de aislamiento)

cr0x@server:~$ sudo sed -i 's/^OPTIONS=.*/OPTIONS="--banirq=0"/' /etc/default/irqbalance
cr0x@server:~$ sudo systemctl restart irqbalance
cr0x@server:~$ systemctl status irqbalance --no-pager | head -n 12
● irqbalance.service - irqbalance daemon
     Loaded: loaded (/lib/systemd/system/irqbalance.service; enabled; preset: enabled)
     Active: active (running) since Tue 2025-12-30 10:18:01 UTC; 2s ago

Qué significa: Ejemplo solamente: vetar la IRQ 0 no es lo mismo que vetar CPUs. El veto de CPU se hace típicamente mediante opciones de máscara de CPU de irqbalance (varía según versión/build). El punto: no toques opciones al azar que no entiendes.

Decisión: Para aislamiento de CPU, prefiere afinidad por IRQ explícita o la configuración de máscara de CPU de irqbalance apropiada para tu versión instalada. Verifica leyendo /proc/irq/*/smp_affinity_list tras el reinicio.

Guía opinada: si manejas una flota normal de servidores Debian, deja irqbalance activado. Si gestionas una caja especializada con CPUs aislados, desactívalo y gestiona afinidad explícita con configuración gestionada para que sea repetible.

Interrupciones NIC: RSS, RPS/XPS, coalescing y sanidad multi-cola

La mayoría de los casos de “latencia extraña” que huelen a interrupciones son impulsados por la red. No porque el almacenamiento sea inocente, sino porque los paquetes pueden llegar a línea completa y exigir atención inmediata de la CPU. Una NIC puede entregar más trabajo por segundo del que tu kernel puede digerir si la configuras como si fuera 2009.

Empieza con RSS (escalado de recepción por hardware)

RSS reparte flujos entre colas RX en hardware. Cada cola RX tiene su propio vector de interrupción. Si RSS está desactivado, o solo existe una cola, un CPU acaba haciendo la mayor parte del trabajo de recepción. Tu throughput puede seguir pareciendo aceptable, mientras la latencia en la cola se destroza por encolamiento y jitter.

RPS/XPS: direccionamiento por software cuando RSS no basta

RPS (Receive Packet Steering) puede distribuir el procesamiento de paquetes entre CPUs aunque el RSS de hardware sea limitado. XPS puede ayudar a distribuir el procesamiento de transmisión. Son funciones a nivel de CPU; pueden mejorar el balance pero también añadir overhead. Úsalas cuando tengas una razón, no porque leíste un blog en 2016.

cr0x@server:~$ ls -1 /sys/class/net/eth0/queues/
rx-0
rx-1
rx-2
rx-3
tx-0
tx-1
tx-2
tx-3

Qué significa: Existe multi-cola. Bien. Ahora confirma que los vectores IRQ se alinean con esas colas y no están todos enrutados al mismo conjunto de CPUs.

Decisión: Alinea cada IRQ de cola a CPUs locales a la NIC y evita CPUs aislados si aplica.

Coalescing de interrupciones: el impuesto de latencia que podrías estar pagando

Si tu servicio es sensible a latencia (RPC, bases de datos, APIs interactivas), los ajustes de coalescing pueden mover la latencia de cola. El coalescing adaptativo está diseñado para ser eficiente en general, no determinista. A veces “eficiente en general” es el enemigo.

cr0x@server:~$ sudo ethtool -C eth0 adaptive-rx off adaptive-tx off rx-usecs 25 tx-usecs 25
cr0x@server:~$ ethtool -c eth0 | egrep 'Adaptive|rx-usecs|tx-usecs'
Adaptive RX: off  TX: off
rx-usecs: 25
tx-usecs: 25

Qué significa: Has reducido el tiempo de espera antes de que salten las interrupciones. Eso puede reducir latencia pero aumentar el uso de CPU y la tasa de interrupciones.

Decisión: Si hay margen de CPU y la latencia mejora, mantenlo. Si el CPU se derrite o aumentan descartes, retrocede. La configuración correcta es la que cumple tu SLO sin malgastar un núcleo.

Broma #2: Afinar el coalescing de la NIC es como sazonar una sopa—muy poco y está sosa, demasiado y de repente estás bebiendo agua de mar.

Interrupciones de almacenamiento: NVMe, blk-mq y la trampa “el disco está bien”

Los problemas de latencia de almacenamiento a menudo se culpan en el disco. A veces es correcto. A menudo no. Los dispositivos NVMe son extremadamente rápidos, lo que significa que pueden completar IO tan pronto que la vía de completado se vuelve el cuello de botella: interrupciones, mapeo de colas y localidad de CPU.

Comprueba si estás limitado por CPU en la capa de bloque

cr0x@server:~$ iostat -x 1 3
Linux 6.12.0 (server) 	12/30/2025 	_x86_64_	(32 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           7.12    0.00    3.01    0.22    0.00   89.65

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz aqu-sz  %util
nvme0n1         1200.0  96000.0     0.0   0.00    0.55    80.00  800.0  64000.0     0.0   0.00    0.60    80.00   0.12  45.00

Qué significa: El await del dispositivo es bajo, la utilización es moderada. Si la latencia de la aplicación es mala igual, el dispositivo no es obviamente el limitador; la ruta de completado, la contención o la ruta de red pueden serlo.

Decisión: Correlaciona con métricas de IRQ/softirq. Si las IRQ NVMe son altas y están concentradas, arregla eso primero.

Verifica que los ajustes de estado de energía/latencia de NVMe no te saboteen

cr0x@server:~$ cat /sys/module/nvme_core/parameters/default_ps_max_latency_us
0

Qué significa: 0 comúnmente significa “sin límite” (permitir estados de baja potencia). En servidores, el ahorro agresivo de energía puede añadir latencia de wake.

Decisión: Para sistemas sensibles a latencia, considera fijar una latencia máxima más estricta vía parámetro del kernel. Valida con cuidado; las políticas de energía dependen de la carga y la plataforma.

Particularidades de virtualización: virtio, vhost y vecinos ruidosos

En VMs, no solo afinás Linux; negocias con el hipervisor. virtio-net y vhost pueden ser excelentes, pero el comportamiento de interrupciones cambia. Puedes ver “tormentas de interrupciones” que en realidad son “tormentas de exits” o contención de colas en el host.

Dentro del guest: comprueba la distribución de IRQs virtio

cr0x@server:~$ grep -E 'virtio|vhost' /proc/interrupts | head -n 8
 40:   22112233    1100223    1099888    1101001   PCI-MSI 327680-edge      virtio0-input.0
 41:     110022   19888777     990001     980002   PCI-MSI 327681-edge      virtio0-output.0

Qué significa: Si un vector virtio domina y está fijado, aún puedes sufrir desequilibrio. Pero en VMs, el pinning del host y la topología de vCPU importan igual.

Decisión: Arregla la afinidad del guest solo después de verificar el pinning/NUMA del host. Si no, estás reorganizando muebles dentro de un camión en movimiento.

Comprueba steal time y presión de planificación

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
 1  0      0 812345  12345 987654    0    0     1     3 1200 2400  6  2 90  0  2

Qué significa: Un st (steal) no trivial sugiere contención en el host. Eso puede imitar problemas de interrupciones porque tu guest no puede correr cuando necesita servir colas.

Decisión: Escala al nivel de virtualización: pinning de CPU, enrutamiento de IRQ del host, mitigación de vecinos ruidosos y asegurar que virtio multi-queue esté habilitado de extremo a extremo.

Tres micro-historias corporativas desde las trincheras

Incidente: la suposición equivocada (“El CPU está al 30%, así que no puede ser CPU”)

Una empresa mediana ejecutaba un tier de API basado en Debian detrás de un balanceador. Tras una actualización del kernel, la latencia p99 se duplicó en horas punta. Los dashboards mostraban CPU medio ~30–40% con mucho idle. La respuesta inicial fue predecible: culpar al nuevo despliegue, culpar a la base de datos, luego culpar al equipo de red por “pérdida de paquetes”.

El SRE de guardia finalmente miró la utilización por núcleo y vio CPU0 fijado. La lista de procesos no mostraba un hilo userland caliente. Era ksoftirqd/0. Mientras tanto CPU1–CPU31 estaban tomando té. El equipo asumió que “%CPU” era un escalar. No lo es. Es una distribución.

/proc/interrupts lo dejó dolorosamente obvio: la interrupción principal RX/TX de la NIC caía casi exclusivamente en CPU0. RSS estaba configurado para múltiples colas, pero la máscara de afinidad de IRQ se había fijado durante un intento anterior de “tuning” que nunca se revirtió. irqbalance estaba en ejecución, pero no puede sobreescribir un pin que has soldado en su lugar.

La solución fue aburrida: quitar el pin manual, reiniciar irqbalance y luego fijar explícitamente las interrupciones de colas NIC a CPUs locales al nodo NUMA de la NIC. La latencia volvió a la línea base sin tocar la aplicación. El postmortem fue más duro que la solución: la suposición del equipo sobre métricas CPU era errónea y se perdió horas en fricción entre equipos.

Optimización que salió mal: “Desactivar coalescing para reducir latencia”

Otra organización tenía un servicio sensible a latencia y un ingeniero nuevo con buenas intenciones. Leyó que el coalescing “añade latencia”, así que desactivó el coalescing adaptativo y puso rx-usecs a 0 en todas partes. Las gráficas se veían increíbles en una prueba sintética: mediana de latencia más baja, respuestas vivas, todos contentos.

Dos semanas después, cambió un patrón de tráfico. Más paquetes pequeños. Más conexiones concurrentes. De repente un subconjunto de servidores empezó a descartar paquetes y mostrar picos periódicos de latencia que parecían pausas de garbage collection. De nuevo, el CPU “medio” se veía bien. La historia real era que la tasa de interrupciones se disparó; un CPU por host se convirtió en conserje de interrupciones, y el resto de la máquina estaba infrautilizado porque el CPU caliente no podía seguir el ritmo.

El equipo había optimizado para la mediana y pagado con la cola. Desactivar coalescing no solo redujo latencia; eliminó una válvula de seguridad. Bajo altas tasas de paquetes, el sistema gastó demasiado tiempo en rutas de interrupciones y softirq. La solución fue reactivar el coalescing adaptativo y luego establecer una línea base fija y moderada para RX/TX usecs alineada con su SLO. También aumentaron colas NIC y se aseguraron de mapear interrupciones al nodo NUMA correcto.

La lección no fue “nunca cambies coalescing”. Fue: coalescing es una perilla de control, no una religión. Si la pones en “siempre lo más bajo”, eliges fragilidad con cargas con ráfagas.

Práctica aburrida pero correcta: mantener una línea base de IRQ/afinidad y hacerla cumplir

Una compañía ejecutaba Debian 13 en nodos con mucho almacenamiento: NVMe + 100GbE. Tenían una regla simple: cada clase de hardware tenía una “línea base de interrupciones y colas” documentada y versionada. Cuando un nuevo servidor se aprovisionaba, la gestión de configuración la aplicaba y un script de validación la comprobaba.

El script no era sofisticado. Capturaba un snapshot de /proc/interrupts, recuentos de colas desde ethtool -l, mapeo NUMA desde sysfs y las listas de afinidad actuales para IRQs calientes. También marcaba puntos calientes en CPU0 y cualquier IRQ fijada a CPUs aisladas. Si algo divergia, el pipeline fallaba y el servidor no entraba en el pool.

Durante un incidente serio—alta latencia en un subconjunto de nodos tras una actualización de firmware del proveedor—esta línea base ahorró días. Pudieron ver inmediatamente qué nodos se desviaron: un lote tenía colas NIC reducidas por el reset de firmware, y las interrupciones colapsaron en dos vectores. Revertir o reaplicar ajustes arregló el problema rápido.

La práctica era poco glamorosa. Nadie sacó una charla para una conferencia por ello. Funcionó de todos modos. En producción, “aburrido y correcto” vence a “ingenioso y frágil” casi siempre.

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

1) Picos de latencia p99, CPU medio bajo

Síntoma: La latencia en cola es mala; los dashboards de CPU muestran mucho idle.

Causa raíz: Un CPU está sobrecargado por interrupciones/softirqs (a menudo CPU0). El promedio oculta el sesgo.

Solución: Revisa top -H, /proc/softirqs y /proc/interrupts. Arregla la afinidad de IRQ y asegúrate de que RSS/multi-queue estén activos.

2) ksoftirqd al máximo, NET_RX enorme en un CPU

Síntoma: ksoftirqd/N está caliente; NET_RX sesgado.

Causa raíz: Interrupción de cola RX fijada a un CPU; RSS no distribuye; flujo único dominando; o RPS mal configurado.

Solución: Aumenta colas, verifica indirection RSS, distribuye vectores IRQ en CPUs locales a la NIC, considera RPS para casos testarudos.

3) “irqbalance está en ejecución pero nada cambia”

Síntoma: irqbalance activo; IRQ sigue atascada en un CPU.

Causa raíz: IRQ está fijada manualmente; el driver impone afinidad; IRQ es inmovible; o irqbalance está limitado por máscaras de CPU vetadas.

Solución: Inspecciona /proc/irq/*/smp_affinity_list. Elimina pins manuales o ajusta la política. Valida tras reiniciar irqbalance.

4) La latencia mejoró al principio después de fijar, luego empeoró

Síntoma: Victoria rápida seguida de regresión bajo tráfico real.

Causa raíz: Fijar mejoró localidad pero sobrecargó un subconjunto de CPUs; cambió la distribución/hashing; o creaste contención con hilos de la aplicación en las mismas CPUs.

Solución: Haz que la colocación de IRQ coincida con la estrategia de scheduling: reserva CPUs para interrupciones, o alinea hilos de la aplicación al mismo nodo NUMA y evita pelear por los mismos cores.

5) Aumentan descartes de paquetes tras “tuning de latencia”

Síntoma: Retransmisiones/descartes después de deshabilitar coalescing/offloads.

Causa raíz: La tasa de interrupciones es demasiado alta; CPU no puede seguir; se llenan buffers.

Solución: Reactiva coalescing adaptativo o fija valores moderados; mantén RSS y multi-queue; verifica que ninguna IRQ domine.

6) Latencia de almacenamiento culpada a NVMe, pero iostat parece limpio

Síntoma: La aplicación ve stalls; métricas del dispositivo NVMe parecen bien.

Causa raíz: Procesamiento de completado en CPU o manejo de interrupciones cross-NUMA; a veces saturación de IRQ en un CPU que comparte trabajo con red.

Solución: Revisa la distribución de IRQ NVMe y la localidad NUMA; evita mezclar interrupciones calientes de NIC y NVMe en el mismo conjunto de CPUs si crea contención.

Listas de verificación / plan paso a paso

Checklist A: Detén la hemorragia en 15 minutos

  1. Captura evidencia: top -H, /proc/softirqs, /proc/interrupts y gráficas de latencia con la misma marca temporal.
  2. Identifica la línea/vector IRQ más caliente y mapea a dispositivo/nombre de cola.
  3. Comprueba su afinidad: cat /proc/irq/<IRQ>/smp_affinity_list.
  4. Si está fijada a un CPU y ese CPU está caliente, muévela como prueba (un paso) y vuelve a medir p99.
  5. Si mover ayuda, implementa una política durable: ajusta irqbalance o establece afinidad persistente.
  6. Verifica que no haya descartes/regresiones: nstat, estadísticas del driver y tasas de error de la aplicación.

Checklist B: Haz la solución duradera (no dependas de héroes)

  1. Documenta la topología CPU prevista: qué CPUs son housekeeping, cuáles están aisladas, cuáles son para carga.
  2. Registra localidad NUMA de NIC y NVMe y asegura que las IRQs estén colocadas en consecuencia.
  3. Establece el número de colas de NIC explícitamente (y verifica tras reboot/actualización de firmware).
  4. Elige una política de coalescing: adaptativa para servidores generales; fija para latencia estricta con margen.
  5. Decide sobre irqbalance: activado para propósito general; desactivado + afinidad explícita para nodos especializados.
  6. Automatiza la validación: falla el aprovisionamiento si las IRQs colapsan en CPU0 o en núcleos aislados.

Checklist C: Confirma mejoras con mediciones reales

  1. Mide p95/p99 end-to-end y tasas de error durante al menos un ciclo de negocio (no 60 segundos de esperanza).
  2. Confirma la distribución de interrupciones a lo largo del tiempo usando deltas (no solo totales).
  3. Vigila tiempo softirq de CPU y residencia de ksoftirqd.
  4. Valida que no aparezcan nuevos descartes/retransmisiones.
  5. Guarda un snapshot antes/después de /proc/interrupts para el postmortem.

Preguntas frecuentes

1) ¿Qué es exactamente una tormenta de IRQ en Linux?

Es una condición donde la actividad de interrupciones (hard IRQs o el trabajo softirq que generan) abruma a los CPUs, causando latencia, descartes y jitter. A menudo es “demasiadas interrupciones” o “interrupciones en el lugar equivocado”.

2) ¿Por qué siempre parece que CPU0 es la víctima?

Los valores por defecto al arrancar, el ruteo legacy de interrupciones y el pinning accidental frecuentemente terminan con trabajo en CPU0. Además, muchas tareas de housekeeping naturalmente corren allí. Si permites que los dispositivos se amontonen en CPU0, se convierte en tu núcleo cuello de botella.

3) ¿Debería simplemente desactivar irqbalance?

En servidores de propósito general: no, déjalo. En sistemas especializados con aislamiento de CPU o requisitos estrictos de afinidad: sí, desactívalo y gestiona afinidad explícitamente. La peor opción es “desactivarlo y no hacer nada más”.

4) ¿Cómo sé si es red o almacenamiento?

Mira /proc/softirqs y /proc/interrupts. Un alto NET_RX y IRQs calientes de colas NIC apuntan a red. IRQs altas relacionadas con bloque y vectores NVMe apuntan a almacenamiento. Luego correlaciona con el momento de la carga.

5) Si aumento colas NIC, ¿la latencia siempre mejora?

No. Más colas pueden reducir contención y repartir carga, pero también incrementar overhead y empeorar la localidad de caché. Es una herramienta, no una garantía. Mide después de cada cambio.

6) ¿Puede un único flujo TCP vencer a RSS?

Sí. RSS hace hash de flujos; un flujo se mapea a una cola RX. Si tu carga está dominada por unos pocos flujos pesados, aún puedes tener una cola caliente. Arregla shardando el tráfico o cambiando cómo la carga se hace fan-out.

7) ¿Cuál es la diferencia entre RSS y RPS?

RSS es distribución en hardware hacia múltiples colas RX. RPS es direccionamiento en software del procesamiento de paquetes entre CPUs. RSS suele ser preferible si está disponible; RPS es un fallback o complemento.

8) ¿Por qué mis ajustes desaparecen después de reiniciar o actualizar firmware?

Muchos ajustes (recuento de colas, coalescing, afinidad) no son persistentes por defecto. Las actualizaciones de firmware pueden resetear el estado de la NIC. Haz persistentes las configuraciones vía unidades systemd, reglas udev o gestión de configuración—y verifica al arrancar.

9) ¿Cómo afecta NUMA a interrupciones y latencia?

Si un dispositivo está en el nodo NUMA 1 pero sus interrupciones corren en CPUs del nodo 0, aumentas tráfico inter-nodo y fallos de caché. Eso añade jitter y reduce margen. Coloca IRQs en CPUs locales cuando sea posible.

10) Moví una IRQ y el problema mejoró, pero no del todo. ¿Y ahora?

Bien: probaste causalidad. Siguiente paso: distribución sistemática: asegúrate de que todas las colas se usan, mapear vectores sobre un conjunto de CPUs, revisar coalescing y evitar poner interrupciones calientes en CPUs que ejecutan los hilos más ocupados de la aplicación a menos que lo hagas intencionalmente.

Conclusión: siguientes pasos que realmente reducen p99

Las tormentas de IRQ y el desequilibrio de interrupciones no son misteriosos. Son medibles. En Debian 13 ya cuentas con lo necesario: /proc/interrupts, /proc/softirqs, ethtool y una cabeza fría.

Haz esto a continuación, en orden:

  1. Prueba si estás limitado por interrupciones/softirq (top -H, /proc/softirqs).
  2. Mapea IRQs calientes a dispositivos y colas (/proc/interrupts más ethtool -i/-l).
  3. Arregla distribución y localidad: colas, RSS, máscaras de afinidad, colocación NUMA.
  4. Sólo entonces: afina coalescing y offloads, con mediciones reales de latencia y planes de rollback.
  5. Hazlo persistente y validado, porque lo peor no es una tormenta sino una tormenta que vuelve después de un reinicio.
← Anterior
Ubuntu 24.04: Secure Boot bloquea controladores — solución sin arruinar tu equipo
Siguiente →
Debian 13: el estrangulamiento térmico arruina el rendimiento — demuestra y arregla refrigeración/límites de potencia

Deja un comentario