¿Qué tipo de carga en los servidores crea mecanismos de red?

Al analizar el funcionamiento del subsistema de red de servidores, generalmente se presta atención a indicadores tales como la latencia, el rendimiento del sistema y la cantidad de paquetes que se pueden procesar por segundo (PPS, paquetes por segundo). Estos indicadores se utilizan para comprender bajo qué carga máxima puede funcionar la computadora en estudio. Y aunque estas métricas son importantes y a menudo pueden decir mucho sobre el sistema, no proporcionan información sobre el impacto que tiene el procesamiento de paquetes de red en los programas que se ejecutan en el servidor. Este material tiene como objetivo estudiar la carga creada por los mecanismos de red en los servidores. En particular, hablaremos sobre cuánto tiempo de procesador puede "robar" la solución a los problemas de red de varios procesos que se ejecutan en sistemas Linux.





Procesamiento de paquetes de red en Linux


Linux procesa un número significativo de paquetes en el contexto de cualquier proceso ejecutado por el procesador al momento de procesar la IRQ correspondiente. El motor de contabilidad del sistema asignará los ciclos de procesador utilizados para esto a cualquier proceso que se esté ejecutando actualmente. Esto se hará incluso si este proceso no tiene nada que ver con el procesamiento de paquetes de red. Por ejemplo, un equipo toppuede indicar que un proceso parece estar utilizando más del 99% de los recursos del procesador, pero de hecho el 60% del tiempo del procesador se gastará en procesar los paquetes. Y esto significa que el proceso en sí, resolviendo sus propios problemas, usa solo el 40% de los recursos de la CPU.

Manejador entrantenet_rx_actiongeneralmente se realiza muy, muy rápido. Por ejemplo, en menos de 25 μs. (Estos datos se obtuvieron de mediciones usando eBPF. Si está interesado en los detalles, mire aquí net_rx_action .) El procesador puede procesar hasta 64 paquetes por instancia de NAPI (NIC o RPS) antes de posponer la tarea a otro ciclo de SoftIRQ. Uno tras otro, sin interrupción, pueden seguir hasta 10 ciclos de SoftIRQ, lo que demora aproximadamente 2 ms (puede obtener más información al respecto leyendo __do_softirq). Si el vector SoftIRQ, después de que ha pasado el número máximo de ciclos, o ha pasado el tiempo, todavía tiene problemas sin resolver, entonces la solución de estos problemas se retrasa para la ejecución en el hiloksoftirqdCPU específica. Cuando esto sucede, el sistema resulta ser un poco más transparente en el sentido de obtener información sobre la carga del procesador creada por las operaciones de red (aunque dicho análisis se realiza suponiendo que se examina SoftIRQ, que está relacionado con el procesamiento de paquetes y no con otra cosa) .

Una forma de obtener los indicadores anteriores es usar perf:

sudo perf record -a \
        -e irq:irq_handler_entry,irq:irq_handler_exit
        -e irq:softirq_entry --filter="vec == 3" \
        -e irq:softirq_exit --filter="vec == 3"  \
        -e napi:napi_poll \
        -- sleep 1

sudo perf script

Aquí está el resultado:

swapper     0 [005] 176146.491879: irq:irq_handler_entry: irq=152 name=mlx5_comp2@pci:0000:d8:00.0
swapper     0 [005] 176146.491880:  irq:irq_handler_exit: irq=152 ret=handled
swapper     0 [005] 176146.491880:     irq:softirq_entry: vec=3 [action=NET_RX]
swapper     0 [005] 176146.491942:        napi:napi_poll: napi poll on napi struct 0xffff9d3d53863e88 for device eth0 work 64 budget 64
swapper     0 [005] 176146.491943:      irq:softirq_exit: vec=3 [action=NET_RX]
swapper     0 [005] 176146.491943:     irq:softirq_entry: vec=3 [action=NET_RX]
swapper     0 [005] 176146.491971:        napi:napi_poll: napi poll on napi struct 0xffff9d3d53863e88 for device eth0 work 27 budget 64
swapper     0 [005] 176146.491971:      irq:softirq_exit: vec=3 [action=NET_RX]
swapper     0 [005] 176146.492200: irq:irq_handler_entry: irq=152 name=mlx5_comp2@pci:0000:d8:00.0

En este caso, el procesador está inactivo (de ahí la aparición de entradas swapperpara el proceso), se llama a IRQ para la cola Rx en la CPU 5, se procesa SoftIRQ dos veces, se procesan 64 paquetes primero, luego 27. La siguiente IRQ se llama después de 229 μs y comienza el ciclo nuevamente.

Estos datos se obtuvieron en un sistema inactivo. Pero en el procesador, se puede realizar cualquier tarea. En este caso, se produce la secuencia de eventos anterior, interrumpiendo esta tarea y realizando tareas IRQ / SoftIRQ. Al mismo tiempo, la contabilidad del sistema atribuye al proceso interrumpido la carga creada por el procesador. Como resultado, las tareas de procesamiento de paquetes de red generalmente están ocultas de las herramientas convencionales de monitoreo de carga del procesador. Se ejecutan en el contexto de algún proceso seleccionado al azar, en el contexto del "proceso de víctima". Esto nos lleva a algunas preguntas. ¿Cómo estimar el tiempo durante el cual se interrumpe el proceso para el procesamiento de paquetes? ¿Cómo comparar 2 soluciones de red diferentes para comprender cuál de ellas tiene un efecto menor en varias tareas resueltas en una computadora?

Cuando se utilizan mecanismos RSS, RPS, RFS, el procesamiento de paquetes generalmente se distribuye entre los núcleos del procesador. Por lo tanto, la secuencia de procesamiento de paquetes anterior está relacionada con cada CPU específica. A medida que aumenta la velocidad de llegada de paquetes (creo que podemos hablar de velocidades de 100,000 paquetes por segundo y más), cada CPU tiene que procesar miles o decenas de miles de paquetes por segundo. El procesamiento de tantos paquetes afectará inevitablemente a otras tareas realizadas en el servidor.

Considere una forma de evaluar este efecto.

Deshabilitar el procesamiento de paquetes distribuidos


Para comenzar, detengamos el procesamiento distribuido de paquetes deshabilitando RPS y configurando reglas de control de flujo destinadas a organizar el procesamiento de todos los paquetes relacionados con una dirección MAC específica en la única CPU que conocemos. Mi sistema tiene 2 NIC agregadas en una configuración 802.3ad. Las tareas de red se asignan a una sola máquina virtual que se ejecuta en una computadora.

RPS en adaptadores de red está deshabilitado de la siguiente manera:

for d in eth0 eth1; do
    find /sys/class/net/${d}/queues -name rps_cpus |
    while read f; do
            echo 0 | sudo tee ${f}
    done
done

A continuación, configuramos las reglas de control de flujo para garantizar que los paquetes ingresen a la máquina virtual de prueba utilizando una sola CPU:

DMAC=12:34:de:ad:ca:fe
sudo ethtool -N eth0 flow-type ether dst ${DMAC} action 2
sudo ethtool -N eth1 flow-type ether dst ${DMAC} action 2

Deshabilitar RPS y usar reglas de control de flujo nos permite garantizar que todos los paquetes destinados a nuestra máquina virtual se procesen en la misma CPU. Para asegurarse de que los paquetes se envían a la cola a la que deben enviarse, puede usar un comando como ethq . Luego puede averiguar a qué CPU pertenece esta cola /proc/interrupts. En mi caso, el turno 2 se procesa por medio de la CPU 5.

Comando de velocidad Openssl


Podría usar utilidades perfo analizar los tiempos de ejecución de SoftIRQ responsables del procesamiento del tráfico entrante bpf, pero este enfoque es bastante complicado. Además, el proceso de observación en sí definitivamente afecta los resultados. Una solución mucho más simple y comprensible es identificar la carga creada por las operaciones de red en el sistema utilizando alguna tarea, una que cree una carga conocida en el sistema. Por ejemplo, este es un comando openssl speedutilizado para probar el rendimiento de OpenSSL. Esto le permitirá descubrir cuántos recursos de procesador obtiene el programa en realidad y compararlo con la cantidad de recursos que se supone que recibirá (esto ayudará a determinar cuántos recursos se gastan en tareas de red).

El equipo es openssl speedcasi 100% un equipo de espacio de usuario. Si lo vincula a una determinada CPU, entonces, durante la ejecución de las pruebas, utiliza todos sus recursos disponibles. El equipo trabaja configurando el temporizador en el intervalo especificado (aquí, por ejemplo, para facilitar los cálculos, lleva 10 segundos), ejecutando la prueba y luego, cuando se activa el temporizador, usándolo times()para averiguar cuánto tiempo de procesador realmente tuvo el programa. Desde el punto de vista syscallse ve así:

alarm(10)                               = 0
times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1726601344
--- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL} ---
rt_sigaction(SIGALRM, ...) = 0
rt_sigreturn({mask=[]}) = 2782545353
times({tms_utime=1000, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1726602344

Es decir, resulta que alarm()se hicieron muy pocas llamadas al sistema entre llamar y verificar los resultados. Si el programa no se interrumpió, o se interrumpió muy raramente, el tiempo tms_utimecoincidirá con el tiempo de la prueba (en este caso, 10 segundos).

Dado que esta es una prueba realizada exclusivamente en el espacio del usuario, cualquier hora del sistema que aparezca times()significará una carga adicional en el sistema. Resulta que, aunque openssleste es un proceso que se ejecuta en la CPU, la misma CPU puede estar ocupada con otra cosa. Por ejemplo, procesando paquetes de red:

alarm(10)                               = 0
times({tms_utime=0, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 1726617896
--- SIGALRM {si_signo=SIGALRM, si_code=SI_KERNEL} ---
rt_sigaction(SIGALRM, ...) = 0
rt_sigreturn({mask=[]}) = 4079301579
times({tms_utime=178, tms_stime=571, tms_cutime=0, tms_cstime=0}) = 1726618896

Aquí puede ver que fue opensslposible trabajar en el procesador durante 7,49 segundos (178 + 571 en unidades de medida correspondientes a 0,01 s). Pero al mismo tiempo 5,71 s. este intervalo está representado por la hora del sistema. Como no está opensslocupado con ningún negocio en el espacio del kernel, esto significa que 5,71 s. - Este es el resultado de una carga adicional en el sistema. Es decir, este es el momento en que el proceso fue "robado" para satisfacer las necesidades del sistema.

Usar el comando de velocidad openssl para detectar la carga del sistema causada por mecanismos de red


Ahora que hemos descubierto cómo funciona el equipo openssl speed, veremos los resultados que produce en un servidor prácticamente inactivo:

$ taskset -c 5 openssl speed -seconds 10 aes-256-cbc >/dev/null
Doing aes-256 cbc for 10s on 16 size blocks: 66675623 aes-256 cbc's in 9.99s
Doing aes-256 cbc for 10s on 64 size blocks: 18096647 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 256 size blocks: 4607752 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 1024 size blocks: 1162429 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 8192 size blocks: 145251 aes-256 cbc's in 10.00s
Doing aes-256 cbc for 10s on 16384 size blocks: 72831 aes-256 cbc's in 10.00s

Como puede ver, se nos informa que el programa pasa de 9.99 a 10 segundos para procesar bloques de diferentes tamaños. Esto confirma que los mecanismos del sistema no toman tiempo del procesador del programa. Ahora, usando netperf, cargaremos el servidor procesando paquetes que provienen de dos fuentes. Ejecute la prueba nuevamente:

$ taskset -c 5 openssl speed -seconds 10 aes-256-cbc >/dev/null
Doing aes-256 cbc for 10s on 16 size blocks: 12061658 aes-256 cbc's in 1.96s
Doing aes-256 cbc for 10s on 64 size blocks: 3457491 aes-256 cbc's in 2.10s
Doing aes-256 cbc for 10s on 256 size blocks: 893939 aes-256 cbc's in 2.01s
Doing aes-256 cbc for 10s on 1024 size blocks: 201756 aes-256 cbc's in 1.86s
Doing aes-256 cbc for 10s on 8192 size blocks: 25117 aes-256 cbc's in 1.78s
Doing aes-256 cbc for 10s on 16384 size blocks: 13859 aes-256 cbc's in 1.89s

Los resultados son muy diferentes de los obtenidos en un servidor inactivo. Se espera que cada una de las pruebas se ejecute en 10 segundos, pero times()informa que el tiempo de ejecución real es de 1.78 a 2.1 segundos. Esto significa que el tiempo restante, que varía de 7,9 a 8,22 segundos, se dedicó al procesamiento de los paquetes, ya sea en el contexto del proceso opensslo en ksoftirqd.

Echemos un vistazo a lo que dará el equipo topal analizar el lanzamiento que acaba de completar openssl speed.

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND              P 
 8180 libvirt+  20   0 33.269g 1.649g 1.565g S 279.9  0.9  18:57.81 qemu-system-x86     75
 8374 root      20   0       0      0      0 R  99.4  0.0   2:57.97 vhost-8180          89
 1684 dahern    20   0   17112   4400   3892 R  73.6  0.0   0:09.91 openssl              5    
   38 root      20   0       0      0      0 R  26.2  0.0   0:31.86 ksoftirqd/5          5

Aquí podría pensar que opensslutiliza aproximadamente el 73% de los recursos de la CPU 5, y ksoftirqdse obtienen los recursos restantes. Pero en realidad, en el contexto openssl, el procesamiento de una cantidad tan grande de paquetes se realiza que el programa en sí solo toma entre el 18 y el 21% del tiempo del procesador para resolver sus problemas.

Si reduce la carga de la red a 1 flujo, openssltiene la sensación de que se está consumiendo el 99% de los recursos del sistema.

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND              P
 8180 libvirt+  20   0 33.269g 1.722g 1.637g S 325.1  0.9 166:38.12 qemu-system-x86     29
44218 dahern    20   0   17112   4488   3996 R  99.2  0.0   0:28.55 openssl              5
 8374 root      20   0       0      0      0 R  64.7  0.0  60:40.50 vhost-8180          55
   38 root      20   0       0      0      0 S   1.0  0.0   4:51.98 ksoftirqd/5          5

Pero en realidad resulta que el programa que se ejecuta en el espacio del usuario obtiene, de los 10 segundos esperados, solo unos 4 segundos:

Doing aes-256 cbc for 10s on 16 size blocks: 26596388 aes-256 cbc's in 4.01s
Doing aes-256 cbc for 10s on 64 size blocks: 7137481 aes-256 cbc's in 4.14s
Doing aes-256 cbc for 10s on 256 size blocks: 1844565 aes-256 cbc's in 4.31s
Doing aes-256 cbc for 10s on 1024 size blocks: 472687 aes-256 cbc's in 4.28s
Doing aes-256 cbc for 10s on 8192 size blocks: 59001 aes-256 cbc's in 4.46s
Doing aes-256 cbc for 10s on 16384 size blocks: 28569 aes-256 cbc's in 4.16s

Las herramientas convencionales de monitoreo de procesos indican que el programa usa casi todos los recursos del procesador, pero en realidad resulta que el 55-80% de los recursos de la CPU se gastan en el procesamiento de paquetes de red. El rendimiento del sistema al mismo tiempo se ve excelente (más de 22 Gb / s por línea de 25 Gb / s), pero esto tiene un tremendo impacto en los procesos que se ejecutan en este sistema.

Resumen


Aquí examinamos un ejemplo de cómo los mecanismos de procesamiento de paquetes "roban" los relojes del procesador de un punto de referencia simple y no muy importante. Pero en un servidor real, los procesos que se ven afectados de manera similar pueden ser cualquier cosa. Estos pueden ser procesadores virtuales, subprocesos de emulador, subprocesos vhost de máquinas virtuales. Estos pueden ser diferentes procesos del sistema, cuyo impacto puede tener un impacto diferente en el rendimiento de estos procesos y de todo el sistema.

¿Considera, analizando sus servidores, el impacto en el rendimiento real de la carga asociada con las operaciones de red?


All Articles