Rendimiento

Solución a problemas de Parameter Sniffing

Seguimos con el tema de ayer sobre el Parameter Sniffing en SQL Server y, como prometimos, vamos a ver cómo podemos hacer para controlarlo y beneficiarnos de ello minimizando sus inconvenientes. Es este, por tanto, una segunda parte del artículo de ayer que os recomiendo encarecidamente leer antes de continuar con este. Como ya sabemos, si la distribución de nuestros datos es relativamente equitativa, un reaprovechamiento del plan de ejecución de una consulta será muy beneficioso para el rendimiento. De lo contrario, si la distribución de datos tiene gran variación, reutilizar un plan hará que este no sea el óptimo para esa ocasión. 

¿Es malo el parameter sniffing?

Empecemos por el principio, la pregunta que todos os estáis haciendo ¿es malo el parameter sniffing para el rendimiento? Para mi la respuesta es no, aun con todos sus inconvenientes, conociendo su comportamiento podremos beneficiarnos en gran medida de ello. En la mayoría de escenarios OLTP, el comportamiento normal del parameter sniffing mejora el rendimiento de las consultas. Para los escenarios en los que nos encontramos con inconvenientes, normalmente será en algún procedimiento almacenado y no todos y, por suerte, tenemos varias alternativas para solventar el problema.

Identificando el problema del parameter sniffing

El primer paso cuando tenemos un problema es reconocerlo y en esta ocasión será sencillo. Si no tenemos habilitado la parametrización forzada bastará con ejecutar nuestra consulta fuera del procedimiento almacenado para comparar los planes. En otras ocasiones los usuarios nos darán pistas sin quererlo, como cuando unos clientes me decían que tras reiniciar el servidor el procedimiento volvía a funcionar. Reiniciar, por si solo no resuelve nada y si no somos capaces de saber que provocaba el problema antes del reinicio nos volveremos a encontrar en esa misma situación antes o después. Pero esta discusión es para otra ocasión, volvamos a mi anécdota. 

Lo que les pasaba a mis clientes es que si la primera ejecución del plan de la caché era con un parámetro con un volumen de datos muy inferior a la media, el plan cacheado iba a ir mal para todas las siguientes ejecuciones. Como la caché de SQL se vacía al reiniciar y ellos estaban esperando el reinicio para ejecutar el SP con los parámetros que antes daban problemas ya se almacenaba en caché el plan correcto y todo iba bien hasta que por presión de memoria ese plan se borraba y la siguiente ejecución era de un parámetro distinto.

Soluciones al problema del parameter sniffing

Bien, sabemos que el causante de nuestro problema de rendimiento es un problema de parameter sniffing. Ahora tenemos que solucionarlo. Para ello os voy a proponer distintas soluciones.

No cambiar nada

Sabemos lo que está pasando y que es el comportamiento natural de SQL Server, expliquemos todo esto a nuestros usuarios y que se conformen con el resultado. No va a funcionar, ¿verdad?. Habéis soltado una carcajada al leerlo que se ha oído en la luna, que os conozco. Los usuarios de SQL necesitan sus datos y los necesitan rápido y por mucho que nosotros les contemos no se van a conformar. Y están en su derecho así que descartemos este punto y vayamos a por los siguientes. 

Pasa el marrón a otro

La siguiente solución que tengo que poner, pero que, al igual que la anterior, tampoco os recomiendo es pasar la pelota al equipo que desarrolla el código del procedimiento. Podríais explicarles lo que está pasando y que creen un SP distinto para cada parámetro. Como os digo esto es una mala idea, malísima en realidad. Acaba completamente con todas las ventajas de un procedimiento almacenado y ni hablar de si problema si es causado por tener activada la parametrización forzada. Descartemos este punto también por favor.

Actualiza tu SQL

Ya comentamos ayer que el las últimas versiones de SQL Server (a partir de 2019) entran en juego los planes de ejecución con joins adaptativos lo que nos permitirá que pasadas unas ejecuciones se persistirá un plan dinámico en caché con varias alternativas en función de los parámetros. Esto es un avance, sin embargo, aún no lo veo una solución pues necesitas de varias ejecuciones lentas para que SQL se de cuenta de lo que pasa y en un entorno con gran cantidad de consultas donde los planes en caché no duran tanto como nos gustaría puede no ser una solución.

Recompilaciones del procedimiento

En este punto, ya hemos descartado no hacer nada y también crear varios procedimientos almacenados, veamos cómo podemos hacer para que nuestro procedimiento problemático rinda como debería. Una de estas soluciones es crear el procedimiento para que no haga uso de los planes en caché y recompile siempre el plan de ejecución. Esto lo haremos en la declaración del procedimiento con la sugerencia de procedimiento almacenado RECOMPILE. Por ejemplo: 

Con esa simple sugerencia conseguiremos que los planes de ejecución de todas las consultas del procedimiento almacenado se recopilen antes de la ejecución, lo que nos supondrá un mayor coste de CPU pero nos garantizará un plan óptimo. Si es uno o unos pocos procedimientos en los que tenemos problemas al final compensa.

Recompilaciones de las consultas

Si el problema lo estamos teniendo fuera de un procedimiento almacenado por tener habilitada la parametrización forzada o si queremos hilar más fino porque sabemos que solo una de las muchas consultas de un procedimiento es la que tiene problemas podemos hilar más fino y aplicar la sugerencia RECOMPILE a nivel de consulta. Por ejemplo 

En este ejemplo tenemos dos consultas, un select y un insert, sin embargo solo recopilaremos el plan de ejecución de la primera.

Optimizado para valor

Otra de las opciones que tenemos a nivel de consulta es utilizar una sugerencia que indique que calcule el plan de ejecución para un valor concreto y no para el que se pase como parámetro de nuestro SP. En algunas ocasiones puede ser una solución pero a mi no me gusta porque genera planes ultra dimensionados cuando no son necesarios y requiere mucho mantenimiento a medida que los datos cambian. Si recordáis el ejemplo del almacén que vimos ayer es como si desplegamos todos los recursos necesarios para mover maquinaria industrial pesada para al final realmente mover un clavo. Aun así os dejo un ejemplo de cómo sería:

Conclusión

Aunque siempre digo que no debemos influir sobre el comportamiento normal de SQL Server en estos casos siempre hago una excepción. Nosotros conocemos nuestros datos (o deberíamos) y cuando el parameter sniffing no termine de adaptarse a nuestras necesidades no debemos tener miedo de actuar. Usa todas las herramientas que SQL pone a nuestra disposición y anticipate a las llamadas de usuarios descontentos, Query Store tiene una vista de consultas recursivas que nos mostrará estos casos de una manera muy cómoda. Añade tus sugerencias de consulta o procedimientos y no dejes que nada frene tus consultas.

Espero que este artículo te haya sido útil y que te ayude a optimizar el rendimiento de tus consultas en SQL Server. Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Otros, Rendimiento, SQL Server, 0 comentarios

Parameter Sniffing, ¿aliado o enemigo del rendimiento?

Vamos a pasar el mal trago de ayer con los índices de SQL Server que nos perjudicaban el rendimiento y vamos a ver un aspecto que sí podemos controlar nosotros directamente. Mucho hemos hablado de los índices ya en el blog, y también de que SQL Server hace uso de las estadísticas de las tablas para decidir el plan de ejecución más óptimo. Sin embargo no habíamos profundizado en cómo funciona esto y no, no es por arte de magia. SQL Server va a hacer lo que se conoce como parameter sniffing.

El motor de bases de datos de SQL Server utiliza el parameter sniffing para hacerse una idea del volumen de datos de cada una de las tablas que intervienen en nuestras consultas (con los filtros ya aplicados) y así tomar las mejores decisiones. Como habrás adivinado, el parameter sniffing, más allá del tecnicismo (que quedará muy bien si lo soltamos en mitad de una conversación técnica), es una de las claves para el rendimiento de las consultas en SQL Server.

Planes de ejecución de consulta

Lo primero que tenemos que entender es como SQL calcula los planes de ejecución y para ellos, vamos a intentar llevarlo a algo que todos podamos imaginar, así será fácil de entender. Imagina que trabajas de encargado de almacén y tienes que gestionar el trabajo de los mozos del almacén. Te han encargado la tarea de mover todo el stock de unos determinados productos a otro almacén de la compañía. Entre los distintos productos que tienes que mover hay desde tornillos hasta maquinaria industrial. Para poder asignar recursos a las distintas tareas, tendrás que saber cuántos artículos de cada cosa debes mover y cuáles son sus pesos, ¿verdad?. Pues en SQL Server pasa lo mismo, cuando nosotros le pedimos que nos de unos datos, necesita conocer cuántos registros van a ser y cuanto ocupan para poder hacer una asignación correcta de recursos.

Parameter Sniffing en acción

Como podéis imaginar, calcular el plan tiene un coste y es por esto que SQL cachea esos planes para poder reutilizarlos. Esto es realmente útil cuando las consultas se repiten siempre igual pero, ¿qué pasa si cambian? Almacenaremos gran cantidad de planes y muchos serán iguales. Para eso existen los procedimientos almacenados, fragmentos de código que SQL almacena como un objeto, donde las variables están parametrizadas para que nosotros podamos definirlas en cada ejecución. En estas situaciones el plan de ejecución es siempre el mismo porque la consulta es siempre la misma sea cual sea el valor que le pongamos en los filtros. Esto podemos forzarlo para las consultas ad hoc con la opinión parametrización forzada de SQL Server de la que ya hablamos en profundidad aquí

En estas situaciones SQL Server analizará el volumen de datos que coinciden con ese parámetro y lo usará para generar un plan de ejecución que se utilizará en todas las consultas siguientes. A este procedimiento interno de SQL Server se le conoce como Parameter Sniffing y reduce mucho los tiempos de ejecución cuando tenemos una distribución equitativa de los datos. Es decir, los registros pesan más o menos lo mismo sea cual sea el valor. 

Los problemas de Parameter Sniffing

Como hemos visto el parameter sniffing reduce mucha carga de compilación de los planes de ejecución en consultas con una distribución de datos similar pero, ¿Qué pasa si no es así? Volviendo a nuestro ejemplo anterior, ¿asignaremos los mismos recursos para mover un clavo que para mover una máquina de varias toneladas? Obviamente no, y esto es un problema (o lo era). SQL Server asignará los recursos a la consulta en función del plan de ejecución que tiene almacenado y este se habrá calculado en base a los resultados de la primera ejecución de la consulta. Al menos, históricamente siempre ha sido así. En Microsoft conocedores de este problema, implementaron en SQL 2019 una optimización en el procedimiento de parameter sniffing y en los planes de ejecución añadiendo los adaptative joins. 

Parameter Sniffing a partir de SQL 2019

En las últimas versiones de SQL Server (2019 y 2022), lo que se hace es compilar el plan de ejecución de varias ejecuciones de la consulta. Si el resultado de estas primeras compilaciones es siempre el mismo plan, se cacheará ese y el comportamiento será el de siempre, pero, si los planes cambian, se almacenará un plan con un join adaptativo que permitirá aplicar un plan u otro en función de los parámetros de la consulta.

Parameter Sniffing caso práctico

Vamos a ver esto en la práctica que con ejemplos es como mejor se quedan las cosas. Para ello vamos a usar la base de datos de demo AdventureWorks.

Si os fijáis, en esta primera imagen, la consulta es la misma y solo cambia el valor que le pasamos al filtro. Solo con eso, dada la cardinalidad de la consulta, vemos como el motor de base de datos calcula un plan de ejecución distinto para cada una de ellas.

Vamos a probar ahora a crear el siguiente procedimiento almacenado, como veis es la misma consulta de la imagen de antes pero hecha procedimiento almacenado.

Con el procedimiento creado mirad lo que pasa. Calcula el plan de ejecución en base al primero de los parámetros y ese es el plan de ejecución que se queda para siempre.

Conclusión

Hoy hemos podido aproximarnos más a cómo SQL usa las estadísticas de las tablas para calcular los planes de ejecución y cómo se comporta cuando usamos consultas parametrizadas. Hemos entendido las ventajas y los inconvenientes del parameter sniffing y lo hemos podido ver en un ejemplo práctico. Dejamos para mañana ver cómo, nosotros como DBAs, podemos influir sobre ello y sacarle todo el partido.

Espero que este artículo te haya sido útil y que te ayude a optimizar el rendimiento de tus consultas en SQL Server. Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 0 comentarios
Un índice que no se usa perjudica el rendimiento de los select

Un índice que no se usa perjudica el rendimiento de los select

¿Alguna vez os han dicho que un índice que no se usa empeora el rendimiento de una consulta SELECT? Todos hemos oído que los índices penalizan la escritura y eso es cierto como que odio madrugar para trabajar pero, las lecturas no se ven afectadas, ¿verdad? ¿Estáis seguros? Recientemente recibí en mi bandeja de entrada el último boletín de la newsletter de SQLAuthority sobre un extraño comportamiento con los índices en SQL Server y me ha parecido tan interesante que he sentido la necesidad de compartirlo con vosotros. Aunque, como os digo, la idea detrás de este post no es mía y es plenamente de Pinal Dave me voy a tomar la libertad de traducirlo y compartirlo con vosotros.

Introducción

Este email que activó mi sentido arácnido:

Hi there,
Have you ever seen that Index which is not used for the query reduces the performance of the SELECT statement?
If yes, good, you can stop reading this email here.
If no, here is the video you MUST WATCH 
That’s it! Have a good day.
~ Pinal from SQL Authority

Cuando lo he leído no me lo podía creer, ¿cómo un índice que no se usa puede empeorar el rendimiento de una consulta de lectura? Sabemos que los índices empeoran los procesos de escritura pero, es justamente porque se usan y se escribe en ellos además de en la tabla. Pero en las lecturas no, eso no es lo que dicen los libros de SQL. Sin embargo, de Pinal me fio completamente (ha demostrado sobradamente saber mucho de esto) así que he ido al video y en efecto, en él demuestra empíricamente lo que dice en el correo.

Tan atónito estaba al terminar los doce minutos de demo de Pinal que he ido raudo a comprobarlo en mi propio servidor de pruebas. En el video, Pinal usa la base de datos AdventureWorks2014 y en las respuestas a los comentarios dice haberlo probado tanto con modo de compatibilidad 2017 como 2019. En mi laboratorio de pruebas yo tengo un 2022 así que perfecto vamos a ver que pasa.

Consulta sin índice

Ejecutamos la consulta sobre la tabla, sin haber creado ningún índice vemos que hace un escaneo de la PK que supone el 42% de la consulta. En cuanto a lecturas, si miramos los mensajes de las estadísticas de E/S vemos que se está leyendo 1238 páginas de disco de 8 Kb, lo que son aproximadamente 10 Mb de datos. Sin embargo para leer esos 10Mb de datos, necesita una tabla auxiliar de la que lee 368.495 páginas o lo que es lo mismo 2,8 Gb de información.

Creemos un índice

Obviamente este rendimiento no es el esperado, leer casi 3 Gb de información para devolver 10 Mb, no sé a vosotros, pero a mi no me parece correcto. Así que creemos un índice llamado IX_1 tal como aprendimos en este otro post y probemos. Vale, tenemos dos lecturas de la tabla y eso no es lo mejor, pero tampoco es un problema. Mirad las estadísticas, tenemos simplemente 610 páginas leídas o lo que es lo mismo, 5Mb de información. 

Creando otro índice

Un caso de uso normal sería ahora crear otro índice invirtiendo el orden de los campos clave para verificar si el rendimiento mejora o no. Sin embargo, al hacerlo, nos vamos a encontrar con la sorpresa que da título a este post. No solo la consulta sigue usando el primero de los índices sino que han vuelto los Table Spool y aunque las páginas leidas de la tabla han bajado a la mitad que antes, tenemos otra vez esos casi 3 Gb de lecturas de la tabla de trabajo. 

Otras pruebas

No os voy a aburrir con más capturas de pantalla, creo que ya lo habéis pillado. Deciros que lo he probado en todos los niveles de compatibilidad posibles de una base de datos desde SQL 2014 hasta 2022 y el resultado ha sido idéntico. 

También he probado con actualizaciones de las estadísticas de la tabla, borrando la caché de planes de ejecución entre las pruebas, con el hint de index para forzarle el uso del índice en las dos ejecuciones y con un hint de RECOMPILE. Todo con el mismo resultado. Para terminar he cambiado la consulta y he usado un join en vez de la subquery pero nada, en todos los casos aparecían esas lecturas al tener el segundo índice creado aunque en el plan de ejecución no aparezca en uso por ningún lado y al borrarlo volvía a los dos escaneos sin lecturas de tablas auxiliares. 

Os diré más, en este punto con ya el artículo escrito se me ha ocurrido una cosa, he borrado el índice 2, he ejecutado la consulta que se ha ejecutado con el plan correcto, he ido a Query Store y he forzado ese plan, luego he creado el índice y, ¿sabéis que? LO HA VUELTO A HACER MAL. Si amigos, incluso forzando en Query Store el plan correcto, SQL me ha sacado el dedo y ha hecho lo peor para él, como un hijo adolescente que no entiende que miras por su bien.

Conclusiones

¿Y ahora que digo yo? ¿Qué conclusión sacamos de esto? Que lo que pone en los libros no es correcto? Igual es que SQL Server hace cosas sin sentido o que visto esto debería pasarme a Oracle? Lo cierto es que no he conseguido encontrar una explicación a lo que está pasando pero ahí está. En su video, Pinal llega al mismo punto, no entiende lo que pasa y obviamente no tiene sentido. Parece un bug del motor de base de datos sin resolver desde hace un montón de tiempo. Por mi parte seguiré probando esto en cada nueva versión de SQL Server y si veo que se resuelve espero poder avisaros por aquí. De momento, no nos queda otra, revisemos los índices de nuestras bases de datos y borremos los que no estén en uso por si nos están penalizando.

Espero que este artículo te haya sido útil y que te ayude a optimizar el rendimiento de tus consultas en SQL Server. Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

PD.: Si alguno queréis ir a la fuente original os dejo el vídeo por aquí.

Publicado por Roberto Carrancio en Índices, Rendimiento, SQL Server, 0 comentarios

Escalado vertical y horizontal en SQL Server y Azure

¿Qué es el escalado en SQL Server y por qué es importante? En este artículo vamos a explicar los conceptos de escalado vertical y horizontal, las diferencias entre ellos y las ventajas y desventajas de cada uno. También veremos cómo aplicar estas técnicas en SQL Server, tanto en la versión On Premise como en la nube de Azure.

¿Qué es el escalado vertical y horizontal?

El escalado es la capacidad de aumentar o disminuir los recursos de un sistema para adaptarse a las necesidades de rendimiento y disponibilidad. Existen dos tipos de escalado: vertical y horizontal.

– El escalado vertical consiste en incrementar o reducir la potencia de un único servidor, ya sea añadiendo o quitando memoria, CPU, disco o cualquier otro componente. Por ejemplo, si tenemos un servidor con 8 GB de RAM y lo ampliamos a 16 GB, estamos haciendo un escalado vertical.

– El escalado horizontal consiste en agregar o eliminar servidores al sistema, de forma que se distribuya la carga entre ellos. Por ejemplo, si tenemos un servidor con 8 GB de RAM y le añadimos otro igual, estamos haciendo un escalado horizontal.

Ventajas y desventajas del escalado vertical y horizontal

Cada tipo de escalado tiene sus pros y sus contras, que debemos tener en cuenta a la hora de elegir la mejor opción para nuestro sistema.

Escalado vertical

– Ventajas: Es más sencillo de implementar, ya que no requiere cambios en la arquitectura ni en el código de la aplicación. Además, al tener un único servidor, se evitan problemas de sincronización, consistencia y latencia entre los nodos.

– Desventajas: Tiene un límite físico, ya que no podemos aumentar indefinidamente los recursos de un servidor. También implica un mayor coste, ya que los componentes más potentes suelen ser más caros. Además, al tener un único punto de fallo (SPOF), se reduce la disponibilidad del sistema.

Escalado horizontal

– Ventajas: Permite un mayor crecimiento, ya que podemos agregar tantos servidores como necesitemos. También implica un menor coste, ya que podemos aprovechar servidores más económicos y estándar. Además, al tener varios nodos, se aumenta la disponibilidad y la tolerancia a fallos del sistema.

– Desventajas: Es más complejo de implementar, ya que requiere cambios en la arquitectura y en el código de la aplicación. Además, al tener varios servidores, se generan problemas de sincronización, consistencia y latencia entre los nodos.

¿Cómo escalar SQL Server On Premise?

Para escalar SQL Server On Premise tenemos varias opciones, dependiendo del tipo de escalado que queramos realizar.

Escalado vertical On Premise

Para hacer un escalado vertical On Premise debemos modificar el hardware del servidor donde está instalado SQL Server. Esto, en algunos casos, implica detener el servicio, cambiar los componentes y reiniciar el servidor. Estos problemas desaparecen, en gran medida, cuando hablamos de servidores virtuales. Algunas recomendaciones para hacer un buen escalado vertical son:

  • Elegir componentes compatibles con el servidor y con SQL Server. 
  • Prestar atención al licenciamiento, algunas ediciones y modos de licenciamiento de SQL Server tienen limitaciones en cuanto a CPU y RAM y si los superamos deberemos adquirir otra licencia.
  • Ajustar los parámetros de configuración de SQL Server según los nuevos recursos.
  • Realizar pruebas de rendimiento antes y después del cambio para verificar la mejora.

Escalado horizontal On Premise

Para hacer un escalado horizontal On Premise debemos agregar más servidores al sistema y distribuir la carga entre ellos. Esto implica crear una arquitectura distribuida, como un clúster, una réplica o una partición. Algunas recomendaciones para hacer un buen escalado horizontal son:

  • Elegir servidores con características similares al existente.
  • Configurar correctamente la redirección del tráfico entre los nodos.
  • Mantener la sincronización y la consistencia de los datos entre los nodos.

Las réplicas de solo lectura de los grupos de disponibilidad Always On son un ejemplo de este tipo de escalado. Añadiendo una réplica de solo lectura a nuestro grupo de disponibilidad podremos redirigir a ella las operaciones de lectura descargando de trabajo el nodo principal.

¿Cómo escalar SQL Server en Azure?

Para escalar SQL Server en Azure tenemos varias opciones, dependiendo del tipo de servicio que estemos usando.

Escalado vertical en Azure

Para hacer un escalado vertical en Azure debemos modificar el tamaño del servicio donde está alojado SQL Server. Esto implica cambiar el nivel de servicio o el plan de tarifa, lo que puede implicar un cambio de precio. Algunas ventajas de hacer un escalado vertical en Azure son:

  • No requiere detener el servicio ni reiniciar el servidor.
  • Se puede hacer desde el portal de Azure o mediante scripts.
  • Se puede automatizar según las métricas de rendimiento.
  • Valorar el apagado de los servicios cuando no están en uso para un menor coste. Esto es especialmente útil cuando hablamos de entornos de desarrollo y pruebas.

Mención especial en este apartado para las bases de datos de Azure en modo de licenciamiento sin servidor donde podremos adaptar los recursos según la carga de trabajo. Aumentando en horas punta y disminuyendo la cantidad de recursos en momentos de menos carga.

Otra opción muy interesante que se nos plantea en Azure son los grupos de recursos, podremos asignar un extra de recursos a un grupo con varios servicios para que los usen en caso de ser necesario. Esto nos permite no tener que sobredimensionar todos y cada uno de los servicios por separado y reducir costes. Si, por ejemplo, nuestra base de datos transaccional tiene su pico de trabajo por el día y la informacional por la noche podrán compartir un grupo de recursos.

Escalado horizontal en Azure

Para hacer un escalado horizontal en Azure debemos agregar más instancias al servicio donde está alojado SQL Server. Esto implica crear un balanceador de carga o un grupo de escalado, lo que puede implicar un cambio de precio. Además, en Azure podemos aprovechar las bases de datos elásticas, que son un tipo de servicio que permite escalar horizontalmente una base de datos SQL sin tener que gestionar los servidores ni la distribución de los datos. Las bases de datos elásticas se componen de un grupo de bases de datos que comparten recursos y se balancean automáticamente según la demanda. d.

Conclusión

El escalado es una técnica fundamental para optimizar el rendimiento y la disponibilidad de SQL Server, tanto en la versión On Premise como en la nube de Azure. Dependiendo de las necesidades y los recursos disponibles, podemos optar por un escalado vertical o horizontal, cada uno con sus ventajas y desventajas. Tienes que tener en cuenta que estos modos no son excluyentes, nuestra aplicación puede hacer uso de una base de datos de Azure por cliente, escalando horizontalmente con cada nuevo cliente y, a la vez, escalar verticalmente una base de datos cuando el volumen de datos o transacciones de un cliente lo requiera. Lo importante es elegir la opción más adecuada para nuestro sistema y realizar las pruebas necesarias para verificar la mejora.

Espero que este artículo te haya sido útil y que te ayude a optimizar el rendimiento de tus consultas en SQL Server. Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 2 comentarios

Particionado Horizontal en SQL Server: Guía completa

En estos últimos días hemos hablado del particionado de tablas como una de las medidas más eficaces para reducir la lectura de páginas de disco y reducir así el consumo de E/S, RAM y CPU. También vimos casos prácticos del particionado vertical de tablas y medimos su impacto en nuestras consultas. Hoy le toca el turno al particionado horizontal. El particionado horizontal consiste en dividir una tabla grande en varias partes más pequeñas, llamadas particiones, que se almacenan en diferentes grupos de archivos. De esta forma, podemos mejorar el rendimiento, la disponibilidad y la administración de los datos.

Elegir una buena columna de particionado horizontal

Lo primero que tenemos que decidir es por qué columna vamos a particionar. Esta será una columna por la que siempre se filtre y que no tenga o tenga muy poca variación. Lo más común es que sean columnas de fecha pero no es ninguna norma. Podremos crear particiones por cualquier columna que no sea de tipo timestamp o datos LOB (ntext, text, image, xml, varchar(max), nvarchar(max) y varbinary(max)). Lo que sí podemos usar son columnas calculadas siempre y cuando se creen con la condición PERSISTED. Esto nos es especialmente útil cuando queremos particionar por una concatenación de varias columnas.

Estas reglas se aplican tanto para particionar tablas como índices. Normalmente la partición de los índices Nonclustered será la misma que la de la tabla subyacente. Esto se llama índice alineado y permite al motor de base de datos cambiar las particiones de la tabla de forma rápida y eficaz al mismo tiempo que mantiene la estructura de la partición tanto en la tabla como en sus índices. 

Elegir una buena función de particionado horizontal

La función de partición es clave para el éxito del particionado horizontal. Lo que hace este objeto es especificar cómo se van a repartir los datos en función de las particiones que hemos creado. Una buena función de partición debe cumplir dos requisitos:

  • Debe distribuir los datos de forma equilibrada entre las particiones, evitando que haya particiones muy grandes o muy pequeñas.
  • Debe facilitar el acceso a los datos según el patrón de uso, evitando que haya que consultar varias particiones para obtener la información deseada.

Para elegir una buena función de partición, debemos analizar las características de los datos y las consultas que se realizan sobre ellos. Algunos factores a tener en cuenta son:

  1. El tipo y el rango de valores de la columna de partición.
  2. La frecuencia y el volumen de inserción, modificación y eliminación de los datos.
  3. La frecuencia y el tipo de consultas que se realizan sobre la tabla.
  4. El nivel de detalle o agregación que se requiere en las consultas.

Aunque en otros motores de bases de datos existen dos tipos de funciones de partición: por rango y por hash. En SQL Server solo vamos a poder particionar por rango. Las funciones por rango asignan los datos a las particiones según un intervalo de valores definido para cada una. Las funciones por hash asignan los datos a las particiones según un algoritmo matemático que calcula un valor numérico para cada fila. Cada tipo tiene sus ventajas e inconvenientes, dependiendo del caso de uso.

Diseñar un buen esquema de particionado horizontal

El esquema de particionado define en qué grupo de archivos se almacena cada partición. Un grupo de archivos es una colección lógica de archivos físicos que contienen los datos y los índices de una base de datos. SQL Server permite crear varios grupos de archivos para una misma base de datos, y asignar cada uno a una unidad diferente. Un buen esquema de particionado debe aprovechar esta característica para mejorar el rendimiento y la disponibilidad de los datos. Algunas recomendaciones son:

  • Crear un grupo de archivos por cada partición, y asignar cada grupo a una unidad diferente. De esta forma, se evita la contención de recursos y se aumenta el paralelismo en las operaciones de lectura y escritura. Esto no siempre podremos hacerlo, por ejemplo el particionado es una característica compatible con Azure Databases pero no los distintos grupos de archivos por lo que todos los archivos pertenecerán al mismo Filegroup.
  • Crear un grupo de archivos adicional, llamado grupo de archivos primario, que contenga los metadatos de la base de datos y las tablas no particionadas. Este grupo debe estar en una unidad diferente a los demás grupos de archivos.
  • Crear un grupo de archivos vacío, llamado grupo de archivos de reserva, que se pueda usar para añadir o mover particiones en caso de necesidad. Este grupo debe estar en una unidad con suficiente espacio libre.

Prueba de concepto

Para esta prueba vamos a usar la tabla [Sales].[SalesOrderHeaderSalesReason] de la base de datos AdventureWorks, esta tabla tiene aproximadamente 31.000 registros que van del año 2011 al 2014. Creando un particionado por la columna OrderDate para tener particiones por año y haciendo una consulta simple a la tabla podemos observar como la reducción en el número de páginas leídas es apreciable.

Os dejo el script que he usado para particionar esta tabla:

Conclusión

El particionado horizontal de tablas en SQL Server es una técnica muy poderosa para optimizar el manejo de grandes volúmenes de datos. Sin embargo, requiere un análisis previo y un diseño cuidadoso para obtener los mejores resultados. 

Espero que este artículo te haya sido útil y que te ayude a optimizar el rendimiento de tus consultas en SQL Server. Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn al que te puedes unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 0 comentarios

Particionado Vertical de Tablas (Casos Prácticos)

Hoy tenemos un artículo completamente práctico poniendo en práctica y comparando las distintas soluciones de particionado vertical de tablas en SQL Server que vimos ayer en la teoría. ¿Será la mejora de rendimiento tal como la pintan? ¿Merece la pena el esfuerzo? Espero que al concluir la lectura de este artículo puedas sacar tus propias conclusiones y estés capacitado para hacer tus propias pruebas en tu entorno de test para decidir si te interesa o no.

Particionado vertical con vistas

Vamos a empezar con el escalado vertical con vistas indexadas. Para ello tenemos una tabla Personas que es una copia de la tabla Person.Person de AdventureWorks a la que le he añadido una columna varchar(max). He cargado la tabla con 15 veces los registros de la tabla Persons y para la columna varchar(max) he replicado un texto relativamente largo 100 veces por cada registro.

Esta es una lectura sobre la tabla:  

Para nuestra prueba de lectura vamos a renunciar a la lectura del campo Description que es el varchar(max), sin embargo el número de páginas de disco leídas sigue siendo el mismo. Probemos a crear una vista indexada sin ese campo y otra solo con ese campo y el ID.

Y ahora leamos la  primera vista:

Conclusiones:

Como veis, hemos conseguido reducir el número de lecturas para la misma consulta de 295.539 páginas a 57.716, parece que el resultado es bueno. Pero no solo eso, cuando hablamos de las vistas indexadas, comentamos que en las versiones Enterprise de SQL Server, el motor de base de datos era capaz de usar el índice de la vista aunque lo que estamos consultando fuera la tabla así que el resultado para una lectura sobre la tabla será el mismo si tenemos esta edición de SQL Server.

Particionado vertical con tablas

Para este ejemplo vamos a crear una tabla que contenga el ID y Description de la tabla Personas. Luego borraremos Description de la tabla personas y veremos el resultado:

Hagamos ahora una lectura de la tabla personas:

¿Qué ha pasado? El número de páginas no se ha reducido. Esto tiene una explicación, el texto de la columna Description no era tan grande como para estar en páginas LOB por lo que al borrarlos simplemente hemos dejado libre el espacio en las páginas de datos pero los datos que permanecen siguen distribuidos de la misma manera. Tenemos una fragmentación muy alta de los datos que solucionaremos con un mantenimiento del índice clustered.

Y ahora sí una lectura nos mostrará una cantidad muy inferior de páginas leídas:

Conclusiones

En esta ocasión el resultado es incluso mejor que en la opción anterior de particionado vertical con vistas. Esto se debe a que los campos XML de la tabla personas no podemos añadirlos al índice de la vista indexada por lo que SQL tenía que hacer un Key Lookup sobre las páginas de datos de la tabla para recuperar esa información. Además, los datos no están almacenados por duplicado (en el índice clustered de la tabla y en el de la vista). Tiene el inconveniente de que tendremos que modificar el código de las consultas de la aplicación al haber cambiado el modelo de datos pero, si podemos afrontar eso, a cambio ganamos en menor consumo de recursos.

Conclusión final

El particionado vertical es una gran solución para paliar problemas de cuello de botella de E/S de disco así como de RAM y CPU. La opción de particionar con vistas es sencilla de implementar si no tenemos problemas de capacidad de almacenamiento y obtendremos resultados de una manera transparente para los usuarios de la base de datos si tenemos una edición Enterprise. El particionado vertical con tablas es más completo y nos da un mejor rendimiento en caso de tipos de datos que no se admiten en índices pero, por contra, requiere una modificación en el código de nuestras consultas. Nos ha quedado por ver el particionamiento horizontal del que también hablamos en el pasado artículo. Este tipo de particionamiento tiene más cosas que valorar y requiere de un artículo específico. Permanece atento al blog que el próximo día hablaremos de particionado horizontal.

Espero que este artículo te haya sido útil y que te ayude a optimizar el rendimiento de tus consultas en SQL Server. Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn al que te puedes unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 0 comentarios

Particionado de tablas en SQL Server (Introducción)

En este artículo vamos a hablar de un tema muy interesante y útil para los DBAs: el particionado de tablas en SQL Server. El particionado de tablas es una técnica que nos permite dividir una tabla grande en varias partes más pequeñas, llamadas particiones, que se almacenan en diferentes grupos de archivos. De esta forma, podemos mejorar el rendimiento de las consultas, las operaciones de mantenimiento y la administración de los datos.

¿Qué es el particionado de tablas?

El particionado de tablas en SQL Server es una técnica a la que podemos recurrir para dividir nuestras tablas grandes en particiones más pequeñas. Esto normalmente está asociado a un menor coste de recursos al tener que recuperar menos información, además nos abre un poco más el abanico de opciones a la hora de administrar pudiendo jugar con distintos grupos de archivos separados en discos, creando copias de sólo algunos grupos de archivos y haciendo planes de mantenimiento específicos para las particiones.

Tipos de particionado

Podemos encontrarnos con particionados de tipo horizontal y vertical en función de si separamos la tabla por filas o columnas respectivamente. 

Particionado vertical 

Este tipo de particionamiento consiste en dividir una tabla en varias particiones según las columnas que se quieren incluir o excluir. Cada partición contiene solo algunas columnas de la tabla original, pero todas las filas. El particionado vertical se suele usar para separar las columnas que se consultan con frecuencia de las que no, o para almacenar las columnas con tipos de datos especiales (como imágenes, documentos o textos grandes) en grupos de archivos diferentes. Dentro de este tipo de particionamiento podemos encontrar varias soluciones, desde dos tablas independientes con un identificador común para enlazar las columnas hasta una misma tabla con varias vistas indexadas sobre ella cada una con distintas columnas. 

Particionado horizontal

Este tipo de particionamiento es en el que todo el mundo piensa cuando se habla simplemente de particionamiento, sin especificar nada más. Consiste en partir una tabla en varias particiones con exactamente las mismas columnas y repartir las filas entre las distintas particiones en función del valor de una de las columnas. Así por ejemplo una tabla con datos de un log podría estar dividida en particiones por año. 

Ventajas de particionar las tablas

Como ya hemos comentado, si nos metemos en este lio del particionamiento no es por gusto, es porque tiene muchas ventajas frente a las tablas “normales”. Entre estas ventajas podemos destacar:

  • Reduce el tiempo de respuesta de las consultas, ya que podemos acceder solo a la partición o particiones que contienen los datos que necesitamos, en lugar de tener que escanear toda la tabla. Esto se conoce como eliminación de particiones o partition pruning.
  • Facilita el mantenimiento de los datos, ya que podemos realizar operaciones como reconstruir índices, actualizar estadísticas o realizar copias de seguridad por partición, en lugar de tener que hacerlo sobre toda la tabla. Esto reduce el tiempo y los recursos necesarios para estas tareas.
  • Mejora la disponibilidad de los datos, ya que podemos aislar las particiones que contienen datos críticos o de uso frecuente en grupos de archivos dedicados, con un nivel de redundancia y recuperación más alto. También podemos mover o restaurar particiones individuales sin afectar al resto de la tabla.
  • Permite una gestión más eficiente del espacio en disco, ya que podemos asignar diferentes niveles de compresión a cada partición, según la frecuencia y el tipo de acceso a los datos. También podemos liberar espacio eliminando o archivando particiones antiguas o poco usadas.

Implementando el particionamiento de tablas 

Los pasos para implementar el particionado de tablas variarán en función del tipo de particionado que queramos usar.

Implementar el particionado horizontal 

  1. Crear una función de partición, que define cómo se van a distribuir los datos entre las distintas particiones, según el valor de una columna o expresión. Por ejemplo, podemos crear una función de partición por año, mes o día, según la fecha de registro de los datos.
  2. Crear un esquema de partición, que asocia la función de partición con uno o varios grupos de archivos, donde se van a almacenar las particiones. Podemos especificar un grupo de archivos diferente para cada partición, o usar el mismo para todas.
  3. Crear o modificar la tabla que queremos particionar, indicando el esquema de partición y la columna o expresión que va a determinar el valor de la partición. Podemos crear una tabla nueva ya particionada, o modificar una tabla existente para aplicarle el particionado.
  4. Crear o modificar los índices sobre la tabla particionada, indicando también el esquema de partición y la columna o expresión que va a determinar el valor de la partición. Podemos crear índices alineados con la tabla, es decir, que siguen el mismo esquema y función de partición, o índices no alineados, que tienen su propio esquema y función de partición.

Implementar el particionado vertical 

Como hemos comentado antes para el particionado vertical podríamos optar por crear varias tablas con el mismo ID y distintas columnas en cada una de ellas. Otra opción será sobre una misma tabla con todas las columnas crear vistas indexadas, para ello los pasos serán:

  1. Crear vistas sobre la tabla original, seleccionando solo las columnas que queremos incluir en cada partición. Por ejemplo, podemos crear una vista con las columnas básicas y otra con las columnas avanzadas.
  2. Crear un índice agrupado sobre cada vista, indicando el grupo de archivos donde se va a almacenar la partición. De esta forma, se crea una copia física de los datos de la vista en el grupo de archivos especificado, y se mantiene sincronizada con la tabla original.
  3. Opcionalmente podremos crear índices no agrupados sobre cada vista, indicando también el grupo de archivos donde se va a almacenar la partición. De esta forma, se crea un índice que facilita el acceso a los datos de la vista, y se mantiene sincronizado con la tabla original.

Planificando el particionado de tablas

Ya hemos visto que el particionado de tablas es una herramienta muy poderosa. Sin embargo no hay que tomárselo a la ligera, requiere una planificación y un diseño adecuados para obtener los mejores resultados. Algunas consideraciones importantes son:

  • Elegir bien la columna y expresión que va a determinar el valor de la partición, ya que debe ser relevante para las consultas y operaciones más habituales sobre la tabla. Por ejemplo, si queremos consultar los datos por fecha, tiene sentido usar una columna que contenga la fecha como criterio de particionado.
  • Elegir bien el número y el tamaño de las particiones, ya que deben ser equilibradas y coherentes con el volumen y la distribución de los datos. Por ejemplo, si tenemos una tabla con muchos años de datos históricos, pero solo consultamos los datos del último año, no tiene sentido crear una partición por cada año, sino quizás una por cada mes o trimestre.
  • Elegir bien los grupos de archivos donde se van a almacenar las particiones, ya que deben tener suficiente espacio y capacidad para soportar la carga de trabajo. Por ejemplo, si tenemos una partición con datos muy consultados o críticos, podemos ubicarla en un grupo de archivos con discos más rápidos o con mayor redundancia.
  • Monitorizar y ajustar el particionado de tablas periódicamente, ya que los datos y las necesidades pueden cambiar con el tiempo. Por ejemplo, podemos usar herramientas como el asistente para el particionado de tablas o query store para analizar el rendimiento y la eficacia del particionado, y realizar cambios como agregar, eliminar, fusionar o dividir particiones, según sea necesario.

Conclusión

El particionado de tablas es una técnica muy útil para mejorar el rendimiento, el mantenimiento y la administración de las bases de datos en SQL Server. Sin embargo, requiere un diseño cuidadoso y un seguimiento constante para obtener los mejores resultados. En este artículo hemos visto los conceptos básicos del particionado horizontal y vertical de tablas, cómo se implementan en SQL Server y qué consideraciones hay que tener en cuenta al usarlos. Si quieres profundizar más en este tema, te recomendamos que leas nuestro anterior artículo sobre cuellos de botella de E/S en SQL Server, donde explicamos cómo el particionado de tablas puede ayudar a resolver este problema común.

Esperamos que te haya gustado este artículo y que te haya servido para aprender algo nuevo. Si tienes alguna duda o comentario, no dudes en dejarnos un mensaje en Twitter, por mail o dejarnos en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn al que te puedes unir. 

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 1 comentario