En muchos escenarios el rendimiento de la memoria RAM es un factor determinante para el rendimiento de SQL Server. Cuando virtualizamos una máquina virtual vamos a pasar a compartir el acceso a la memoria con N máquinas virtuales y el propio host. Desgraciadamente no existen contadores de rendimiento en Windows por defecto que de forma sencilla nos puedan indicar que estamos sufriendo de problemas de congestión en el acceso a la memoria.

En general existe una correlación entre el uso de CPU y los accesos a memoria pero debemos contextualizarla en base a la carga. Por ejemplo, una aplicación que esté realizando cálculos pesados dentro del procesador, pero sin acceder a grandes conjuntos de datos para ello, probablemente tengo un alto acierto de lecturas/escrituras en la caché L3 reduciéndose así el acceso a memoria. Esto producirá que podamos tener porcentajes de consumo de CPU elevados sin que extenuemos el acceso a la memoria. SQL Server sin embargo trabaja con conjuntos de datos grandes, soportando concurrencia de cargas diversas, etc. lo que produce que con mucha mayor frecuencia se generen fallos en el acceso a la L3 y tengamos que acceder a la memoria RAM. Por ejemplo en un SELECT COUNT(*) FROM TABLA_GRANDE tendremos muchos requerimientos de lectura de memoria (y disco si una parte de la tabla no está en el buffer pool) pero pocos requerimientos de CPU, ya que únicamente debemos contar filas.

Como ya comentábamos, lo ideal sería disponer de contadores de rendimiento que mostraran el estado de la controladora de memoria de cada procesador y poder conocer así la cola de peticiones a memoria, el número de peticiones atendidas por segundo, el throughput en GB/s, etc. Por defecto no disponemos de dicha información por lo que cuando tenemos sospechas que algo no está funcionando bien con el acceso a memoria debemos recurrir a benchmarks sintéticos y analizar si sus resultados son coherentes con el hardware del que disponemos. Por ser más concretos en nuestra experiencia con SQL Server virtualizados que están sufriendo este problema “invisible” hemos observado lo siguiente:

  • Un dimensionamiento correcto de la memoria, no hay presión de memoria, ni interna ni externa por parte de otras máquinas. En muchos casos la memoria estaba reservada de forma estática.
  • No hay un exceso de lectura/escritura a disco ni tampoco un consumo de CPU elevado. En muchos casos también teníamos CPUs/ciclos de CPU reservados en cantidad suficiente.
  • No existen esperas significativas internamente en SQL Server a nivel de procesador, latches, spinlocks, etc.
  • El feeling durante el uso de la máquina es que tiene un comportamiento “poco ágil” en general. Ciertas operaciones habitualmente ligeras se ralentizan excesivamente.

En resumen, tras analizar los información que tenemos disponible mediante monitorización nos quedamos con que todo “debería ir bien” pero la realidad es que los tiempos de respuesta de la base de datos son lentos.

Vamos a analizar los resultados de algunos benchmark de memoria para que podamos ver qué podemos esperar en casos donde tenemos presión externa a nivel de controladora de memoria. Lo primero que debemos hacer es conocer el hardware que tenemos. En mi caso cuento con un procesador Intel Haswell i7-4710MQ con una caché L1 de 128 KB (code) + 128 KB (data), una caché L2 de 1 MB y una L3 de 6 MB. La latencia mínima de acceso a la RAM en este procesador de la serie Haswell con memoria PC3-12800 para operaciones de 256 MB es de unos 90ns. Las operaciones de tamaño más pequeño nos darían resultados ficticios al medir latencias medias o throughputs mayores por el impacto de la caché.

Como software de benchmark vamos a utilizar SiSoftware Sandra por ser de los más conocidos y completos. El primer paso es medir el rendimiento del host lo más “limpio posible” para tener la baseline del rendimiento “sin carga”. Con este dato sabremos si el hardware está rindiendo acorde a las especificaciones o no. Idealmente realizaríamos las pruebas con el hipervisor (hyper-v en este caso) desconectado. Esto es debido a que simplemente por tener el hipervisor habilitado el host también puede sufrir penalizaciones, aún sin ninguna máquina virtual ejecutando. Las siguientes mediciones muestran el resultado de mi host sin hyper-v activo:

mem_sin_anchomem_con_lat

A continuación repetiremos las mediciones con el hyper-v activo en el host:

mem_con_anchomem_sin_lat

Podemos ver ya que existe cierta penalización por el uso del hipervisor, aunque es ciertamente pequeña (~1% en mi caso). El siguiente paso sería realizar una medición con una única máquina virtual encendida con todos los recursos asignados a ella en exclusiva:

mem_vir_anchomem_vir_lat

En este punto podemos ver que las penalizaciones ya son algo más significativas pero bastante razonables (~3-4% en mi caso). A continuación vamos a lanzar en la máquina física una consulta muy sencilla a nivel de cómputo pero que requiere realizar un SCAN de una tabla que contiene aproximadamente 5 GB de datos y 300 millones de filas:

SELECT MAX (countervalue) FROM [Perfmon].[dbo].[CounterData]

GO 1000

Ejecutaremos la consulta 1000 veces mientras medimos el rendimiento de la memoria de nuevo dentro de la máquina virtual:

mem_vir_ancho_loadmem_vir_lat_load

Podemos ver que la presencia de carga externa afecta ya de una forma más sustancial a las mediciones en la máquina virtual. Concretamente vemos que nos aumenta la latencia de forma sustancial y se nos reduce el ancho de banda disponible.

Escenario Ancho banda (GB/s) % reducción Latencia (ns) % aumento latencia
Valor ideal 25,6 89
Sin hyper-v en host 18,66 27,11% 95 6,74%
Hyper-v en host 18,53 27,62% 99 11,24%
VM sin carga 17,78 30,55% 101 13,48%
VM con carga externa 15,99 37,54% 250 180,90%

En este último escenario el aumento en la latencia a la memoria es bastante sustancial, lo cual implica que en que aquellos SQL Server que tengan operaciones que requieran baja latencia, iteren por cursores, etc. pueden sufrir especialmente. No olvidemos que los principales proveedores de hipervisores como Xen,Microsoft,VMware, etc. disponen de guías/whitepapers para configurar máquinas virtuales que requieran “baja latencia”/“tiempo real” y también disponen de guías para configurar máquinas virtuales para SQL Server. Nuestra recomendación es que leamos ambas guías para nuestro hipervisor y sigamos las recomendaciones de forma conjunta para entornos OLTP de alto rendimiento (ya que suelen ser recomendaciones complementarias).

Tras la lectura de dichos documentos veremos como la “magia de la virtualización” realmente no existe y la línea general persigue aislar la VM de SQL Server lo máximo posible del resto de cargas realizando una especie de “particionado” hardware. Es decir, se tiende al uso de cores reservados, de sockets NUMA en exclusiva, de bancos de memoria dedicados, de tarjetas de red exclusivas, de priorización de interrupciones, etc. buscando que las “interferencias” generadas por parte de otras máquinas virtuales sean menores. De hecho en ocasiones como “prueba rápida” se puede dejar la máquina virtual SQL Server ejecutando en exclusiva en un host para verificar que en esas condiciones el rendimiento es el esperado. A partir de ese punto cuanto más nos alejemos de esa configuración 100% aislada, peor será el rendimiento aunque no siempre nos afectará de la misma manera.

Si dejamos de lado esta aproximación indirecta a medir el uso del ancho de banda nos queda la posibilidad de instalar contadores propietarios del fabricante del microprocesador. Difícilmente podremos abordar esta labor en un entorno de producción ya que implica compilar e introducir un nuevo driver en el sistema. Si nuestro procesador es Intel tenemos el código disponible aquí: https://software.intel.com/en-us/articles/intel-performance-counter-monitor/  Lo que se proporciona es únicamente el código fuente, sin ningún instalable ni ejecutable listo para usar. Por tanto necesitaremos hacer algo de trabajo extra:

  1. Instalar el Windows Driver Kit (WDK) https://msdn.microsoft.com/en-gb/windows/hardware/hh852365.aspx Este paso es necesario para poder cargar drivers en el kernel, pero también podemos utilizar las dll WinRing0 ya compiladas de otro software, como por ejemplo de RealTemp (http://www.techpowerup.com/realtemp/)
  2. Instalar Visual Studio con el compilador de C++
  3. Compilar la librería IntelPerformanceCounterMonitor-PCM-V2.10\Intelpcm.dll
  4. Compilar el servicio IntelPerformanceCounterMonitor-PCM-V2.10\PCM-Service_Win
  5. Copiar el resultado de las compilaciones anteriores en una carpeta.
  6. Instalar el servicio con PCM-Service.exe -Install
  7. Arrancar el servicio ‘net start pcmservice’

En el siguiente enlace incluyo un ZIP con el resultado de los pasos del 1 al 5 por lo que se podrían instalar directamente (https://onedrive.live.com/redir?resid=7498331004712053!306110&authkey=!ALYycIqIOtEXOSw&ithint=file%2czip). Para poder realizar la instalación se requiere del Visual C++ Redistributable for Visual Studio 2015 (https://www.microsoft.com/en-us/download/details.aspx?id=49984) así como de permisos de administrador local

Una vez tenemos el servicio en marcha el siguiente paso es comprobar que podemos acceder a los contadores desde Performance Monitor:

contadores_driver

Como podemos ver, no todos los contadores están disponibles en el procesador de mi portátil, de hecho únicamente aparecen con datos los relativos al consumo de energía pero no los de consumo del ancho de banda de la memoria.

contadores_driver2

La cosa cambia cuando se instala en un procesador para servidores, por ejemplo en un servidor con 2 sockets con procesadores Intel E5 v3:

contadores_driver3

Este servidor se encuentra inactivo de ahí que el ancho de banda de la memoria en uso sea de unos pocos MB/s únicamente. En un entorno virtualizado de producción con bastantes máquinas ejecutando podemos ver valores de varios GB/s de forma casi sostenida. Si lanzamos la consulta anterior (SELECT MAX (countervalue) FROM [Perfmon].[dbo].[CounterData]), vemos como se produce un pico importante en las lecturas:

contadores_driver4

En este caso, como la consulta únicamente utiliza un socket, el impacto lo hemos tenido únicamente en la controladora de memoria de uno de los procesadores, donde hemos llegado a un valor de prácticamente 18 GB/s:

contadores_driver5

Este tipo de procesadores dispone de cuatro canales independientes lo que le permitiría alcanzar hasta 68 GB/s. Nuestra recomendación es añadir este tipo de contadores a nuestra monitorización ya que puede ser información clave a la hora de solucionar problemas de rendimiento, especialmente en entornos virtualizados. Por concluir con un ejemplo, en un cliente nos encontramos con una máquina virtual con SQL Server en un host donde habían otras máquinas virtuales pequeñas con poca/nula carga. Esto que en principio parece una idea razonable, ya que dejaba muchos recursos libres para la virtual de SQL Server, generaba problemas por excesivos cambios de contexto de CPU. El hipervisor necesita asignar cada cierto tiempo CPU a todas y cada una de las máquinas virtuales lo cual generaba un alto número de cambios de contexto entre máquinas y de “robos” de CPU a la virtual con SQL Server. No olvidemos que el modelo de SQL Server de planificación de tareas (tasks >> workers >> scheduler >> core) busca poder controlar el tiempo que estamos ejecutando y prioriza una salida voluntariamente de la CPU para maximizar la eficiencia total. Si tenemos un factor externo (operativo o hipervisor) que nos cambia de contexto de forma continua lo que conseguiremos es que esa eficiencia se pierda en gran medida.

0 Shares:
Deja una respuesta

Tu dirección de correo electrónico no será publicada.

You May Also Like

Carga de Slowly Changing Dimensions y tabla de Hechos con atributos de Tipo 2 (Parte 2 de 3)

Este es el segundo post de la serie en el que explicaremos como cargar nuestra tabla de Hechos a partir de una dimensión con atributos de Tipo 2, usando dos maneras diferentes, una de ellas será mediante un componente “Look Up” con caché parcial y la otra opción será usando un componente “Merge Join” con un “Conditional Split” para seleccionar el registro que se encuentra en el rango de fechas correcto. Para mas información sobre qué es un atributo de Tipo 2 y sobre como cargar la dimensión que usaremos en este ejemplo puedes consultar el primer post de la serie.