Sr. Administrador, más CPU por favor.

Actualmente la gente de IT, en concreto la relacionada con Sistemas, está constantemente bombardeada por las campañas de publicidad de distintas compañías, las cuales presentan productos que prometen, en el caso de los procesadores, chips más veloces, con más cores, que pueden ejecutar más threads, con más GHz, cachés L1, L2, L3 y varias unidades de ejecución, TLBs más grandes, etc. El problema, por lo menos así lo veo yo, es que la gente no conoce la tecnología que está comprando, ni la conoce y en muchos casos no muestra el menor interés en conocerla.

Tener un desconocimiento de la tecnología, para nuestro caso los procesadores, no supone un problema para la mayoría de los negocios, las máquinas funcionan, mejor o peor, si es “mejor” no tenemos problemas, pero si es “peor”, la mayoría de la gente decide solucionarlo por fuerza bruta.



“Si tu caballo no puede arrastrar el carro, no te preguntes si el caballo tiene un problema, pon otro caballo”


Este ejemplo nos puede ayudar a comprender muchas de las decisiones que se toman en IT para resolver un problema de rendimiento en un sistema, añadir más caballos. Cuando tenemos un problema de rendimiento, existen, al igual que en la analogía del carro, muchas posibles causas del problema, las ruedas, el diseño del propio carro, el tipo de carga, el caballo, etc. Debemos realizar un estudio para conocer la causa y una vez detectada proponer la mejor solución, algunos casos consistirá en sustituir las ruedas, aligerar la carga, cambiar de carro, incluir mas caballos, a nadie le gusta comprar un caballo para un carro que no lo necesita.

Uso de CPU

Este post pretende cubrir dos objetivos, el primero analizar el proceso de identificación y resolución de un problema que afecta al rendimiento del sistema y como segundo objetivo, intentar refleja la problemática de utilizar, en un entorno profesional, unos conocimientos bastante superficiales de parte de la tecnología con la que se trabaja, para el caso que vamos a tratar el Sistema Operativo y en concreto el uso de CPU.

Cualquier persona que trabaje en IT, conoce o cree conocer, qué significa “porcentaje de uso de CPU” y mucha de esta gente relaciona un porcentaje de uso de CPU elevado con un problema en el sistema. Para los administradores de SO, escuchar algunas de las siguientes expresiones es algo habitual:

“Hay un problema en la máquina porque está consumiendo mucha CPU.”

“La máquina va muy lenta, claro!!! está al 85% de uso de CPU”

“Poned más CPUs a la máquina que está al 100% de CPU y se va a caer.”

Estas expresiones reflejan una serie de ideas preconcebidas sobre el rendimiento de un sistema, basadas en el porcentaje de uso de CPU del sistema. Tomar decisiones basadas en este tipo de ideas preconcebidas pueden empujarnos a cometer errores, que en muchos casos pueden ser dramáticos, por ejemplo, pensar que comprando más HW se solucionará el problema.

El problema

Vamos a ver un caso que me ocurrió hace tiempo, sobre un problema de rendimiento en una aplicación y la forma en la que lo solucionamos. Comencemos realizando una descripción del entorno donde se desarrolla nuestro ejemplo. El problema lo tenemos con una de las aplicaciones, la cual daba unos tiempos de respuesta bastante elevados, comprobamos que ciertas operaciones consumían bastante tiempo.

En un primer momento se realiza un análisis superficial de todos los sistemas que utiliza dicha aplicación, durante este análisis se comprueba que existen unos 10 procesos que consumen entre el 6% y el 8% de CPU, la máquina dispone de 8 CPUs, el porcentaje total de uso de la CPU ronda el 85%. Aún no hemos hecho un análisis de la causa, hasta ahora únicamente tenemos una vaga hipótesis:

Hay 10 procesos en la BD que consumen el 85% de la CPU

Si basásemos nuestra decisión en esta única hipótesis, la solución sería sencilla, deberíamos añadir más CPUs al sistema (comprar mas caballos) y aquí estaría nuestro error, hemos intentado dar una solución a un problema, basándonos en una hipótesis que no se ha podido demostrar y por lo tanto no podemos asegurar que el problema del rendimiento de la aplicación esté directamente relacionado con el elevado porcentaje de uso de CPU.

Midiendo el rendimiento

El porcentaje de uso de CPU es una de las variables que, normalmente, se toma como medida del estado de salud de un sistema, sin embargo, en un entorno donde se intuye un problema de rendimiento, hay que realizar un análisis mas profundo y estudiar otros parámetros del sistema para intentar deducir la causa del problema. Entre estos parámetros, son importante, el número de procesos, el número de llamadas a sistema, el número de bloqueos, la entrada/salida de la máquina, uso de CPU de los distintos procesos, tiempo de ejecución a nivel de usuario y tiempo de ejecución a nivel del kernel.

Para nuestro caso, realizamos un análisis en profundidad de todas variables comentadas anteriormente, de dicho análisis podemos sacar las siguientes conclusiones:

Acceso a discos

No existen problemas de rendimiento en el acceso a disco por parte de los procesos. Este suele ser uno de los principales problemas de rendimiento en aplicaciones, la tasa de transferencia media es de 500KB/Seg. Hacemos pruebas en los momentos de carga de la aplicación y obtenemos picos de 15MB/seg. Podemos descartar problemas de acceso a disco desde el SO, aunque no podemos descartar que la aplicación tenga problema de acceso a disco, puede que haya un problema en la forma en la que la aplicación gestiona el acceso a disco, ficheros que no existen, lecturas/escrituras repetidas de un mismo dato, etc. Nuestra conclusión con respecto al acceso a disco es:

“No hay problema en el acceso a disco por parte del SO.”

Paginación

No existen problema de paginación de memoria, los valores ha permanecido dentro de los rangos normales, teniendo en cuenta que la máquina pose 16GB de memoria RAM. Es importante destacar que en ningún momento se han producido la activación del sistema scan rate el cual provoca que el gestor de memoria penalice el rendimiento del sistema, buscando páginas ocupadas para liberarlas.

“No hay un problema con la cantidad de memoria disponible en el sistema.”

Bloqueos

Otros de los problemas que causan contención en un sistema son los bloqueos que se producen entre los distintos procesos al intentar acceder a un recurso compartido que implemente algún mecanismo de exclusión. En el caso de nuestra aplicación, no se aprecian bloqueos ni en el kernel ni entre los threads de la aplicación.

“No se aprecian bloqueos ni en el SO, ni en la aplicación.”

Llamadas a sistema

Una de las actividades que nos pueden ayudar en la deducción del problema consiste en el estudio de las llamadas a sistema que están ejecutando los distintos procesos que están corriendo en la máquina.

Las llamadas a sistema son el interfaz que existe entre el espacio de usuario y el espacio de kernel del sistema. Las llamadas son utilizadas por los distintos procesos para solicitar al kernel que realice una determinada acción, por lo tanto, estudiar cuantas y cuales son las llamadas a sistema que ejecuta el proceso, nos puede ayudar a conocer la forma en la que el proceso está interactuando con el sistema y si existe algún problema de rendimiento

syscall.JPG

Estudiando el problema

El método que hemos seguido, ha consistido en estudiar qué estaban haciendo los procesos que más CPU consumían en la máquina. Como ya hemos comentado, de todos los proceso de la aplicación que están ejecutándose en el sistema, hay unos 10 procesos, cada uno de los cuales consume entre un 6% y un 8% de la CPU, realizando un análisis de las llamadas a sistemas que están ejecutando dichos procesos, nos ha llamado la atención que la mayoría de estos procesos estaban ejecutando de forma intensiva la llamada poll().

Si utilizamos la documentación del comando man, podemos leer que la llamada poll() se utiliza para comprobar si en una lista de “descriptores de fichero” ocurre un evento determinado. Se suele utilizar, por ejemplo, cuando queremos que un proceso atienda peticiones por varios sockets.

Que nuestra aplicación esté realizando de forma intensiva llamadas a poll(), es algo bastante extraño, sobre todo por el número de llamadas que realiza. Utilizando el comando truss , podemos realizar una serie de estadísticas sobre la ejecución de un proceso:

root@machine # /usr/ucb/ps -aux 2966 ; truss -c -f -p 2966 

USER PID %CPU %MEM SZ RSS TT S START TIME COMMAND 
app01 2966 6.6 2.91016128924760 ? O 09:30:36 1:44 app 

^Csyscall seconds calls errors 

 poll .557 36038 
 pread .147 2428 
-------- ------ ---- 

 sys totals: .704 38466 0 
 usr time: 2.059 
 elapsed: 3.770

De 38466 llamadas, 36038 son llamadas poll() y de los 3,7 segundos durante los que hemos recogido datos, 2,0 segundos se han empleado en el espacio de usuario, este tiempo se ha utilizado para ejecutar instrucciones que no han necesitado llamadas a sistema.

En las siguiente gráficas podemos ver un ejemplo de las llamadas poll() ejecutadas por uno de los procesos, que creemos son problemáticos, frente al total de las llamadas a sistema ejecutadas y justo debajo hemos podemos ver una gráfica del uso de CPU del mismo proceso.

syscall_poll_pid_6495.JPG

cpu_pid_6495.JPG

Se puede apreciar, que el uso de CPU se incrementa cuando el proceso está ejecutando las llamadas poll(), mientras que el uso de CPU disminuye cuando está realizando otras llamadas a sistema. De estas 2 gráficas, lo que nos llama poderosamente la atención es que durante bastante tiempo, (tiempo que ha durado la muestra), el proceso únicamente realiza llamadas a sistema poll(), no permanece en un estado de espera o sencillamente ejecuta las instrucciones de su espacio de usuario.

Con los datos recogidos, algunos de los cuales hemos representado en las gráficas anteriores, podemos comentar, con cierta seguridad, que el echo de que el proceso esté usando entre un 6% y un 8% de CPU, se debe a las llamadas a sistema poll() que está ejecutando, tenemos que tener cuidado con esta afirmación, el proceso puede que esté ejecutando instrucciones que no necesitan llamadas a sistema

Hemos comprobado que los procesos de la aplicación se comportan de esta forma (realizando las llamadas poll()) cuando están ejecutando una serie de operaciones, que hemos denominado “pesadas”, pero estas operaciones no están relacionadas con las llamadas poll(), uno de los threads del proceso se encarga de ejecutar las operaciones pesadas, mientras que otro de los threads se encarga de atender las conexiones mediante poll(). El proceso no necesita ejecutar la llamada poll() para realizar las operaciones “pesadas”, por lo tanto, podemos asegurar que la aplicación no va lenta porque sus procesos están consumiendo el 7% de la CPU (esto que provoca que el uso de CPU total del sistema llegue al 90% ) sino todo lo contrario, en nuestro caso, el que la aplicación esté ejecutando operaciones “pesadas”, las cuales requieren un tiempo t1 determinado, provoca que durante todo este tiempo t1 se estén realizando de forma intensiva llamadas a sistema poll(), lo que implica que el uso de CPU del proceso aumente.

” La aplicación no va lenta a causa de que el uso de CPU del sistema sea del 90%, sino que la lentitud de la aplicación al ejecutar ciertas operaciones pesadas, provoca que el uso de CPU de la máquina sea del 90%”

Disminuyendo el porcentaje de uso de CPU

Existen dos formas para disminuir el porcentaje de uso de CPU:

  • Aumentar el número de CPUs, provoca que al dividir la carga del sistema entre mas CPUs, el porcentaje disminuya.
  • Disminuir el tiempo de ejecución que una aplicación necesita tener ocupada la CPU, si una aplicación conseguimos que para ejecutar una operación en vez de tardar 5 seg, tarde 1 seg, vamos a provocar que el porcentaje de uso de CPU también disminuya.

En nuestro caso, el uso de CPU es provocado por el elevado número de llamadas poll(), esta llamada, no provoca ningún tipo de bloqueo, ni con otros procesos, ni con los recursos compartidos del sistema, por lo tanto, tener un número elevado de llamadas poll() no afecta al rendimiento del sistema, el sistema le dará tantos quantums de CPU al proceso como éste solicite y el sistema disponga.

También tenemos que comentar que en nuestro caso, aumentar el número de CPUs, no tendrá ningún efecto real, únicamente conseguiremos realizar una inversión económica, ya que la aplicación no se ejecutará de forma más rápida, lo único que conseguiremos será que la aplicación ejecute un mayor número de llamadas poll().

Durante el análisis comprobamos que no había problemas con el número de proceso en la cola de ejecución, por lo tanto no había un problema con la cantidad de tiempo que un proceso necesitaba para ejecutarse.

Nuestra solución pasa por disminuir el tiempo de CPU que la aplicación necesita para ejecutarse y en concreto, intentar disminuir el tiempo de ejecución de los procesos de la aplicación están consumiendo entre un 6% y un 7% de la CPU.

Tras unos cambios realizados en la parametrización de la aplicación, se consigue disminuir el tiempo que los procesos emplean para ejecutar las operaciones “pesadas”, al disminuir el tiempo en el que están ejecutándose los procesos, disminuimos el tiempo que están consumiendo un 7% de la CPU y por lo tanto conseguimos reducir el porcentaje de uso de CPU hasta un 10%.

Hemos conseguido bajar un 75% el uso de CPU sin haber realizado un inversión en HW, dicha inversión, hubiera sido totalmente innecesaria e injustificable.

 

En la siguiente gráfica podemos ver un ejemplo de la ejecución de uno de los procesos de la aplicación, mientras ejecuta una de las operaciones “pesadas”, estamos representando las llamadas a sistema poll() frente al número total de llamadas a sistema.

 

syscall_poll_pid_29328.JPG



Con los cambios en la parametría de la aplicación, hemos conseguido que la mayoría de las llamadas a sistema ya no sean poll(), el proceso está usando tiempo de CPU para devolver los datos que la aplicación necesita.

Ahora los procesos utilizan menos tiempo de CPU, por lo tanto ejecutan menos llamadas poll(), en la siguiente gráfica podemos ver la evolución del número de llamadas a sistema por segundo que se ejecutan en el sistema. Se ha reducido de unas 200.000 llamadas por segundo, la mayoría de las cuales eran poll() a una media de 100.000 llamadas a sistema, después de los cambios.

syscall_1.JPG

syscall_2.JPG

Conclusión

Como conclusión podemos decir que los SO, en cuanto a rendimiento se refiere, disponen de algunas variables más que el porcentaje de uso de CPU y que antes de realizar ningún juicio sobre el estado del sistema y las medidas a tomar en caso de un problema, debemos realizar un análisis en profundidad de qué está ocurriendo y por qué ocurre, ya sea un problema en el rendimiento de una aplicación, en la forma en la que se gestiona la memoria, el acceso a los discos o los posibles problemas de bloqueos.

Para nuestro ejemplo, el uso de CPU se consiguió reducir cambiando parte de la parametrización de la aplicación, sin tener que añadir mas procesadores, como hemos comentado antes, se habría demostrado innecesario.

Actualmente, con los nuevos procesadores multicores, el estudio del rendimiento del SO será bastante relevante para el desempeño de las aplicaciones. Estamos pasado de un arquitectura de frontends monoprocesadores y backends multiprocesadores, donde el crecimiento en horizontal, para los fronteds, consistía en añadir más máquinas, y el crecimiento en vertical, para los backends, consistía en añadir más recursos a la máquina (CPU, MEM, IO). Ahora los nuevos sistemas con procesadores multicores nos plantean nuevos problemas, lo cuales tendremos que resolver, la gestión de los recursos del sistema entre entornos virtualizados, los problema de acceso a memoria, aciertos de caches, tamaño de la páginas de memoria, estudio de los bloqueos, comportamiento de los distintos threads de un proceso, etc.

Elementos que hasta ahora podían ser desconocidos para la mayoría de los administradores, cobran relevancia, TLBs, TSBs, aciertos de cache L1 y L2, cambios de contexto, interrupciones del procesador, fallos de memoria, latencia de los acceso a memoria, etc.