Partiendo del escenario con AKS creado en la parte 1 de este artículo nos vamos a centrar ahora en testear un escenario más habitual.  Además de habitual, es uno de los más críticos por su impacto y consiste en enfrentarnos a un cursor o proceso serial “comandado” por el cliente. Es un escenario donde un bucle de cliente lanza peticiones en serie a la base de datos y espera respuesta del servidor para cada una. En este tipo de escenarios la latencia “por fila” es crítica para el tiempo total. El siguiente script va a crear una tabla, realizar 1000 inserciones y medir los tiempos de dicha operación:

CREATE TABLE dbo.test1234 (id int identity, data char(100), date datetime)
GO
set nocount on
INSERT INTO dbo.test1234 (data,date) values (replicate(‘A’,100), GETDATE())
go 1000
DECLARE @time datetime = (SELECT TOP 1 date from dbo.test1234)
select datediff(ms, @time,getdate()), datediff(ms, @time,getdate())/20000.0 ms_per_insert
DROP TABLE dbo.test1234
go
 

Preparando el entorno cliente-servidor en la nube

Para testear la parte AKS, hemos creado una VM en la misma zona, vnet y grupo de recursos donde se aloja nuestro AKS, de forma que sería lo más parecido a “la misma red local” en Azure. Si conectásemos directamente desde un equipo remoto, debido a la latencia de la conexión, obtendríamos resultados varios órdenes de magnitud peores. Estos son los resultados con el acceso al SQL Server en un contenedor desde una VM en la misma VNET en Azure comparados con el acceso a un VM en red local onpremise:

nerwork

Podemos ver como pasamos de tener un tiempo de 0.42 ms por fila a más de 18ms por fila. Una diferencia de esta magnitud nos impactará fuertemente en el rendimiento percibido de cargas SQL que tengan estas características.

Aumentando el tamaño de batch

En estos escenarios una técnica conocida y recomendable para mejorar el rendimiento consiste en agregar operaciones en un único batch, de forma que solo tengamos un roundtrip entre el cliente y la base de datos. Para que las mediciones puedan realizarse de mejor manera, aumentaremos el número de filas a insertar a 20000 para esta prueba:

CREATE TABLE dbo.test1234 (id int identity, data char(100), date datetime)
GO
set nocount on
declare @i int =0
while (@i < 20000)
begin
INSERT INTO dbo.test1234 (data,date) values (replicate(‘A’,100), GETDATE())
set @i=@i+1
end
go
DECLARE @time datetime = (SELECT TOP 1 date from dbo.test1234)
select datediff(ms, @time,getdate()) total_ms, datediff(ms, @time,getdate())/20000.0 ms_per_insert
DROP TABLE dbo.test1234
Go
 

Al lanzar esta operación vemos que los resultados no acompañan y muestran que no es este el problema principal:

network roundtrip

¿Si no es la latencia de red el problema, dónde está el problema?

Podemos ver como en el caso de AKS el problema no viene principalmente por la latencia de red. Pese a realizar esta operación sin pagar el precio del roundtrip por fila, el coste total por fila sigue siendo muy elevado. En el caso de la máquina virtual onpremise, hemos conseguido disminuir a la mitad el tiempo de ejecución por fila, lo cual demuestra que es una buena opción para muchos casos.

¿Qué está ocurriendo entonces si no es un problema de latencia de red? En este tipo de situaciones el siguiente sospechoso es el overhead de tener una transacción por cada fila. En el siguiente test además de procesar todo en un batch encapsularemos también los 20K inserts en una única transacción:

CREATE TABLE dbo.test1234 (id int identity, data char(100), date datetime)
GO
set nocount on
declare @i int =0
begin tran
while (@i < 20000)
begin
INSERT INTO dbo.test1234 (data,date) values (replicate(‘A’,100), GETDATE())
set @i=@i+1
end
commit tran
go
DECLARE @time datetime = (SELECT TOP 1 date from dbo.test1234)
select datediff(ms, @time,getdate()) total_ms, datediff(ms, @time,getdate())/20000.0 ms_per_insert
DROP TABLE dbo.test1234
go
 

Estos son los resultados en este caso:

network roundtrip resultados

Cazando al verdadero culpable

Podemos ver que una vez eliminados los roundtrips de red y el overhead por la realización de un commit por cada fila los tiempos ya bajan a valores más aceptables.

Una vez vistas las diferencias, el siguiente paso es que nos preguntemos… ¿Cuál es la causa raíz de las diferencias abultadas? Para ello podemos recurrir a las esperas que la instancia SQL Server en AKS nos reporta:

waiType

Es decir, por un lado tenemos unas esperas importantes por flush a disco a nivel de operativo y por otra tenemos también esperas importantes de escritura en el log de transacciones. También hay ciertas esperas por coordinación entre threads en planes paralelos aunque dichas esperas también se sufren en la VM onprem. Además al ejecuta los contenedores sobre Linux la optimización de SQL Server sobre Linux también sería algo a revisar. Ello merecería un artículo aparte pero me gustaría aprovechar para añadir al menos un par de referencias interesantes:

Performance best practices and configuration guidelines for SQL Server on Linux

SQL Server On Linux: Forced Unit Access (Fua) Internals

¿Qué podemos hacer para solucionar este problema?

De todas estas esperas la parte que más contribuye a la diferencia de rendimiento en general es el rendimiento de la entrada/salida en las escrituras del log de transacciones. Es muy habitual que en los entornos con Kubernetes la latencia del storage se vea aumentada respecto a lo que estamos acostumbrados, generando diferencias importantes en la percepción del rendimiento habitual de nuestra base de datos. Si analizamos el almacenamiento persistente que se nos crea por defecto veremos que se trata de Standard SSD LRS:

aks

Este tipo de discos no son apropiados para cargas intensivas y sensibles a latencia, son algo mejores que los Standard HDD, pero el incluir “SSD” en el nombre puede llevarnos a falsas expectativas:

standar SSD

Escalando el storage a nivel Premium y comparando manzanas con manzanas

Vamos a modificar nuestro despliegue para que utilice un disco SSD premium P30 en vez de standard. Para ello, además de cambiar a Premium_LRS nuestro storageclass, aumentaremos el tamaño del disco ya que en los discos premium el rendimiento está también determinado por dicho tamaño.

parameters

Es posible que tengamos dudas razonables respecto a si el problema de fondo es el hecho de ejecutar SQL Server en un contenedor. Para tener claro si es un factor relevante o no vamos a crear en la misma máquina que hace de host de la VM onpremise, un contenedor Docker con SQL Server y vamos a probar también esta misma carga utilizando por tanto el mismo hardware (solo cambiará de VM a contenedor). Para poner en marcha un contenedor SQL Server tan solo tendremos que lanzar un sencillo comando como este:

docker run -e “ACCEPT_EULA=Y” -e “SA_PASSWORD=ElPassw0rd” \
-p 1433:1433 –name sql2017 -h sql2017 \
-d mcr.microsoft.com/mssql/server:2017-latest
 

En este caso vamos a mapear el puerto 1433 con el 1433 de nuestra máquina, por lo que si ya tenemos alguna otra instancia SQL Server escuchando en dicho puerto deberemos modificarlo.

Una vez arrancado, relanzaremos los mismos tests anteriores. Estos son los resultados de todos los tests incluyendo los del Docker local y el AKS con disco premium:

serial query
parallel query

Podemos ver que en los dos primeros tests, cuyo rendimiento depende básicamente de la CPU, los resultados no han cambiado. Podemos ver como el rendimiento con Docker local es prácticamente idéntico a la máquina virtual. Por lo que en lo que al rendimiento de CPU respecta, el uso de contenedores no supone ningún problema.

Vamos a analizar los resultados en el resto de tests que son más sensibles a la latencia de entrada/salida de disco y red:

network roundtrip
without network
resultados transaction

Como era de esperar el rendimiento de las operaciones que dependen de la CPU no ha cambiado. Sin embargo para las operaciones más agresivas con el disco podemos ver como el uso de un storage premium en AKS puede mejorar mucho los resultados, quedando alrededor de unos 5-6ms el coste por fila en los escenarios más adversos. Debemos tener en cuenta que las escrituras en discos Azure Premium (usando la política de caché ReadOnly) van directamente contra el storage remoto.

En general para los discos Premium SSD podemos esperar latencias entre 1-2 milisegundos para peticiones que sí se cacheen y alrededor de 5ms para operaciones no cacheadas. Por tanto esos 5-6 ms son prácticamente los propios límites que el tipo de storage nos impone.

Esta situación no ocurre únicamente con AKS, es algo bastante generalizado cuando nos movemos a un entorno cloud. Cuando el rendimiento de escritura es crítico es recomendable “mimar” el log de transacciones y pasar a discos Ultra y/o utilizar aceleradores de escrituras. Os dejo una referencia a un checklist interesante respecto a este tema.

Es interesante ver cómo cuando usamos un contenedor on-premise se aprecia que en los escenarios más exigentes para la entrada/salida los contenedores nos ralentizan más que una máquina virtual, pese a estar utilizando el mismo almacenamiento. Esto puede ser debido a muchas razones, desde una menor eficiencia en el proveedor de almacenamiento persistente, a interacciones inesperadas con antivirus, etc.

Tampoco debemos  olvidarnos que Kubernetes se diseñó inicialmente para proporcionar servicios stateless únicamente y fue posteriormente cuando se añadió el soporte de storage persistente para abrir el abanico de aplicaciones que fuesen capaces de funcionar dentro de contenedores. Es posible que en un futuro se produzca algún tipo de “relevo” en esta tecnología de contenedores/orquestador que permita obtener mejores latencias.

Conclusión

Como siempre, el rendimiento de la entrada/salida es crítico para el rendimiento de SQL Server. Las latencias en proceso RBAR (row by agonizing row) son críticas para que el tiempo total no se dispare. También tenemos que tener en cuenta que un AKS no deja de ser un despliegue de Kubernetes sobre máquinas virtuales, no es un despliegue bare-metal. Cuando buscamos maximizar el rendimiento de la entrada/salida y reducir las latencias cuantas menos capas tengamos hasta llegar al hardware físico más sencillo será obtener buenos resultados.

Por tanto, si nos planteamos mover cargas SQL a un sistema Kubernetes gestionado como AKS o a cualquier sistema de contenedores, es muy importante que revisemos el rendimiento en general y especialmente el que su almacenamiento persistente pueda proporcionar y ajustar en base a ello nuestras expectativas de rendimiento alcanzable para nuestras cargas SQL Server en contenedores.

¡Sigue Aprendiendo!

Si quieres que te ayudemos a poner en marcha este tipo de proyecto, no dudes en contactar con nosotros. También puedes aprovechar las formaciones existentes para ampliar tus conocimientos 👇🏼

>> Más info
0 Shares:
Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

You May Also Like

Cuando haces BOT ya no hay stop

Hoy en día no es raro encontrarse con un Bot como medio para interactuar con una plataforma online. La creciente implantación de esta tecnología en el mercado, nos invita a conocer Azure Bot Framework. Repasaremos las herramientas disponibles, el proceso de creación, buenas practicas, casos de uso y muchos más. Acompáñanos ¡cuando hacer bot ya no hay stop!