Es muy habitual que nos encontremos en nuestros clientes situaciones de lo más variopintas en cuanto al rendimiento de las máquinas virtuales con SQL Server. En muchos casos lo que encontramos son casos donde el rendimiento está lejos del ideal, pero en general la virtualización en sí, no tiene la culpa. Lo que suele ocurrir es que cuando virtualizamos un SQL Server que teníamos en máquina física pasamos a disponer de una cantidad máxima o puntual de recursos (CPU/memoria/IO) sustancialmente distinta a las que teníamos en la máquina física.

En general las asignaciones de memoria, poco a poco vemos que van siendo más razonables y se reducen los problemas de balloning que sufríamos en el pasado. En el caso de la CPU seguimos viendo ciertos problemas cuando tenemos cargas muy críticas en la “latencia” de acceso a dicha CPU lo cual las hace muy susceptibles a los “ruidos” causados por otras máquinas virtuales. En general debemos pensar que el comportamiento de unos cores que están con una utilización media elevada no puede compararse al que tendríamos con unos cores “descargados”. Esto es cierto tanto en virtualización como en máquina física si compartimos la CPU con otros procesos.

Desde el punto de vista de CPU lo ideal es que SQL Server pueda contar con el máximo porcentaje de sus cores de forma dedicada y no hagamos “trampas”. Con trampas me refiero a presentar cores a SQL Server de los que, de forma efectiva, solo podrá hacer un uso muy bajo, de un 20% por ejemplo. Es preferible disponer de un número menor de cores virtuales pero que pueda contar con ellos hasta un 90% de uso en caso necesario.

Sin embargo, aún solemos encontrarnos una mayoría de casos donde el “problema” principal es pasar de un entorno con latencias de disco bajas, con un throughput predecible, etc. a un entorno con latencias de disco más elevadas y un throughput mucho más variable.

En aplicaciones que tienen una alta carga transaccional, como ERPs por ejemplo, estas circunstancias pueden alargar mucho procesos críticos para el negocio, como las facturaciones mensuales. Es por ello que queremos ilustrar como, una misma máquina con la misma cantidad de CPU y memoria, puede variar muy sustancialmente su rendimiento en función del rendimiento de disco. Si suponemos que la memoria está bien dimensionada, el rendimiento de lectura/escritura en discos de datos no suele ser un problema y donde encontramos la mayor parte de problemas es en el rendimiento de escrituras en el log de transacciones.

Vamos a crear 3 copias exactas de una base de datos “estilo tpc-c” creada con HammerDB. Los que no conozcáis la herramienta podéis descargarla en su web oficial  (https://www.hammerdb.com/). Básicamente lo que nos facilita es la creación de una base de datos de pruebas “similar” a la TPC-C contra la que podremos lanzar un conjunto de usuarios virtuales que ejecutarán carga contra ella. También es capaz de generar una base de datos y carga similar a TPC-H con lo que podremos también realizar pruebas de carga de naturaleza analítica con esta herramienta.

En nuestro caso al tratarse de una máquina virtual pequeña, hemos creado únicamente 4 warehouses. El número de warehouses determinará en gran parte cómo de distribuida estará la carga ya que se presupone que los usuarios asociados a un warehouse harán hasta el 90% de sus operaciones contra su warehouse local. Por tanto, a mayor número de warehouses normalmente es más sencillo alcanzar valores de TPC mayores siempre que tengamos memoria suficiente. El objetivo en nuestro caso es mostrar cómo afecta el rendimiento del disco en el rendimiento manteniendo el resto de variables (CPU y memoria) constantes. Concretamente utilizaremos una VM con 8 vCores y 16 GB de RAM a la que adjuntaremos tres tipos de discos distintos, el más lento será un RAID 1 de discos SAS, el segundo será un SSD SATA y el tercero corresponderá con un almacenamiento all-flash de alto rendimiento (de uno de los líderes según el cuadrante de Garner):

Las tres bases de datos serán idénticas con el único cambio del storage en el que residen. Los nombres asignados corresponden con el rendimiento esperado, “tortoise” para el RAID 1 de SAS, “kangaroo” para el disco SSD SATA y “cheetah” para el storage high end all-flash:

Comenzaremos con el escenario tortoise, lanzando 16 virtual users sin think time, para generar una cantidad de carga suficiente para saturar la máquina. Durante la carga el rendimiento se estabiliza alrededor de 41K tpm:

Si revisamos las esperas más significativas vemos que tenemos una cantidad significativa de bloqueos, algo esperable en este escenario y en segundo lugar tenemos esperas de tipo WRITELOG con una media de 17.7ms de espera. En general para un rendimiento “normal” esperaríamos valores medios entre 1 y 5 ms para las escrituras en el log de transacciones. Para rendimientos “buenos” los valores deben estar por debajo de 1 milisegundo.

A continuación, pasaremos a testear la base de datos kangaroo con la misma configuración que el caso anterior. En este caso vemos que llegamos a multiplicar casi por 5 el rendimiento, llegando a algo más de 200K tpm:

Si analizamos las esperas vemos que son similares en su distribución al caso anterior, es decir, tenemos más o menos un 60% de esperas de bloqueos y un 30% de esperas por escritura al log:

La mejora de rendimiento es producida por la disminución de las esperas medias. En el caso de los bloqueos bajamos de una media 61.5 ms a 16.3ms y en el caso de la escritura en el log bajamos de 17.7ms a 5.7ms. Estas latencias medias de escritura en un SSD pueden parecer elevadas, y realmente lo son, pero son normales cuando se produce una saturación del dispositivo. También debemos tener en cuenta que las escrituras son mucho más costosas que las lecturas para la mayor parte de los SSDs.

Finalmente, lanzaremos la misma prueba contra la base de datos cheetah. En este caso vemos cómo el rendimiento sube hasta 1.1 M tpm, quintuplicando el rendimiento de los SSD sata y multiplicando por 25 el rendimiento respecto al disco magnético SAS. Una diferencia de 25 veces en el rendimiento cambiando simplemente entre un disco lento y uno rápido es algo que debería resultar bastante “iluminador” a la hora de decidir donde debemos invertir para tener un buen rendimiento de este tipo de cargas:

En este caso si analizamos las esperas podemos ver que existen diferencias. Seguimos teniendo al frente las esperas por bloqueos, con prácticamente el 50% del total de esperas. La duración media de las esperas por bloqueo ha bajado de los 16.3ms que teníamos con el SSD a tan solo 2.2 ms de media (7.4 veces menos). Además, las esperas por escrituras en el log se han reducido de un 30% a un 7.29%, con una bajada de la espera media de 5.6ms a 0.1ms (56 veces menos):

Con el uso de esta tecnología ultra rápida de disco el cuello de botella ya no recae sobre éste. Pasamos de tener el disco como causa principal de las duraciones de bloqueos elevadas, que indirectamente afectan a los tiempos de transacción y por tanto a la duración de los bloqueos, a que sea el propio gestor de bloqueos, el almacenamiento row (vs in-memory por ejemplo) y la falta de más CPU (o más rápida por core) principalmente lo que nos esté frenando (se satura por uso de CPU la VM) para alcanzar una mayor concurrencia y rendimiento.

Podemos apreciar también la importancia de una IO rápida también en otras tareas, como por ejemplo un REBUILD de un índice. Aunque la reconstrucción tiene una parte de CPU importante también es muy sensible al rendimiento de disco. Si lanzamos un rebuild en el disco lento vemos que nos lleva casi 35 segundos con caché fría y 28 con caché caliente:

use [tortoise]

GO

DBCC DROPCLEANBUFFERS

GO

-- Cold data

ALTER INDEX [order_line_i1] ON [dbo].[order_line] REBUILD

GO

--CPU time = 11594 ms,  elapsed time = 34786 ms



-- Hot data

ALTER INDEX [order_line_i1] ON [dbo].[order_line] REBUILD

GO

--CPU time = 11735 ms,  elapsed time = 28423 ms.

En la base de datos con disco SSD SATA mejoramos estos tiempos, aunque menos de lo que esperábamos, pasando de 35 segundos a 27 segundos con caché fría y de 28 segundos a 23 segundos con caché caliente:

use [kangaroo]

GO

DBCC DROPCLEANBUFFERS

GO



-- Cold data

ALTER INDEX [order_line_i1] ON [dbo].[order_line] REBUILD

GO

-- CPU time = 11360 ms,  elapsed time = 26986 ms.



-- Hot data

ALTER INDEX [order_line_i1] ON [dbo].[order_line] REBUILD

GO

--CPU time = 11594 ms,  elapsed time = 22609 ms.



Por último, el storage más rápido no únicamente provee de mejores latencias, también un mejor throughput, lo cual se traduce en una reducción del tiempo de reconstrucción, bajando hasta los 10 segundos y obteniendo casi el mismo tiempo con caché fría que caliente:

USE [cheetah]

GO

DBCC DROPCLEANBUFFERS

GO

-- Cold data

ALTER INDEX [order_line_i1] ON [dbo].[order_line] REBUILD

GO

--CPU time = 9858 ms,  elapsed time = 10363 ms.



-- Hot data

ALTER INDEX [order_line_i1] ON [dbo].[order_line] REBUILD

GO

--CPU time = 9522 ms,  elapsed time = 9597 ms.





De forma gráfica podemos ver claramente la magnitud de las diferencias:

En conclusión, antes de migrar nuestros servidores físicos a máquinas virtuales es muy recomendable que tengamos en cuenta sus características. Inevitablemente la virtualización añade cierta penalización en el acceso a los recursos, pero eso no es una razón de peso suficiente para evitarla por norma. Las ventajas de un entorno virtualizado son elevadas también, por lo que, salvo escenarios muy concretos, creemos que es una posibilidad que merece ser analizada. En este artículo hemos visto cómo el rendimiento percibido para ciertas cargas SQL es muy dependiente del rendimiento de entrada/salida a disco por lo que, manteniendo la misma CPU y memoria en una máquina virtual, podemos obtener rendimientos muy variables.

Rubén Garrigós

Rubén Garrigós is an expert in high-availability enterprise solutions based on SQL Server design, tuning, and troubleshooting. Over the past fifteen years, he has worked with Microsoft data access technologies in leading companies around the world. He currently is a Microsoft SQL Server and .NET applications architect with SolidQ. Ruben is certified by Microsoft as a Solution Expert on the Microsoft Data Platform (MSCE: Data Platform) and as a Solution Expert on the Microsoft Private Cloud (MSCE: Private Cloud). As a Microsoft Certified Trainer (MCT), Ruben has taught multiple official Microsoft courses as well as other courses specializing in SQL Server. He has also presented sessions at official events for various Microsoft technologies user groups.

Latest posts by Rubén Garrigós (see all)