Muchas veces se hace hincapié en los beneficios de las operaciones orientadas a conjuntos versus las operaciones fila a fila. No vamos a entrar en la ya muy analizada ventaja de dicho enfoque sino en mostrar de forma práctica el impacto que podemos llegar a sufrir si no lo hacemos.

Habitualmente este tipo de patrones lRBAR los encontramos cuando se utilizan en las aplicaciones bucles donde internamente se realizan accesos a base de datos. En aplicaciones complejas, con múltiples capas, etc. es a veces difícil determinar si una llamada concreta acabará generando una llamada o no a la base de datos.

Cada vez que realizamos una petición a la base de datos hemos de ser conscientes de lo compleja y lenta que puede resultar una operación de este tipo comparada con otras. Para comenzar, un acceso a base de datos implica una comunicación por red, donde mediante un protocolo (TDS sobre TCP/IP en el caso de SQL Server) tenemos que intercambiar por red un mínimo de dos paquetes, uno para la petición y otro para la respuesta. Este coste lo tendremos siempre que intentemos acceder a un servicio que se encuentre físicamente separado del cliente que lo solicita y aplica por tanto a servicios de base de datos, servicios web, etc.

A este tiempo mínimo de intercambio de mensajes debemos añadir el tiempo de procesado de dicha petición. En un escenario poco favorable (adhoc sin parametrizar) implicaría pasar por muchas fases como el parseo de la consulta, la normalización, la compilación, la optimización, la preparación de la ejecución, la ejecución de la consulta y la devolución de los resultados.

Suponiendo un escenario ideal donde todas estas fases estén optimizadas al máximo me gustaría mostrar el impacto que la latencia de red puede tener en el tiempo final de ejecución. En estos últimos años vamos viendo como esta latencia en muchos escenarios empresariales en vez de disminuir va en aumento, causando problemas en aplicaciones existentes que estaban funcionando correctamente previamente. Las razones principales son dos, el uso de virtualización y de entornos cloud.

Con la virtualización añadimos capas adicionales entre el cliente y el servidor que hacen que la latencia de red aumente inevitablemente. Estos aumentos pueden parecer muy pequeños y son del orden de microsegundos por ejemplo cuando hablamos de virtualización sobre redes de 10 Gbps. El problema es el efecto multiplicador que se genera con las múltiples iteraciones en estos bucles que comentábamos anteriormente.

Para poder ver el impacto de la latencia utilizaremos un simulador de condiciones de red gratuito llamado Clumsy (http://jagt.github.io/clumsy/) Existen otras soluciones comerciales mucho más completas pero para demostrar el impacto de la latencia es más que suficiente.

Como petición a la base de datos vamos utilizar una de las más sencillas posibles, un sencillo «select 1». Primero ejecutaremos dicha operación 1000 veces conectándonos por TCP/IP localmente a una instancia de SQL Server:

latencia_1

Podemos ver como hemos tenido 1000 roundtrips al servidor, 1000 paquetes enviados y 1000 paquetes recibidos. El tiempo total de ejecución ha sido de 109 ms (109 microsegundos por petición). Vamos a ver lo que ocurre cuando simulamos una latencia de 1 milisegundo en el acceso a la red. Para ello configuraremos Clumsy para que añada 1 milisegundo de latencia:

latencia_2

No está de más que verifiquemos mediante un ping a nuestra dirección local que realmente estamos en valores de ~1ms ya que el uso de ciertos antivirus o herramientas de monitorización de red pueden aumentar la latencia real. A continuación lanzaremos la misma carga:

latencia_3

Podemos ver que hemos aumentado el tiempo total del proceso hasta 2764ms, unas 25 veces el tiempo que teníamos sin la latencia añadida. Ciertamente 1 milisegundo de retraso es un valor elevado en el caso de virtualización pero no es un valor impensable en algunos entornos cloud. En casos donde interconectamos distintos centros de proceso de datos la latencia puede ser aún mayor entre máquinas situadas en distinto centro de proceso de datos.

En el caso de virtualización tradicional el overhead suele oscilar entre un mínimo de unos 20us en entornos con baja carga, con ratios de consolidación bajos, etc. y valores cercanos a 100us en entornos con alta carga y valores de consolidación altos (demasiado habituales desgraciadamente). Es decir que hablaríamos de entre 40us y 200us extras por petición sobre una línea base de 109 us por petición que teníamos en el acceso local. Esto se traduce muchas veces a efectos prácticos y desde el punto de vista de experiencia del usuario o del desarrollador en que la aplicación «en local/máquina física» funciona mucho más rápida. Con todo esto no queremos transmitir la falsa impresión de que estemos en contra de la virtualización, de los entornos cloud o de que se extiendan las soluciones a lo largo de múltiples centros de datos. Únicamente pretendemos que se sea plenamente consciente de las implicaciones de rendimiento que pueden existir.

Como posibles soluciones a estos problemas solemos recomendar como primera opción analizar si es viable un enfoque orientado a conjuntos. Si éste no es posible, planteamos alternativas que empaqueten las llamadas, por ejemplo de 100 en 100, de forma que en un único batch realicemos 100 operaciones de golpe. Si tampoco es posible (aunque también es recomendable combinarlo con lo anterior) otra alternativa es hacer uso de múltiples threads que de forma paralela ejecuten las operaciones. Por ejemplo si la prueba anterior la lanzamos en paralelo desde 4 threads con 250 peticiones individuales cada uno, con la misma latencia de 1 milisegundo el tiempo total baja a unos 577ms por cada thread, a lo que habría que sumar el coste de sincronización entre los threads.

latencia_4

En el caso de virtualización hemos tenido buenas experiencias utilizando colocación condicional de las máquinas virtuales de forma que tengamos virtualizadas en el mismo host físico aquellas máquinas que intercambien peticiones que requieran baja latencia. En esos casos la comunicación a través de un interfaz virtual, sin necesidad de acceder físicamente a la red, puede llegar a tener menos latencia incluso que la comunicación tradicional entre dos máquinas físicas no virtualizadas.

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)