Surviving the Night

El blog de Pablo Doval sobre .NET, SQL, WinDbg...
Why Do I?: Memoria para los Workspaces en SQL Server (I)

Os voy a confesar un oscuro secreto: tengo un plan. Y no solo eso, sino que es un plan de los buenos. De los de Dominación Mundial™. Curiosamente, y como no podía se de otro modo, ese plan pasa por hacerme bueno, muy bueno en SQL Server1, así que últimamente estoy aprovechando este descanso navideño para estudiar en profundidad algunos temas en los que nunca entre en suficiente detalle, o que tengo olvidados.

Durante las sesiones de estudio me gusta tomar notas de lo que voy repasando, así como preparar unas slides personales sobre el tema, que voy almacenando para futuras referencias y para dar charlas de esas que me gusta hacer de vez en cuando para satisfacer mi lado mas nerd. En esta ocasión, sin embargo, he pensado utilizar parte de esas notas para darle un poco mas de vidilla al blog y aprovechar para escribir de algunas partes de SQL Server de las que no hay demasiada literatura en nuestro idioma.

En esta ocasión, voy a hablaros de una zona de memoria especial de SQL Server, llamada Workspace y que me niego a traducir al castellano :) Mi objetivo para esta mini serie será explicaros algunos fenómenos perniciosos desde el punto de vista del rendimiento en SQL Server (como son Hash Warnings, Sort Warnings, etc…) y su relación con esta misteriosa y desconocida área.

NOTA: Tras muchos intentos en el pasado, soy plenamente consciente de que esta pequeña reactivación de mi blog va a tener un carácter muy temporal; después vendrá el día a día y se llevará todas mis buenas intenciones. Así que, tomaros esto como una pequeña aportación navideña, un regalo de reyes anticipado :P

La Memoria en SQL Server

Esta claro que antes de zambullirnos en la manera en la que SQL Server gestiona la memoria workspace, es importante conocer el funcionamiento básico de la memoria en el producto. Para esta entrada tengo que hacer la suposición de que ya estáis familiarizados con la gestión de memoria básica de SQL Server. Algunas de las cosas básicas que debes de saber antes de continuar leyendo es que…

  • La memoria de SQL Server se divide en dos zonas principalmente:
    • Buffer Pool: Es la caché principal de datos de SQL Server. Aquí se cachean, en páginas de 8Kb, datos, índices, planes de ejecución… Todas las reservas de 8Kb van aquí, y es sin duda la zona mas importante de memoria del producto.
    • Non-Buffer Pool: También llamada mem-to-leave, es la zona de memoria en al que se realizan las reservas superiores a 8Kb.
  • El parámetro de configuración max server memory (MB) limita sólo Buffer Pool
  • El tamaño de la región Non-Buffer Pool viene determinado por la siguiente formula: Memoria Física Total – (Max Server Memory + Memoria Física usada por el OS y otras aplicaciones)

Si no es así, os recomiendo que busquéis alguna introducción básica al tema y luego continuéis leyendo por aquí :)

NOTA: Aunque estas cosas son bastante genéricas, en realidad solo son correctas para un SQL Server 2008 R2 con arquitectura x64.

Este maravilloso diagrama “Windows 8 Modern UI”  que os he preparado, sin bordes, ni colorido, ni ná de ná, muestra la relación entre los componentes de asignación de memoria en SQL Server 2008 R2:

image

Un rápido resumen podría ser el siguiente:

  • Las reservas de memoria de 8Kb o menores se almacenan en el Buffer Pool (mediante en single-page allocator)
  • Las reservas de memoria mayores de 8Kb se dirigen al área mal llamada Mem-to-leave a través del multi-page allocator.
  • Las reservas de memoria del código de CLR que se ejecuta dentro del proceso de SQL Server se dirigen también al mem-to-leave mediante el VAS allocator.

En SQL Server 2012 se han producido bastantes cambios a la gestión de memoria: sin duda, el más importante es que ya no se realiza una diferencia entre el single-page allocator y el multi-page allocator, habiendo sido sustituidos ambos por un único y genérico gestor de reservas de memoria, como se puede ver en el siguiente diagrama:

image

A partir de ahora, tanto las reservas tradicionales menores de 8Kb, como las reservas superiores a 8Kb y las reservas del CLR pasan a estar en los limites de memoria definidos entre el max server memory (MB) y el min server memory (MB).

NOTA: Si habéis hecho una actualización a SQL Server 2012 sin tener en cuenta este cambio, es probable que queráis repasar vuestra configuración de memoria después de leer esto. Tened en cuenta también que que muchas DMVs han cambiado, por lo que scripts de captura y monitorización pueden estar devolviéndoos resultados no fiables.

Una vez descritas las dos grandes zonas de memoria de SQL Server, estoy seguro de que muchos de vosotros ya estáis poniendo en su lugar, dentro de la zona controlada por el Memory Manager (la zona verde), las varias caches de datos que conocéis en SQL Server: para los datos recientemente usados, para los planes de ejecución, etc. Sin embargo, SQL Server tiene mas usos para la memoria principal, y uno de ellos es para la…. ¡¡Ejecución de Consultas!!

<sonido de truenos y lluvia distante, por favor….. …… ¡gracias!>

Ok… ok… se que era predecible :) Pero es un paso importante para llegar a donde os quiero llevar. Vamos a hablar de unas pequeñas estructuras muy amigables que viven de de nuestros planes de ejecución.

Los Iteradores

En realidad, los iteradores (u operadores, pero como en realidad todos son iteradores, prefiero llamarlos asi) son las piezas de lego con las que se construyen nuestros planes de ejecución. Hay aproximadamente un centenar de ellos en SQL Server 2012, y nos sirven para realizar acciones tan variadas como leer registros, realizar filtrados y agregados, actualizar datos, etc… Estos se combinan y son los que finalmente dan lugar a nuestros planes de ejecución.

A nivel interno, todos exponen los mismos tres métodos:

  • Init(): Instancia el operador físico e inicializa todos los recursos que pueda necesitar. Idealmente solo se ejecutará una vez, pero puede ser necesario llamarle múltiples veces: esto suele indicar algún problema y se materializa con un número alto de binds/rebinds en el plan de ejecución.

  • GetNext(): La llamada a este método devuelve la primera fila, o la fila siguiente a la actual. El operador puede recibir cualquier número de llamadas a este método. ¿Entendéis ahora porque llamamos iteradores a todos los operadores? ;)

  • Close(): Este método provoca la ejecución de las tareas de limpieza y eliminación de recursos del operador. Solo puede ser invocado una vez.

Aunque a primera vista parece extraño que *todos* los operadores se implementen básicamente con un GetNext(), esta aproximación hace que sea relativamente fácil implementar nuevos operadores para el Query Optimizer/Query Processor.

NOTA: Aquí tenéis la referencia de los disponibles en SQL Server 2012: http://msdn.microsoft.com/es-es/library/ms191158.aspx

Sigamos: todos los iteradores (Table Scan, index Scan, Compute Scalar, etc…) en un plan de ejecución necesitan algo de memoria para levantar su infraestructura básica, para mantener el estado del iterador y para almacenar las filas que están siendo procesadas. Este último punto es el más interesante, veamos por qué.

Los iteradores pueden ser de dos tipos:

  • La mayoría de los iteradores que nos vamos a encontrar en un plan de ejecución son de tipo streaming; esto es, pedirán una fila, la procesarán y pedirán la siguiente. Evidentemente, en estos escenarios el consumo de memoria del iterador para almacenar las filas que están siendo procesadas será escaso y predecible: siempre se almacena una única fila.
  • Hay unos pocos operadores que son de tipo parcialmente o completamente bloqueantes (blocking). Estos deben de mantener un subconjunto (o la totalidad) de las filas en memoria hasta que finaliza su proceso, en cuyo momento el GetNext() devuelve un bloque de n filas, en lugar de una única filas.

Evidentemente los iteradores parcialmente o completamente bloqueantes consumen mucha más memoria. Un ejemplo básico es el operador sort, que es complentamente bloqueante ya que requiere que todas las filas se almacenen hasta la llegada de la última fila de su operador de entrada, antes de empezar a producir resultados por su salida. Esto es así, porque la última fila en entrar podría ordenarse la primera; es un ejemplo muy claro.

La buena noticia es que son muy pocos los operadores bloqueantes, y se pueden categorizar en estos tres tipos:

  • Hash Match:
    • Hash Match
    • Hash Match (Aggregate)
  • Sort:
    • Sort
    • Sort (Top N Sort)
  • Exchange:
    • Distribute Streams
    • Gather Streams
    • Repartition Streams

Cuando una consulta utiliza este tipo de operadores, tiene que realizar peticiones de memoria especiales a SQL Server para poder realizar las tareas que estos iteradores requieren: estas peticiones de memoria son las que definen el workspace de la consulta (workspace memory grants).

Algunas Preguntas

Lo primero que nos podemos preguntar es, ¿de dónde sacamos esa memoria? Esta pregunta es la fácil; estoy convencido de que todos ya sabéis que viene del Buffer Pool… entre otras cosas porque ya lo hemos comentado antes ;)

La siguiente pregunta es: ¿cuanta memoria se va a reservar de nuestro Buffer Pool para la ejecución de la consulta? De esto se encargará nuestro amigo, el Query Optimizer, que tratará de averiguar el Ideal Grant, es decir, la reserva de memoria total para el plan de ejecución que permitirá que todos los operadores puedan ejecutarse completamente en memoria. Para realizar este calculo del Ideal Grant se utilizarán las estadísticas (histograma de distribución de los valores de cada columna), en concreto el número de filas, y el tamaño por cada fila.

Es interesante aquí darse cuenta de que estamos trabajando con estimaciones. A modo de ejemplo, supongamos un escenario de una consulta simple con un ORDER BY: el Query Optimizer sabe que ese operador necesitara “estimated rows * estimated row size” bytes. ¿porque decimos que son estimados? Bien, imaginaros que el tipo de datos es NVARCHAR(100)… realmente no sabemos lo que ocupa esa columna en cada fila, solo tenemos una estimación.

Para ver la información de la memoria asignada al workspace de una consulta desde el SSMS (y sin tener que recurrir a leer el XML a mano), podemos simplemente mirar las propiedades del operador principal (el SELECT en el ejemplo), y veremos un nodo llamada MemoryGrantInfo:

image

SQL Server impone un limite máximo a la memoria destinada a los workspaces (sobre el 75% del Buffer Pool), para evitar que una consulta extremadamente pesada impida la correcta ejecución de consultas mucho mas livianas y rápidas en el servidor.

Esto puede parecer mucha memoria, pero imaginaros que tenéis un Data Warehouse con una tabla de hechos que os pesa 500 Gb, y queréis realizar algo ‘tan simple’ como un ‘SELECT * FROM FactTable ORDER BY Date’, ¿Podría vuestro servidor mantener todo el operador en memoria? ¿que sucedería?

Las respuestas, ¡en la próxima entrada! ;)

Resumiendo

Buff.. ¡no ha estado nada mal! En una única entrada hemos hablado de la gestión de memoria en SQL Server, de qué son y como funcionan los iteradores y hemos introducido el concepto de iteradores bloqueantes y sus workspaces asociados. Nos queda mucho por ver aún; de hecho, aún no os he presentado ni siquiera los problemas que queremos resolver, pero no he podido evitar la tentación de aprovechar esta mini serie para ir introduciendo algunos elementos de SQL Server de los que no solemos hablar.

Por cierto, si has llegado hasta aquí, ¡enhorabuena! Definitivamente debes de tener interés en SQL Server o mucho tiempo libre, ya que admito que no es el tema más ameno y ligero para una lectura en temporada navideña.

1: Lo siento, pero no puedo explicaros la razón por la que un plan de Dominación Mundial™ requiere un conocimiento extremo las tripas de SQL Server. Bueno… en realidad si que podría explicarlo, pero tendría que mataros después. Y la verdad es que ya os he cogido cariño y esas cosas, así qué mejor no preguntéis :)
Rock Tip:

Si en la última entrada os ponía un temazo de W.E.T., en esta ocasión no me voy a ir muy lejos: ‘Why do I?’ es el tema que abre Artwork, el disco de debut de los suecos Work of Art. I digo que no me voy muy lejos, ya que el guitarrista, principal compositor y lider de esta banda no es otro que Robert Säll, uno de los lideres de W.E.T. de quien ya os hablaba anteriormente.

¿Que deciros de esta banda? Una de las mayores y más agradables sorpresas de mi vida musical: hablamos de un hard rock melódico de mucha, mucha clase, estilo y elegancia. Si duda una de mis bandas favoritas del momento, aunque he de reconocer que les tengo un cariño especial y poco objetivo. Decidieron hacer el primer concierto de su historia en Madrid, en la venerable sala Ritmo & Compás, y os puedo decir que todos los que tuvimos la suerte de vivir ese momento lo vamos a guardar como uno de los conciertos de nuestras vidas.

Keep Rockin’!

Brothers in Arms: We are Hiring!

Today I’m bringing you great news! We are looking for a talented BI Developer to join our team at Plain Concepts and get involved in some amazing projects we are enganged in right now.

Sounds good to you? Great! so take a look at the details below, and if you think you are a good match, the let us know! You can reach me directly, or send an email to info@plainconcepts.com.

The Role

Be a part of an agile development team which creates Business Intelligence projects for our customers all around the world. You will also have the chance of being a trainer and mentor on the amazing new tools and technologies in this field, and to help our customers at their premises occasionally.

We are a really multidisciplinary team here at Plain Concepts. We can guarantee you that you won’t be stranded at a customer site without knowing who the rest of your colleagues are. The rest is up to you, your aptitudes and preferences.

What skill set do we specially value?

The SQL/BI Team at Plain Concepts is currently engaged in some pretty exciting projects leveraging the whole SQL Server stack, as well as other Big Data/Hadoop technologies. The current members of the team have to deal with different tasks, ranging from Data Warehouse modeling, ETL processes, OLAP development both in Multidimensional and Tabular fashion, Reporting systems, Map/Reduces programming and general performance optimization (both at DW and OLAP environments). We expect you to be able to handle most of these tasks, and to be proficient in at least one of them.

A working knowledge of C# and .NET Framework would be highly desired, since some of these tasks – such SSIS scripting and Map/Reduce programming - will involve programming to a certain extent.

We don’t expect you just to perform a specific set of tasks assigned: being a part of this small team means you will be taking an active part in all design decision in our projects. You should also be able to help solve our customers’ problems; although it won’t be your main task, you might need to help in consulting projects with your preferred technologies, so basic soft skills are also required.

Since some of the projects in which the team is currently engaged are for international customers, a good English level is highly desirable.

All Microsoft certifications are welcome, but not indispensable.

Which aptitudes do we value the most?

You must be a team player, constructively criticizing your colleagues’ work while being open to receiving critics about yours.

You must have a constant interest for learning, and doing so quickly!

You must be passionate about all software development activities.

You must be a good communicator. Having a blog is a plus.

Collaborating with the .NET community is preferred: Being part of a user club, speaking at events about .NET, writing on MSDN forums and helping others…

How will the selection process be?

Hard. You’ll be interviewed by everyone from the team you’d work at. They all can decide on your selection.

You should be prepared to discuss with us some of the projects you have worked in, your favorite tips and tricks, features you are especially passionate about, etc.

Also, you can surprise us with any project that you might have developed in your free time for your “geek” pleasure.

Will I have to travel?

As a BI Developer you shouldn’t be packing your luggage every other day, but traveling may be involved to attend occasional mentoring or consulting sessions at customer’s sites, etc.

How much money will I make?

As much as you can justify for, our limits are blurry on either side. This, whenever reasonable, won’t be a problem. If you are a real master, we’ll value it. We want the best in our team.

And salary aside?

You’ll be able to get training, attending Microsoft events, assist to courses…

How will my schedule be?

Whatever works for you: most of us work from eight-ish through six-ish. We have lunch at around 1:30pm and we go home at 3:00pm on Friday. Nobody will ask you to be attached to your chair for hours; we want results, meeting commitments, and satisfying our customers.

 
Rock Tip:

By joining this team, you will be joining a ‘band of brothers’ working together on these amazing projects, so I thought this time it was really appropiate for me to share with you this incredible song by WET.

WET is the side project of extremely talented musicians in the Hard Rock/AOR scene; we can find Robert Sall (from “Work of Art”), Erik Martensson (from “Eclipse”) and Jeff Scott Sotto (from “Talisman” and many others) working together in what has to be one of the best melodic hard rock albums ever written. Highly

NOTE: By the way, your musical tastes will not count during the selection process… In fact, I’m the only one in the team with some decent musical taste :)

Keep Rockin’!

Love Bites: Generación Programática de Volcados de Memoria

Llevo mucho tiempo deseando escribir esta entrada, pero por una razón u otra nunca conseguí hacer un hueco para prepararla como es debido. Estos días he vuelto a abrir el Live Writer y me he propuesto no dejar pasar la ocasión. Además, estos últimos meses parece que solo vivo de SQL y de BI, así que estará bien por un rato volver a recordar los viejos tiempos con WinDbg ;)

Así que a ver si consigo que este pequeño artículo le haga justicia a lo interesante que es la técnica que os quiero enseñar hoy. ¡Al turrón!

Depuración Post-Mortem

No puedo evitarlo, ¡me encanta este término! Creo que cada vez que lo empleo se me concede automáticamente un +10 en geek skills :) Tonterías aparte, creo que la depuración post-mortem nos proporciona uno de los escenarios más interesantes desde el punto de vista de soportabilidad de nuestras aplicaciones, tanto desde un enfoque técnico como económico.

Ya he hablado de este tema aquí en varias ocasiones, pero como Rodri no me cobra por palabras en el blog (aún!), voy a volver refrescar el concepto de la mejor manera que se me ocurre: con un escenario de la vida real™.

Supongamos que tenemos una aplicación desplegada en un cliente que se encuentra en la Base Amundsen-Scott. Imaginemos ahora que la aplicación revienta en el peor momento y requiere un fix urgente. Si, ya lo se, vuestras aplicaciones no revientan jamás y están testadas para todos los entornos conocidos y por conocer, pero haced el ejercicio de imaginación conmigo ;) Vamos a seguir fantaseando: el cliente os notifica el problema, pero nosotros no somos capaces de reproducirlo en nuestro entorno de ninguna de las maneras. Perdemos un tiempo precioso para el cliente intentando obtener una repro del problema y finalmente no nos queda otra que coger un avión, un barco y un trineo tirado por una docena de Alaskan Malamute(1) y llegar, posiblemente junto a una copia de nuestro código cuente o símbolos de depuración, al lugar donde se encuentra el cliente. Y ya conocéis las reglas de Murphy: el día que llegamos, ¡¡seguro que el problema no se reproduce!!

Y si en lugar de ir nosotros al lugar donde se produjo el problema, fuéramos capaces de hacer llegar el equipo con el fallo a nuestras instalaciones, justo en el mismo instante que se ha producido, y con un depurador ya adjuntado. ¿No sería genial? Pues en realidad podemos conseguir algo aún mejor: obtener esa información sin tener que enviar el equipo físicamente :) Se trata de capturar un volcado de memoria (memory dump, o dump para los amigos), consiste en una copia (total o parcial) de los contenidos del espacio de memoria virtual del proceso.

Este fichero con el volcado de memoria podemos abrirlo con un depurador (como mi queridísimo WinDbg) y obtenemos una imagen del proceso en el momento exacto que reventó: podremos ver la pila de llamadas de todos los hilos, examinar las excepciones lanzadas, valores de las variables, etc.

Pensemos ahora en otro tipo de escenario: software que pueda ejecutarse en muchos equipos, y muy diferentes cada uno de ellos, como por ejemplo los videojuegos. En esta industria se emplean mucho la generación automática de memoria, porque el numero de usuarios y la gran dependencia sobre el hardware especifico hace imposible testar todos los entornos hardware y software en los que el producto se va a ejecutar.

OPINION PERSONAL: Esto nos podría llevar al debate de si es ético que un producto salga al mercado con fallos o incompatibilidades, y tengan que ser los propios usuarios de pago los que hagan de ‘conejillos de indias’… pero voy a tratar de evitar entrar en este espinoso tema… al menos por hoy ;)

Otros escenarios que nos pueden interesar de la generación de volcados de memoria automatizados pueden ser los siguientes:

  • Clasificador de Incidencias en la Aplicación:

¿Os suena de algo la ventanita de Windows que nos pregunta si queremos enviar un informe de errores a Microsoft cuando una aplicación revienta? Pues cuando envías el informe, en realidad se captura el volcado de memoria y un hash de este y se almacena en unos servidores de bases de datos de Microsoft. Un proceso clasifica todos los incidentes que tienen call stack similar, clasificándolo como potencialmente el mismo problema, e incrementando un contador.

De este modo, la gente de los equipos de desarrollo de hotfixes saben donde tienen que centrar sus esfuerzos, atacando primero la resolución de los problemas que impactan a un mayor número de usuarios.

Por tanto, mi recomendación es que siempre enviéis los informes de errores, ya que eso hará que vuestros problemas puedan ser atendidos más rápidamente. Y no, no os va a delatar si tenéis un Windows poco legal, ni le va a decir a Microsoft que habéis estado mirando páginas de dudoso valor moral :P

  • Capturas Periódicas para Evaluar Uso de Aplicación:

El volcado de memoria no tiene porque realizarse como respuesta directa a una situación de error en la aplicación. Podemos realizar capturas periódicas para evaluar el uso de la aplicación.

<IdaDePinza Modo=”Plan de Dominación Mundial”>

Imaginaros lo divertido que sería generar mini dumps periódicos cada 10 segundos de todos los usuarios de nuestra aplicación y volcarlos a un entorno Hadoop y hacer MapReduce para conocer patrones de uso…. Muahahahaha!

</IdaDePinza >

¿Cómo generar los volcados de memoria?

En realidad se trata de algo mucho más sencillo de lo que parece, ya que la funcionalidad para generar los Mini Dumps nos la proporciona directamente DbgHelp.dll, la librería de depuración del sistema. Podemos acceder a la última versión a través de las Debugging Tools for Windows, y distribuirla junto con nuestra aplicación. Esta DLL nos proporciona una gran cantidad de funcionalidad interesante para realizar operaciones con los símbolos, búsquedas y relaciones con el código fuente, etc… pero nos vamos a centrar en este caso en una única función expuesta: MiniDumpWriteDump.

Esta función recibe ciertos parámetros (handle al proceso del que queremos obtener el volcado (en este caso será el propio proceso), el PID,  otro manejador al fichero de salida donde vamos a dejar el dump, etc.) y nos genera el dump por nosotros; como veis, todo muy sencillito!

Armados con esta información, solo nos queda importar la función en nuestro código y empezar a usarla. Aquí os dejo un código de referencía en C# para importar e invocar a la función MiniDumpWriteDump de DbgHelp.dll:

using System; 
using System.Runtime.InteropServices; 
using System.IO; 
using System.Diagnostics;

namespace PlainConcepts.DumpGenerator 
{ 
    public enum MINIDUMP_TYPE 
    { 
        MiniDumpNormal = 0x00000000, 
        MiniDumpwithDataSegs = 0x00000001, 
        MiniDumpWithFullMenory = 0x00000002, 
        MiniDumpWithHandleData = 0x00000004, 
        MiniDumpFilterMemory = 0x00000008, 
        MiniDumpScanMemory = 0x00000010, 
        MiniDumpwithUnloadedModules = 0x00000020, 
        MiniDumpwithlndirectlyReferencedMemory = 0x00000040, 
        MiniDumpFilterModulePaths = 0x00000080, 
        MiniDumpwithProcessThreadData = 0x00000100, 
        MiniDumpWithPrivateReadWriteMemory = 0x00000200, 
        MiniDumpWithoutOptionaloata = 0x00000400, 
        MiniDumpWithFullMemoryInfo = 0x00000800, 
        MiniDumpwithThreadInfo = 0x00001000, 
        MiniDumpwithCodesegs = 0x00002000 
    }

    public class DumpGenerator 
    { 
        [DllImport("dbghelp.dll")] 
        static extern bool MiniDumpWriteDump( 
        IntPtr hProcess, 
        Int32 processld, 
        IntPtr hFile, 
        MINIDUMP_TYPE dumpType, 
        IntPtr exceptionParam, 
        IntPtr userStreamParam, 
        IntPtr callbackParam);

        public static void MiniDumpToFile(string file) 
        { 
            FileStream dumpFile = null; 
            if (File.Exists(file)) 
                dumpFile = File.Open(file, FileMode.Append); 
            else 
                dumpFile = File.Create(file);

            Process currentProcess = Process.GetCurrentProcess(); 
            MiniDumpWriteDump(currentProcess.Handle, 
                              currentProcess.Id, 
                              dumpFile.SafeFileHandle.DangerousGetHandle(), 
                              MINIDUMP_TYPE.MiniDumpNormal, 
                              IntPtr.Zero, 
                              IntPtr.Zero, 
                              IntPtr.Zero); 
            dumpFile.Close(); 
        } 
    } 
} 

No es el código mas robusto, y presenta ciertos problemas al estar ejecutándose en un hilo del mismo proceso, pero para empezar a trastear creo que será mas que suficiente.

Y ahora… ¿que hacemos con ellos?

Ahora podemos analizarlos usando herramientas como WinDbg. El análisis de los mismos esta fuera del ámbito de esta entrada, pero os recomiendo que veáis las entradas introductorias a WinDbg en este blog y, sobre todo, los materiales de depuración de Tess Ferrandez, Ingeniera de Escalación de Microsoft y una autentica crack a la hora de explicar escenarios de depuración .NET con WinDbg.

Y con esto, creo que por fin he saldado mi pequeña deuda personal con este artículo. Espero que os haya sido de utilidad, o al menos os haya dado alguna idea de para vuestros propios planes personales de dominación mundial ;)

(1): Si, lo sé, la Base Amundsen-Scott tiene su propia pista (NZSP); podríamos llegar sin barco y sin trineo tirado por perros, pero seguro que me permitís esa pequeña licencia artística para darle un punto de romanticismo a nuestro épico viaje ;)

Rock Tip:

No pude evitar hacer el chiste malo, el juego de palabras que me sirve como excusa para traer en una nueva ocasión a una de las mejoras bandas del Hard Rock/AOR de las islas británicas a mi blog: una vez más, con todos ustedes, directamente desde Sheffield….Def Leppard!!!

En esta ocasión, el tema ‘Love Bites’ será el que nos acompañara. Aparecido en su espectacular LP ‘Hysteria’ en 1987, sería uno de los singles más exitosos del mismo, ¡lo que no es decir poco! Si habéis pinchado en el enlace a la canción en YouTube, habréis comprobado que no se trata de una versión cualquiera; es el directo de Sheffield de 1993, una interpretación que me pone los pelos como escarpias. Hacia solo un par de años que Steve Clark había fallecido, pero Vivian Campbell ya estaba perfectamente integrado en la banda, Rick Allen ya volvía a tocar con sets acústicos de batería y la banda estaba en el que posiblemente fuera uno de los mejores estados de forma de toda su carrera. ¡Atención al final que se curran para este increíble temazo!

Evidentemente, he decidido poner esta canción como Rock Tip™ del post porque gracias a la técnica que os explico más arriba vamos a ser capaces de extraer automáticamente esos ‘bits del amor’, ese volcado de memoria sobre el cual podremos hacer nuestro análisis post-mortem. Mmm… ¿rebuscado? Puede, pero… ¡intentad vosotros sacar un rock tip para cada artículo, a ver que os sale! XDD

Como anécdota personal os diré que a esta canción le tengo un cariño especial porque se trata de la primera canción que escuche en MP3 hace muchos, muchos años… cuando tenias que decidir si querías escuchar un MP3 o hacer otra cosa con el ordenador, porque era imposible hacer alguna otra tarea simultáneamente a la reproducción… ¡que tiempos!

Keep Rockin’!

Posted: 30/5/2012 20:49 por Pablo Alvarez | con 2 comment(s)
Archivado en:
Just the Beginning: Webcast de Introducción a BI con Tecnologías Microsoft

Es incuestionable que Business Intelligence y Big Data son dos de los términos más de moda en nuestro mundo, como demuestra el gran número de artículos, entradas de blog y eventos que se están dedicando últimamente a cubrir estas áreas. Otro indicador quizá más significativo aún de la relevancia y actualidad que tienen estas áreas es el número de ofertas de empleo relacionadas, así como los nuevos roles (con nombres más o menos rimbombantes) surgidos al auspicio de estas nuevas líneas de negocio.

El problema viene a la hora de meter la nariz en este mundillo para ver que se cuece… ¿Por donde empezamos? La avalancha de términos, tecnologías y productos puede echar atrás a más de uno. Es por ello que se me ocurrió brillante idea de escribir una serie de entradas en este blog para intentar allanar un poco el camino a los que queráis conocer el big picture de el estado actual de proyectos de Business Intelligence con herramientas de Microsoft.

La idea era muy buena, pero seamos sinceros… ¿yo? ¿escribiendo en mi blog? xDDD Ya me conozco lo suficientemente bien, y mi lista de borradores si publicar es lo suficientemente amplia, como para saber que eso no sucedería jamás ;) Pero aquí es donde intervinieron los chicos de IT Pro de Technet, que se han encargado de organizar un webcast para el próximo Viernes 7 de Junio a las 19:00h (GMT+1):

BI

La sesión será totalmente introductoria: mi objetivo es que a su conclusión, todos los asistentes tengas una idea clara de que os puede aportar a vuestro negocio tener un sistema de BI (si es que puede aportaros algo ;)), que necesitaríais tener en vuestra organización para montar un sistema de BI y que narices es Big Data y Hadoop y como encaja en el ecosistema Microsoft ;)

Así que ya sabéis, si el Jueves 7 tenéis un par de horas libres y os gusta el plan, no dejéis de apuntaros en el siguiente enlace: https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032514331&Culture=es-ES

Rock Tip:

“Just the Beginning” es uno de los doce temazos que componen el “Out of this World”, el que fuera sucesor en 1988 del archiconocido “The Final Countdown” de Europe. Evidentemente sobran las presentaciones, y más en este blog donde creo que a estas alturas es más que sabido que son una de mis bandas preferidas.

La verdad es que el tema es un buen ejemplo de lo que fue el hard rock melódico más comercial de los ochenta: intro de sintetizador, muy buen trabajo a las guitarras del amigo Kee Marcello (en lo que sería su primer disco con Europe, habiendo sustituido recientemente a John Norum) y un trabajo excelente a las voces de Joey Tempest.

Ya hacía tiempo que tenía ganas de compartir este tema en uno de mis Rock Tips, y creo que esta ocasión era perfecta, ya que eso es lo que vamos a ver en este webcast: el principio de algo mucho mas grande y complejo ;)

Keep Rockin’!

This is the Moment: Arranca el SQL Server Tour de Plain Concepts

Después de los preparativos, ¡por fin vamos a poder divertirnos un poco! Y es que mañana arranca el SQL Server Tour 2012 de Plain Concepts, y nuestra primera visita será Bilbao. Allí estaremos Jose Fernández (@jfvizoso), Ibon Landa (@ibonilm) y un servidor hablando largo y tendido sobre SQL Server 2012, haciendo un gran hincapié en las novedades en BI y Big Data.

BannerSQLServerTour

Me gustaría aprovechar para dar las gracias a todos los que habéis mostrado interés en el evento, pues lo cierto es que las tres fechas publicadas han agotado su aforo en un plazo increíblemente corto de tiempo. Del mismo modo me gustaría pediros un favor…

… un favor …

Ahí está, lo he puesto en grande porque creo que después de los agradecimientos, esto es lo más importante de esta entrada :)

Algunas personas nos han comunicado su interés en acudir al evento una vez agotadas todas las plazas; por ello nos gustaría desde aquí recordaros a los que tengáis una inscripción realizada y sepáis a estas alturas que no vais a poder acudir a alguno de los eventos, nos lo comuniquéis a cualquiera de nosotros tres para que podamos asignar esa plaza a la siguiente persona en lista de espera.

¡Gracias!

Aprovecho para recordaros la agenda:

09:30 - 10:15 Novedades SQL Server 2012
10:15 - 11:00 Escalabilidad y disponibilidad: Always On
11:00 - 11:15 Café
11:15 - 12:00 Gestión de la Calidad y Versionado de Datos: DQS y MDS
12:00 - 13:00 Optimización de Cargas Masivas de Datos
13:00 - 14:00 Comida
14:00 - 15:30 BISM y PowerView
15:30 - 17:00 Big Data: Apache Hadoop, Hadoop on Azure y SQL Server 2012

… y ahora voy a terminar de preparar los flecos pendientes y mañana nos vemos en Bilbao! Hablaremos de SQL Server y disfrutaremos de excelentes pintxos y del que posiblemente sea el mejor zumito de naranja que conozco ;)

Rock Tip:

Ya echabais de menos los “rock tips™” del tío Doval, ¿verdad? ;) En esta ocasión no me extenderé mucho, ya que como os podréis imaginar, aunque el tour empieza mañana, aún nos queda bastante trabajo de última hora. De todos modos creo que yo no sería capaz de escribir una entrada sin presentar al menos mínimamente una pequeña joya musical que pueda estar relacionada de algún modo con el asunto en cuestión de la entrada – si bien es cierto que la mayor parte de las veces esta relación es muy cogida con pinzas, como es el caso.

Para la ocasión de hoy he querido compartir con vosotros el tema ‘This is the Moment’, del discazo ‘Mercury’s Down’ que el amigo Toby Hitchcock (bajo la ‘tutela’ del gran Erik Marktensson, de W.E.T., Eclipse, etc…) se sacó de la manga el año pasado para sorpresa de muchos de nosotros. Un disco más que recomendable para los amantes del Hard Rock ultra melodico/AOR, y aún más para los que aun no conocéis este tipo de música y os apetece escuchar algo nuevo.

Keep Rockin’

Roar: Inserciones Masivas en MongoDB vs SQL Server (IV)

Me alegra comprobar que, al parecer, el pequeño reto que nos traemos Unai y yo está provocando cierto interés; más allá de haber conseguido que el de Monforte y yo nos hayamos tenido que rascar el bolsillo e invitarnos el uno al otro a varias cenas, algo de debate si que se ha generado en twitter, llegando a contar incluso con alguna prueba similar en otra plataforma!(*)

Para quien no haya leído la entrada anterior, o la respuesta de Unai, os recuerdo brevemente las reglas de nuestra improvisada apuesta:

El ganador será aquel de nosotros dos que sea capaz de insertar 500.000 filas (documentos, en su dialecto) compuestas por un GUID y una cadena de 20 caracteres (no unicode). Recordaros que la solución bcp ha sido declarada ilegal, para que Unai siga manteniendo alguna opción de victoria ;)

Lo cierto es que no tenía pensado publicar nada mas avanzada la semana. Sin embargo, me ha surgido un imprevisto viaje a la pérfida Albión este fin de semana para participar en una taskforce de rendimiento (yeah!), y he tenido bastantes ratos libres entre viajes y transbordos. No se me ocurrían muchas maneras mejores de ocupar este tiempo que compartiendo mi siguiente solución al reto, así que ahí vamos!

Descargo de Responsabilidad: Al igual que hice en la entrada anterior, voy a tratar de volver a cubrirme las espaldas frente a conversaciones que deriven en fanatismos. Siempre que escribo acerca de una tecnología ajena al ecosistema de Microsoft acabo con problemas de fanatismos y guerras de religión. Por ello, dejadme que deje muy claro que este post solo pretende contar la historia de un pique interesante con un compañero, así como tratar de aprovechar la ocasión para comentaros un par de técnicas no triviales para realizar cargas rápidas de datos en SQL Server. Vaya por delante que MongoDB me resulta un producto más que interesante y con escenarios para los que es más idóneo que una base de datos relacional.

NOTA: Si alguien esta leyendo mi blog de modo secuencial y nota la ausencia de una entrada llamada ‘Inserciones Masivas en MongoDB vs SQL Server (II)’, esto es porque esta entrada la ha realizado Unai en su blog, y podéis acceder a ella aquí. De hecho, os recomiendo encarecidamente que la leáis incluso en el caso de que no estéis particularmente interesados en MongoDB o bases de datos NoSQL.

(*): El amigo Javier Torrecilla hizo la prueba en Oracle, aunque me niego a dar los resultados! ;) Por otra parte, aun esperamos la versión en WP7 de Yeray!!! En el factor rendimiento no creo que nos gane ni a SQL Server ni a MongoDB, pero lo que es en cool factor nos va a dar una paliza.

¿Por dónde íbamos?

Si recordáis la entrada anterior, mis esfuerzos de inserción ya no eran capaces de aprovechar mejor los procesadores, a pesar de que no se encontraban aún al máximo; mi cuello de botella se había desplazado a otros lugares, y se materializaba en el lado de SQL Server mediante esperas por dos tipos de eventos concretos (WRITELOG y PAGELATCH_EX) como muestra la siguiente captura:

image_thumb58

El tener casi un 50% de las esperas globales en WRITELOG evidencia el uso masivo del log de transacciones por las inserciones. Recordemos que, al contrario que con la escritura de los datos, esta operación es síncrona. A priori poco más podemos hacer de este lado, a no ser que recurriéramos a un escenario de BULK INSERT mínimamente logado, pero lamentablemente esta opción viola las reglas del concurso. Doh… estúpidas reglas!

¡Vamos a ver si la otra espera nos da alguna pista! La espera es por el tipo PAGELATCH_EX, que se trata de una vieja amiga mía. De todos modos, en caso de que os encontréis con alguna espera que no conozcáis, las tenéis casi todas listadas en la entrada relacionada a sys.dm_os_wait_stats de los Books Online… o en vuestro buscador favorito! ;) La respuesta de la documentación para esta espera es la siguiente:

PAGELATCH_EX : Occurs when a task is waiting on a latch for a buffer that is not in an I/O request. The latch request is in Exclusive mode.

Lo que sucede es que al hacer tantas inserciones de modo concurrente, la ultima pagina del heap sufre de contención, lo que limita seriamente nuestra capacidad para hacer inserciones.

¿Que podemos hacer para reducir la contención de esperas PAGELATCH_EX? Aquí viene el momento genial, pero absolutamente contraintuitivo, de esta entrada del blog:

<drum roll>

Vamos a particionar la tabla sobre la que vamos a hacer las inserciones.

</drum roll>

¡¿Particionar la tabla para las inserts?! Se te pira…

Eso es lo que pensé la primera vez que escuché esta técnica; todos estamos más que habituados al particionado de tablas pensando en las mejoras administrativas que nos proporcionan y, como no, de rendimiento a la hora de realizar búsquedas debido a la eliminación de particiones, la menor altura de los índices, etc…. ¿Pero, mejoras en inserciones?

Os podéis imaginar como me quede cuando leí por primera vez acerca de la idea de particionar una tabla para optimizar la carga de datos en un artículo de Thomas Kejser; sin embargo, tiene sentido. Ahora os estoy escuchando a algunos pensar…

¿¡pero que hardware tiene tu portátil?! Si vas a hacer tantas particiones como procesadores (cuatro), ¿no deberías seguir las buenas recomendaciones y poner cada archivo en un disco independiente?

¡Buena pregunta! (y tanto… ¡como que me la he hecho yo a mi mismo! ;)) Pero daros cuenta de un detalle: la contención NO está en el disco, sino en las estructuras internas de SQL Server de la tabla. Lo que pretendo con esto no es optimizar el acceso a disco, sino la sobrecarga que tiene SQL Server cuando tiene varios hilos tratando de insertar en la misma tabla. Al particionarla, estas esperas se reducen, y ese es nuestro objetivo (PAGELATCH_EX).

imageDe cara a realizar este particionado, vamos a realizar un pequeño cambio a mi tabla que, evidentemente, consensué con Unai previamente. De hecho, a priori, este cambio me pone en una situación mas comprometida, ya que implica agregar un campo nuevo (de tipo TINYINT y al que llamaré Hash aunque realmente no es tal…), así como un índice clustered sobre el ID y este nuevo campo.

La siguiente pregunta es: ¿cuantas particiones crear sobre la tabla? En este punto no he sido muy profesional, para ser sinceros: solo he hecho una prueba por el momento, con 8 particiones. Escogí este valor un poco por intuición; mi maquina tiene 4 núcleos reales, y consideré que 8 particiones deberían ser un numero apropiado para poder jugar un poco con la ociosidad de las tareas y minimizar el signal_wait_time. De todos modos, si encuentro algo más de tiempo, trataré de hacer al menos un par de escenarios mas con diferente número de particiones.

Como ya comentamos antes, todas estas particiones apuntaran al mismo filegroup y fichero, por lo que en realidad no se trata de paralelizar las escrituras en ficheros o unidades diferentes; una vez mas recuerdo que solo pretendemos reducir la contención en las estructuras internas de SQL Server.

Así pues, nuestro particionado quedaría tal y como muestra el siguiente diagrama:

image

A continuación os muestro el script de creación de mi nueva tabla, así como la función y esquema de particionado que he empleado, y el nuevo índice clustered:

CREATE PARTITION FUNCTION pf_hash (TINYINT) 
AS RANGE LEFT FOR VALUES 
(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)

CREATE PARTITION SCHEME ps_hash
AS PARTITION pf_hash TO ([ALL])
GO

CREATE TABLE dbo.Test
(
    ID uniqueidentifier NOT NULLDEFAULT NEWID(),
    Payload varchar(20) NULL,
    Hash tinyint NOT NULL
) ON ps_hash(Hash)

CREATE UNIQUE CLUSTERED INDEX CIX_Hash 
ON dbo.Test (ID ASC, Hash ASC) ON ps_hash(Hash)

Además del particionado, voy a adelantarme a un potencial problema de contención de bloqueos en la tabla asegurándome que mi índice no permita bloqueos de pagina ni fila, y estableciendo la política de escalado de bloqueos de la tabla a Auto:

ALTER INDEX CIX_Hash ON dbo.Test
SET (ALLOW_PAGE_LOCKS = OFF, ALLOW_ROW_LOCKS = OFF)

ALTER TABLE dbo.Test SET (LOCK_ESCALATION = AUTO)

El código de la prueba es similar a la versión anterior, pero agregando el código hash a cada una de los Tasks, de modo que cada Task enviará sus inserts a una partición diferente:

        private static void DoWork(int hash)
        {

            int countIterations = 0;
            while (countIterations < 63)
            {
                using (SqlConnection conn = new SqlConnection(connStr))
                {
                    conn.Open();

                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = conn;

                        int countValues = 0;
                        var builder = new StringBuilder();

                        builder.Append("INSERT INTO Test(Id,Payload, Hash) VALUES ");

                        while (countValues < 1000)
                        {
                            builder.Append("(NEWID(), REPLICATE('X',20), ");
                            builder.Append(hash.ToString());


                            if (countValues != 999)
                                builder.Append("),");
                            else
                                builder.Append(")");

                            countValues++;
                        }

                        cmd.CommandText = builder.ToString();
                        cmd.ExecuteNonQuery();
                        countIterations++;
                    }
                }
            }
        }

Y todo esto.. ¿para que?

Solo hay una respuesta a esta pregunta: para derrotar, y humillar si cabe, a Unai y su MongoDB :P Vamos a ver si lo hemos conseguido!

Bueno, en este caso no me entretendré demasiado con los resultados; simplemente os diré que después de los cambios consigo la inserción de los 500.000 registros en 1,8 segundos estables.

Esta solución esta ligeramente por detrás de los 1,6 segundos del MongoDB de Unai, pero aun así me siento muy satisfecho porque es muy estable en tiempos y, bueno… si miro atrás al primer día del reto, no creía que pudiera bajar de los 20 segundos!

Aun así, ya estoy trabajando en la siguiente idea… No se si será mas rápida, pero os garantizo que es bastante espectacular! ;)

Conclusiones:

A pesar de que aún falta lo más importante, el cara a cara final sobre el mismo metal, parece que ya podemos ir obteniendo algunas conclusiones buenas:

  • Para empezar, y contra todo pronóstico (al menos por mi parte), parece que en esté escenario concreto SQL Server puede plantar cara a MongoDB. Aún tengo un as en la manga, pero incluso en el caso de que finalmente SQL Server salga derrotado en este pequeño juego, ya me resulta increíble estar en el mismo orden de magnitud de tiempos, así que no digamos estar en una situación tan reñida y con posiciones de victoria.
  • Aun no llegamos al limite de nuestras CPUs en este escenario; estoy convencido de que algo más podremos hacer… y aquí entra en juego mi as en la manga.
  • En este caso hemos tratado de mejorar las esperas que no se corresponden con las entradas en el log de transacciones; ¿podríamos mejorar este otro frente, que supone el otro 50% del tiempo en nuestro escenario original? Esta parte es mas difícil, por no decir imposible si no podemos realizar el proceso con operaciones mínimamente logadas (BULK), así que no creo que podamos ganar nada por ahí sin hacer trampas a Unai.

Igual que hice en la ocasión anterior, os voy a dejar una pista de lo que será mi siguiente intento para mejorar el rendimiento en las inserciones; solo os diré que voy a tratar de reducir las esperas por SOS_SCHEDULER_YIELD :)

Cuando publiquemos los resultados definitivos, publicaremos también el código de ambas implementaciones; me encantaría que alguien mas se animara a realizar las pruebas y extraer conclusiones!

Por último, quizá sea interesante reflexionar si realmente es necesario tanto esfuerzo para ganar, en este caso, simplemente dos segundos.

Keep Rockin’!

Rock Tip:

“Ready to Roar, ready to rock, ready to give it all I’ve got” reza el estribillo de ‘Roar’, uno de os temas más espectaculares del increíble ‘Coup de Grace’, el último disco de la ya venerable banda de Hard Rock melódico sueca Treat.

Y eso es precisamente lo que me dije a mi mismo cuando Unai y su MongoDB empezaron a apretarme las tuercas! Ready to give it all I’ve got! De hecho, este disco me estuvo acompañando durante gran parte de las pruebas que iba realizando; debería poner al final un grupo de los artistas, discos y temas involucrados en todo el reto ;)

Hot: Inserciones Masivas en MongoDB vs SQL Server

Esta semana estoy en San Sebastián, impartiendo una pequeña formación sobre optimización de SQL Server. A priori, la semana se presentaba tranquila y con gran carga turística al tratarse de la primera ocasión que visito esta ciudad, y mi predilección general por estas tierras del norte.

Lamentablemente, todos mis planes se torcieron tan pronto como descubrí que Unai estaba en la misma ciudad que yo. Como no podía ser de otro modo, ente pintxo y pintxo nuestros temas técnicos favoritos empezaron a surgir en nuestras conversaciones. Y, como cualquiera que conozca a nuestro Unai podrá intuir, no pasó demasiado tiempo antes de que MongoDB secuestrara todos nuestros hilos conversacionales y se convirtiera en nuestro particular trending topic, por delante de otras apasionantes cuestiones como la entropía de la demografía en el postmoderno barroco, o el peculiar criterio que tengo a la hora de escoger mis guitarras.

He de confesar que no recuerdo muy bien la cadena de acontecimientos que nos condujo a la historia que os voy a contar; quizá fuera mi falta de conocimiento respecto a este producto tan de moda (MongoDB), o el número de zuritos que nos habíamos tomado… No lo acabo de tener muy claro, pero el hecho es que, de algún modo, me metí de lleno en un juego/apuesta que acabaría con el escaso tiempo libre que me quedaba a lo largo de la semana.

La Apuesta

Las reglas eran muy sencillas: el ganador sería aquel de nosotros dos que fuera capaz de insertar una carga de trabajo acordada previamente en menos tiempo. Él con su MongoDB de marras, y yo con mi viejo, fiable, pero no siempre bien ponderado SQL Server 2008 R2, ¡como no!

Tras duras negociaciones de paz, Unai y yo llegamos a un acuerdo para evitar emplear armas de destrucción masiva; por este motivo finalmente decidimos no evaluar la que es, de lejos, la manera más rápida de insertar datos en SQL Server: las Bulk Inserts.

La carga de trabajo que nos pusimos de objetivo fue 500.000 registros, cada uno conteniendo un GUID y 20 caracteres textuales (no unicode).

Descargo de Responsabilidad: Siempre que escribo acerca de una tecnología ajena al ecosistema de Microsoft acabo con problemas de fanatismos y guerras de religión. Por ello, dejadme que deje muy claro que este post solo pretende contar la historia de un pique interesante con un compañero, así como tratar de aprovechar la ocasión para comentaros un par de técnicas no triviales para realizar cargas rápidas de datos en SQL Server. Vaya por delante que MongoDB me resulta un producto más que interesante y con escenarios para los que es más idóneo que una base de datos relacional.

Construcción del Entorno de Prueba

En entorno de prueba inicial consistió simplemente en una base de datos que contiene la tabla sobre la que vamos a insertar nuestros 500.000 registros. La única preparación inicial que he realizado de cara a mejorar un poco el rendimiento ha sido dimensionar de modo apropiado los ficheros (para evitar autocrecimientos por uso del log de transacciones en las inserciones) y establecer el modelo de recuperación a SIMPLE.

A continuación os dejo el script inicial, aunque como veréis más adelante hemos realizado algún que otro cambio:

CREATE DATABASE DemoInserts ON PRIMARY 
( 
    NAME = 'DemoInserts', 
    FILENAME = 'c:\Databases\DemoInserts.MDF', 
    SIZE = 200, 
    FILEGROWTH = 10% 
) 
LOG ON 
( 
    NAME = N'DemoInserts_Log', 
    FILENAME = N'c:\Databases\DemoInserts_Log.LDF', 
    SIZE = 200, 
    FILEGROWTH = 10% 
) 
GO 
ALTER DATABASE DemoInserts SET RECOVERY SIMPLE 
GO 
USE DemoInserts 
GO 
CREATE TABLE [Test] 
( 
    ID UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(), 
    Payload VARCHAR(20) NULL, 
)

 

¡Vamos a Insertar Filas!

La primera solución consistirá en un simple bucle de 500.000 ejecuciones, en el que para cada iteración insertamos una fila. Su única ‘optimización’ fue el empleo de la opción NCOUNT ON para evitar saturar el output buffer con los típicos mensajes de (1 row affected),  tal como se puede ver en el siguiente fragmento de código:

SET NOCOUNT ON

DECLARE @cont INT 
SET @cont = 0

WHILE (@cont < 500000) 
BEGIN 
   INSERT INTO Test(ID, Payload) VALUES (DEFAULT, REPLICATE('X', 20)) 
   SET @cont = @cont + 1 
END

El tiempo de ejecución de la inserción en mi equipo fue de 3 minutos y 8 segundos.

NOTA: Todos los tiempos tomados en este artículo se han calculado realizando el promedio de tres ejecuciones sucesivas.

Evidentemente, desde antes de realizar esta prueba yo era consciente de que este mecanismo no iba a arrojar buenos resultados, pero me serviría para tener una línea base respecto a la que poder evaluar las mejoras posteriores y para hacerme una idea respecto a lo que podría esperar de SQL Server.

Llegados a este punto le comenté a Unai que me daría con un canto en los dientes si era capaz de bajar mi tiempo de inserción por debajo de los 20 segundos. Mi amigo y competidor me corrigió, asegurándome que sería él mismo quien se encargaría de darme con el susodicho canto en toda mi dentadura si conseguía tal hazaña. Creo que en ese momento empecé a tomarme nuestra pequeña competición en serio ;)

Manos a la Obra… ¿Dónde esta mi cuello de botella?

Siempre hay un cuello de botella; ¡siempre! Aunque el rendimiento sea excelente, siempre hay un factor limitante, y solo uno. En este caso, un vistazo rápido al Task Manager me permitió ver que la CPU no eran en este caso el problema:

image

Esto me dio una buena idea para el siguiente intento: realizar una pequeña aplicación en C# que se encargara de lanzar INSERTS modo masivo en paralelo, usando múltiples hilos, para tratar de aprovechar al máximo las cuatro CPUs de las que dispone mi equipo de pruebas.

A continuación os pongo el código exacto que he empleado para esta prueba, aunque casi me da vergüenza ;) No me juzguéis por él, es solo un ejemplo ‘quick and dirty’!:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Diagnostics;

namespace ConsoleApplication5
{
    class Program
    {

        static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            var mainTask = new TaskFactory().StartNew(() =>
            {
                new Task(() => DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
            });

            mainTask.ContinueWith((t) =>
            {
                watch.Stop();
                Console.WriteLine(watch.Elapsed);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            Console.ReadLine();
        }

        private static void DoWork(int rows)
        {
            string connStr = "Data Source=.;Initial Catalog=DemoInserts
;Integrated Security=true;Application Name=TestInserts"; int countIterations = 0; while (countIterations < rows) { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand()) { cmd.Connection = conn; cmd.CommandText = "INSERT INTO Test(Id,Payload) VALUES (NEWID(), REPLICATE('X',20))"; cmd.ExecuteNonQuery(); countIterations++; } } } } } }

Como podéis ver, el código simplemente instancia 4 tareas (una por cara núcleo disponible en mi máquina). Estas tareas reciben el número de filas a insertar, y proceden a realizar estas inserciones como comandos SQL dentro de un bucle.

La inserción de las dichosas 500.000 filas por este mecanismo se produjo en 1 minuto y 7 segundos; si bien el valor no está nada mal en términos de mejora, sabía que aún me quedaba mucho para tener un tiempo competitivo. Una nueva visita al Task Manager me hizo darme cuenta rápidamente de que, si bien había aumentado mucho mi consumo de CPU, aparentemente aún disponía de mucho margen adicional:

image

A la vista de esto, decidí probar a aumentar el número de hilos empleados para lanzar las sentencias INSERT, y monitorizar tras cada prueba tanto la duración de la misma como la utilización de los procesadores durante el transcurso de las inserciones. De este modo pude lograr grandes usos del procesador, pero es importante tener presente una cuestión: el incremento de la actividad de CPU no implica necesariamente un mejor rendimiento en las inserciones.

Para demostrar este punto, os adjunto una pequeña gráfica que muestra los tiempos obtenidos en función del número de hilos empleados:

image

(Atiende que graficazaaa… ¿verdad que una de estas ayuda a darle un look más profesional a cualquier artículo? ;))

Como se puede apreciar, el sweet spot de este proceso de inserción en mi equipo se encuentra en los 8 hilos, con un tiempo de ejecución de 43 segundos. A continuación os muestro la gráfica de uso de CPU de esta ejecución, para que podáis contrastarla con la primera de todas y apreciéis el notable incremento en la utilización que hemos logrado:

image

 

¿Por donde seguimos ahora?

Bien, llegados a este punto ya tenía mas o menos claro el punto más idóneo de paralelismo desde el lado de la aplicación C# de inserciones, pero no conocía lo que estaba sucediendo por parte de SQL Server. Por esto, decidí estudiar las esperas del servidor; borre las estadísticas de esperas, lancé el proceso y a su finalización, use el celebérrimo script de Glenn Berry, personalizado por Paul Randal para la agregación de información de esperas por recursos en SQL Server.

El resultado de la prueba, si bien era de esperar, resulta revelador:

image

Como se puede ver, un 99.22% del tiempo total de la prueba se destino a realizar entradas en el log de transacciones; esto se debe al hecho de que las inserciones, salvo casos muy puntuales en escenarios de BULK INSERT, son operaciones completamente logeadas. Dicho a lo bestia: por cada inserción, a parte del dato en si, hay que escribir la intención de realizar la inserción previamente en el log de transacciones. Esta operación es síncrona, y es la que esta provocando una gran cantidad de esperas.

Para tratar de remediarlo, se me ocurrió tratar de reducir el número de cláusulas INSERT que enviamos al servidor mediante las sentencias de inserción múltiples de SQL Server 2008, que nos permiten insertar en el mismo statement hasta mil filas(*). Ya sabéis, algo así:

INSERT INTO Test(ID, Payload) VALUES
(NEWID(), 'AAA'),
(NEWID(), 'BBB'),
...
(NEWID(), 'XXX')

Modifique un poco la aplicación para realizar estas sentencias, quedando el código del siguiente modo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Diagnostics;

namespace ConsoleApplication5
{
    class Program
    {

        static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            var mainTask = new TaskFactory().StartNew(() =>
            {
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
                new Task(() => DoWork(), TaskCreationOptions.AttachedToParent).Start();
            });

            mainTask.ContinueWith((t) =>
            {
                watch.Stop();
                Console.WriteLine(watch.Elapsed);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            Console.ReadLine();
        }

        private static void DoWork()
        {
            string connStr = "Data Source=.;Initial Catalog=DemoInserts;Integrated Security=true;
Application Name=DemoInserts"; int countIterations = 0; while (countIterations < 63) // 63 { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand()) { cmd.Connection = conn; int countValues = 0; var builder = new StringBuilder(); builder.Append("INSERT INTO Test(Id,Payload) VALUES "); while (countValues < 1000) { builder.Append("(NEWID(), REPLICATE('X',20)) "); if (countValues != 999) builder.Append(","); countValues++; } cmd.CommandText = builder.ToString(); cmd.ExecuteNonQuery(); countIterations++; } } } } } }

Con esta nueva versión, usando ocho hilos, baje el tiempo a… (redoble de tambor, por favor) 4 segundos escasos!!!!

Llegados a este punto me sorprendí yo mismo; no creí que pudiera bajar tanto el tiempo de estas inserciones, y por primera vez vi peligrar mi dentadura ;) Como me encanta tener datos y números de cada mejora que voy logrando, volví a lanzar la prueba y consulté las estadísticas de esperas del servidor durante el tiempo de ejecución de la misma:

image

Como podéis ver, el hecho de agrupar las inserciones en batches ha hecho milagros! ahora mismo el servidor esta dedicando aproximadamente el mismo tiempo a escribir en el log de transacciones que en en resolver bloqueos de latches, una situación más normal, y que ha repercutido de manera espectacular en el rendimiento de estas inserciones.

(*): Por si alguien se lo pregunta, yo tampoco tengo ni puñetera idea de por qué el hard-limit de mil filas.

Y por hoy ha estado bien, ¿no? ;)

Con esta entrada he buscado abriros un poco el apetito y, con un poco de suerte, picar a alguien más para que entre al pequeño juego que tenemos Unai y yo. He explicado mis aproximaciones iniciales, pero me reservo para la siguiente entrada la solución ‘buena, buena’ desde el lado del SQL Server.

Para los mas ansiosos os iré adelantando que se basa en particionar la tabla para evitar contención sobre la misma; los detalles de implementación los pondré en mi siguiente post, pero como mis actualizaciones son como los despertares del lazy_writer (ya sabéis, puede ser dentro de 3 milisegundos, o dentro de 3 años!), os he querido adelantar la técnica por si queréis investigarla por vosotros mismos.

A modo de resumen, voy a finalizar con otra gráfica de estas elegantes y vistosas, que hoy tengo el día ejecutivo ;)

image

Por cierto, si os preguntáis que tal lo hizo MongoDb… vais a tener que esperar a que Unai lo publique en su blog. No obstante, os puedo adelantar que fue capaz de bajar de estos 4 segundos….

...

..

.

¡¡¡¡Pero yo también!!!! Muahahahaha!!! ;)

Keep Rockin’!

Rock Tip:

Hacía mucho tiempo que no compartía con vosotros uno de mis rock tips… o mejor dicho, hacía mucho que no compartía nada con vosotros! Tengo el blog muy dejado, pero un servidor no da para más :(

En esta ocasión me traigo a uno de mis descubrimientos del año, los geniales Reckless Love. Banda finlandesa liderada por el increíble Olly Herman, quien fuera vocalista de Crashdiet por una breve temporada y una de las personas con mas carisma y buen rollo que he visto nunca sobre un escenario. Herederos de las mejores raíces del hard rock melódico ochentero, pero con un increíble toque dance y discotequero, puedo afirmar que verles en directo es la experiencia mas cercana a ver al David Lee Roth sin recurrir a un De Lorean con condensador de fluzo.

El tema que he escogido es el single de su segundo (y último hasta la fecha) disco: Hot. No tiene gran relación con la temática de la entrada, pero realmente me apetece compartir este tema con el mundo: si a uno solo de vosotros le hace sentir tan solo una decima parte de lo bien que me sienta a mi escuchar este temazo, ya habrá justificado saltarme mi costumbre de poner temas relacionados.

Forget Me Not: SQL Server Crash Dump Analysis at SQL Bits

Time flies! It’s been more than six months since I had the chance of speaking about SQL Server crash dump analysis at SQL Bits in London.

As you will understand, for me it was a huge honor to share event with SQL Server heroes like Simon Sabin, Ramesh Meyyappan and, obviously, Connor Cunningham – who came from Redmond to London just to give his lecture on my same slot and leave me with just a handful of attendants which I’m sure were left at my session because they thought Connor was the guy with long hair :)

Luckily, the organizers of the event recorded all the tracks and they are now available freely for everyone at their website, so in case you attended SQL Bits but wasn’t able to attend to my session, or if you were unable to get to SQL Bits at all, just click here and you will be able to see and download my session. By the way, I strongly recommend you to check the rest of the sessions; some of them were really impressive.

Lastly, I would like to send a big ‘thank you’ to all the organizers at SQL Bits. They did a tremendous job to achieve an excellent event, even though several factors were against them – such a little Icelandic volcano that I’m sure many of you will remember :) Thank you guys, you rock!

Rock Tip:

This post’s Rock Tip is ‘Forget Me Not’, one of the biggest hits from the L.A. band Bad English, led by the great voice of John Waite and the unmistakable guitar of Mr. Neal Schon. A great song, released on 1989, which blends the melodic AOR and West Coast style with some bright moments from Neal’s Les Paul.

Why did I choose this song for this post? To begin with, because even though it’s already been a while since this event took place, I won’t forget it for a long time! Not only because I had the chance of meeting great people, speak about something I’m passionate about in an excellent venue and attend some great sessions, but also because I was involved in one of my craziest journeys; trying to reach a customer while all European transport and accommodation system were about to collapse due to the eruption of some Icelandic volcano was definitely one of the highlights of my life! J

On the other hand, the song seemed so appropriate because it was composed and performed by Bad English and… well, let’s say that my English skills used to be a lot better that what is shown on that video; sadly, it was a long time since I had last spoken publicly in English, and that shows on the recording. I just hope it is not so bad that you can’t follow it!

Posted: 31/10/2010 0:36 por Pablo Alvarez | con 1 comment(s)
Archivado en: ,
Forget Me Not: Análisis de Volcados de Memoria en SQL Server

¡Cómo pasa el tiempo! Fue ya hace más de medio año, exactamente el pasado 16 de Abril, que tuve la oportunidad de hablar un poco sobre depuración de volcados de memoria de SQL Server para el diagnóstico avanzado de problemas en la sexta edición del SQL Bits de Londres.

Como os podréis imaginar, para mi fue un inmenso honor compartir escenario con grandes de la talla de Simon Sabin, Ramesh Meyyappan y, sobre todo Connor Cunnigham, Principal Software Architect del equipo del Query Optimizer, que se vino desde Redmond a Londres con el único propósito de dar una charla en el mismo slot que yo y dejarme sin asistentes! ;)

Afortunadamente, la organización del evento grabó todas las sesiones y acaban de ponerlas a disposición de todo el mundo en su pagina web, por lo que si acudisteis al evento pero no pudisteis asistir a mi track, o si Londres os quedaba un poco lejos, aquí podéis ver y descargar mi sesión. Os recomiendo que echéis un vistazo a las mismas, porque muchas de ellas son realmente excelentes.

Por último, me gustaría agradecer a toda la organización del SQL Bits su esfuerzo para lograr un evento tan excelente, a pesar de que muchos factores se alinearon para complicarlo todo…. como cierto volcán islandés que nos dejó a muchos buscándonos la vida para atender nuestros compromisos fuera de Londres, y a otros tratando de llegar a tiempo para dar sus sesiones. Great job, guys…You rock! :)

NOTA: Evidentemente, todas las sesiones están en inglés.

Rock Tip:

En este caso, el rock tip será ‘Forget Me Not’, uno de los grandes exitazos de los angelinos Bad English, liderados por el británico John Waite a la voz y la inconfundible guitarra del señor Neal Schon. Un tema de 1989 que mezcla tan bien ese puntito de caña con el estilo melódico y West Coast que caracteriza a esta banda.

¿Por qué he seleccionado el Forget me Not? Pues para empezar, porque aunque ya hace bastante tiempo, no me había olvidado ni del evento, ni de la charla, ni de las peripecias que me sucedieron en ese viaje; creedme, esas no las olvidaré en la vida… Por otra parte, me pareció apropiado poner un tema de los Bad English, ya que en esa ocasión, como podréis comprobar los que veáis el video, no tenía yo mi mejor día en cuanto a soltura con la lengua de Shakespeare y Tim Minchin Sonrisa

Posted: 30/10/2010 23:51 por Pablo Alvarez | con 1 comment(s) |
Archivado en: ,
Heaven: Taller de BI con SQL Server 2008R2 en Pamplona

Los días 3 y 4 de Noviembre voy a tener la oportunidad de impartir un taller sobre Business Intelligence con SQL Server 2008R2 en el Centro de Excelencia de Software de Navarra, lo que supondrá una oportunidad magnífica para darle un repaso al estado del stack de Business Intelligence en la plataforma de Microsoft, comentar algunas cosas interesantes que se pueden hacer con los juguetes nuevos que tenemos a mano (estoy pensando en PowerPivot y en integración con StreamInsight aqui ;)) y, como no, disfrutar un poco de las tierras navarras, su vinito y sus pintxos que tanto me gustan!!

A continuación os dejo la agenda establecida para el curso, para ir poniéndoos los dientes largos :)

  • Vistazo a la Plataforma de BI de Microsoft
    • SSIS, SSAS y SSRS
    • Integración con Sharepoint
    • Consideraciones de Despliegue e Infraestructura
  • Introducción a SQL Server 2008 Integration Services
    • Desarrollo de Soluciones ETL con SSIS
    • Depuración y Gestión de Errores
    • Configuración y Despliegue de Paquetes
  • Introducción a SQL Server 2008 Server Analysis Services
    • Desarrollo de Soluciones Multidimensionales con SSAS
    • Trabajando con Cubos y Dimensiones
    • Trabajando con Hechos y Grupos de Medidas
    • Consultando soluciones Multidimensionales
    • Personalizando la funcionalidad del cubo (Drilldown, Reports, etc...)
    • Introducción a la Minería de Datos
    • Mejoras en SQL Server 2008 / SQL Server 2008 R2
  • Introducción a SQL Server 2008 Server reporting Services
    • Creación de Informes Básicos
    • Mejorando los Informes Básicos
    • Manipulando DataSets
    • Empleando Report Models
    • Publicación y Ejecución de Informes
    • Subscripciones para Distribución de Informes
    • Administración del Report Server
    • Informes con Código
    • Mejoras en SQL Server 2008 / SQL Server 2008 R2
  • Otras Caracterésticas:
    • Clientes: Excel y PowerPivot
    • Monitorización de Rendimiento y Estabilidad

Si estáis interesados y tenéis esos días disponibles, no dejéis de visitar la página del Centro de Excelencia de Software de Navarra para consultar la información detallada del evento, así como la forma de registro.

Rock Tip:

Como la mayoría de vosotros sabéis, tengo la costumbre de acompañar cada entrada de este blog con un poquito de buen Rock n’ Roll; me encanta la idea de compartir con vosotros otra de mis pasiones, y quiero creer que en alguna ocasión haya podido descubriros algún grupo interesante. En alguna ocasión ya os comenté que a veces me resulta muy complicado escoger el tema, pues trato de que el título de la canción tenga alguna relación con la parte técnica del post… ¡creedme cuando os digo que esa tarea suele llevarme casi tanto tiempo como escribir la propia entrada!

Lamentablemente en esta ocasión me toca hacer una excepción; la canción escogida ha sido una decisión puramente personal y nada tiene que ver con el contenido técnico del blog. Los que me conocéis puede que sepáis que uno de mis grupos favoritos es Gotthard, una increíble banda de Hard Rock liderada por el simpatiquísimo Leo Leoni a la guitarra y la inconfundible voz y presencia de Steve Lee.

Lamentablemente, el día 5 de Octubre de 2010, la que ya estaba siendo una semana muy dura de por si se terminó de torcer para mí al conocer la noticia del fallecimiento de Steve Lee en un desafortunadísimo accidente de tráfico; solo nos queda el consuelo de que murió cumpliendo un sueño que llevaba planeando muchos años, un viaje interestatal en su Harley Davidson con sus amigos.

Se me ha hecho increíblemente difícil escoger una canción para esta entrada, pero al final me he decantado por una versión con voz en directo de su balada ‘Heaven’, donde seguro que se encuentra ahora cantando con algún ‘Angel’ este auténtico gentleman… Y aunque no acompañe al post, termino dejándoos el último videoclip que grabaron, para el tema ‘Need to Believe’; video que creo que tardaré años en poder ver sin que se me haga un nudo en la garganta…

Algún día le dejaré mi pequeño recuerdo en el paso del Gotardo, pero por ahora sirva este minúsculo homenaje para recordarle: Danke für alles, Steve…  du gaben uns tolle Songs… du machte uns lächeln und glücklich seing. Mein Beileid geht an die Familie, Freunde und die Band.

Keep rockin’…

Tell Me Where To Go: Estudio de Consultas AdHoc en SQL Server

Uno de mis temas favoritos a la hora de dar charlas de rendimiento de SQL Server es el estudio de la cache de planes de ejecución y la presencia de consultas AdHoc. Puedo pasarme horas hablando sobre ello, y contando batallitas… muy a pesar de los pobres asistentes que deben acabar hasta las narices de las consultas AdHoc, de mí, y de mi santa madre :)

Pero no nos engañemos, si me apasiona tanto este tema es porque se trata de un aspecto muy importante de la salud del servidor y de las aplicaciones que le atacan. Y, además, resulta muy fácil comprobar si estamos sufriendo algún problema relacionado con la presencia masiva de consultas AdHoc en el sistema.

Lo curioso es que, a pesar de lo mucho que me gusta y lo importante que es el tema, no le he hecho justicia con artículos en este blog. Y con esto no quiero decir que no haya escrito acerca de ello; aquí podéis encontrar una breve descripción del ciclo de vida de las consultas en SQL Server, y aquí podéis leer sobre la opción de optimización para cargas de trabajo AdHoc en SQL Server 2008.

Sin embargo, creo que nunca he explicado como procedo a estudiar el estado de salud de la cache de planes de ejecución y procedimientos almacenados, así que espero redimirme un poquito con esta entrada.

NOTA: En esta entrada no voy a explicar el qué son las consultas AdHoc, ni el por qué pueden resultar perniciosas desde el punto de vista del rendimiento. Para esta explicación os recomiendo revisar los dos enlaces que os he puesto un poco más arriba. En esta ocasión solo me centraré en el proceso de estudio de estas consultas.

Vistazo General

Una de las maneras más rápidas y cómodas de comprobar si tenemos una elevada presencia de consultas AdHoc en el sistema es mediante los informes del SQL Server Management Studio (SSMS).

Aunque parezca mentira, voy a dedicar un par de párrafos y capturas de pantalla a detallar como localizar estos informes. Con el paso de los años y las charlas es ido aprendiendo que, curiosamente, estos informes son tan potentes como desconocidos. Lo más habitual es que más de la mitad de los asistentes a mis sesiones desconozcan la existencia de los mismos, por lo que aprovecho para presentarlos a quienes aún no los conozcáis.

Dentro de la interfaz del SSMS, y más concretamente, en el menú pop-up (el del botón derecho del ratón) de todos los nodos del Object Explorer, tenemos una opción llamada Reports.

image

Esta opción es contextual: vamos a tener informes a nivel de instancia, a nivel de base de datos, de usuarios, etc. Evidentemente, hay ciertos tipos de nodos que no tienen ningún informe predefinido, pero siempre podríamos crearnos informes personalizados incluso para los nodos vacíos; son simples ficheros RDLC.

Si bien en esta caso solo vamos a trabajar con un informe concreto, os recomiendo encarecidamente que os deis una vuelta por los informes que vienen predefinidos (Standard Reports), y especialmente por los definidos sobre el nodo de instancia y los definidos sobre los nodos de bases de datos específicas.

En esta caso concreto, nos interesa un informe llamado Server Dashboard, que se encuentra a nivel de instancia. Por tanto, para llegar a él, pinchamos con el botón derecho sobre el nodo de la instancia. Ahí vámos a Reports –> Standard Reports –> Server Dashboard.

NOTA: Esto habituado a trabajar con la versión del producto en inglés, por lo que me vais a perdonar que los pantallazos estén en este idioma, y que no realice las traducciones pertinentes.

image

Como su propio nombre indica, este informe nos muestra un vistazo general del servidor, con la configuración del mismo, versiones del producto, detalles de actividad, etc. Y, cómo no, también nos deja ver un par de gráficas de tarta que alegran el informe con su vivos colores :P

Estas gráficas muestran el porcentaje de utilización de CPU y de E/S por parte de cada una de las bases de datos del sistema, algo bastante útil para detectar si alguna de las bases de datos está monopolizando los recursos del sistema.

A continuación os muestro, a modo de ejemplo, un pantallazo del informe en mi máquina local:

image

Si os fijáis, las tareas que más CPU están empleando en mi entorno no son relacionadas con ninguna de las bases de datos presentes (msdb, PerfDW, ReportServer y db_ProcedimientosAdministrativos), sino con tareas AdHoc.

Como podéis ver, un rápido vistazo a este informe nos muestra que podríamos estar experimentando un problema de consultas AdHoc en el sistema, ya que el consumo de CPU de las consultas AdHoc muy es elevado. ¿Quiere esto decir que si en la gráfica no aparece un gran consumo de CPU o E/S por consultas AdHoc, estamos libres del problema?

… pues va a ser que no :)

Cierto es… aunque tengamos muy poca presencia de consultas AdHoc en el diagrama, es posible que las consultas de este tipo sean un problema en nuestro sistema. Desde aquí, haciendo un pequeño esfuerzo, puedo escuchar vuestras voces de incredulidad en la distancia:

- ¡¿Cómo es posible?! (…se oyen las voces desde más alla del Manzanares…)
- Das ist Unglaublich!! (…algun avezado lector desde la puerta de Brandemburgo…)
- Cómo ye, ¡ho! (…estremecedores palabras que oigo entre Mieres y La Moreda…)

¡Muy fácil! Puede ser que el sistema tenga miles de consultas AdHoc, muy livianas, cuya ejecución no es costosa ni en CPU ni en E/S, pero sin embargo genera presión por recompilación, etc.

Entrando en Detalle

Vale, ya sabemos que puede ser necesario investigar un poco más ¿Cómo podemos hacerlo? Yo recomiendo empezar por comparar el ratio entre consultas AdHoc y consultas No AdHoc. A continuación os pongo un ejemplo capturado de un cliente en el que he estado recientemente:

SELECT
   COUNT(*)
FROM sys.dm_exec_cached_plans
WHERE objtype LIKE 'Adhoc'

En el caso concreto de este servidor, esta consulta ha devuelto 15636 planes adhoc. ¡No es precisamente un número pequeño de planes de ejecución en la cache! Vamos a ver ahora cuantos planes de ejecución no adhoc hay en la cache:

SELECT
   COUNT(*)
FROM sys.dm_exec_cached_plans
WHERE objtype NOT LIKE 'Adhoc'

La consulta anterior nos indica que hay 706 planes de ejecución de consultas no AdHoc. Evidentemente, el ratio entre unas y otras es extremadamente preocupante, y signo evidente de que el sistema está generando constantemente consultas AdHoc.

NOTA: Es difícil dar reglas a seguir en estos casos, pero generalmente puedo decir que si detecto que más del 30% de las consultas de la cache son de tipo AdHoc, considero que hay un problema que investigar respecto a la generación y parametrización de las consultas.

El siguiente paso sería detectar la memoria empleada en planes reutilizados, y en planes no reutilizados, y catalogarlos apropiadamente. En este caso yo utilizo un script, basado en el excelente trabajo de Davide Mauri, que me viene de perlas en estos casos:

WITH Planes AS
(
    SELECT
        Reutilizado = CASE
                        WHEN usecounts > 1 THEN 'Plan Reutilizado (Mb)'
                        ELSE 'Plan No Reutilizado (Mb)'
                      END,
        size_in_bytes AS [Tamaño (Mb)],
        cacheobjtype AS TipoObjetoCache,
        objtype AS Tipo
    FROM
        sys.dm_exec_cached_plans
),
Agregado AS
(
    SELECT
        Reutilizado,
        Tipo,
        TipoObjetoCache,
        [Tamaño (Mb)] = SUM([Tamaño (Mb)] / 1024. / 1024.)
    FROM
        Planes
    GROUP BY
        Reutilizado,
        TipoObjetoCache,
        Tipo
),
Pivote AS
(
    SELECT *
    FROM
        Agregado a
        PIVOT
        ( SUM([Tamaño (Mb)]) FOR Reutilizado IN ([Plan Reutilizado (Mb)], [Plan No Reutilizado (Mb)]) ) p
)
SELECT
    Tipo,
    TipoObjetoCache,
    [Tamaño Reutilizado (Mb)] = SUM([Plan Reutilizado (Mb)]),
    [Tamaño No Reutilizado (Mb)] = SUM([Plan No Reutilizado (Mb)])
FROM
    Pivote
GROUP BY
    Tipo,
    TipoObjetoCache
    WITH ROLLUP
HAVING
    (Tipo IS NULL AND TipoObjetoCache IS NULL ) OR
    (Tipo IS NOT NULL AND TipoObjetoCache IS NOT NULL )

Volviendo al caso de mi cliente, la ejecución de esta consulta devolvió los siguientes resultados:

image

Como podéis comprobar, en este caso el tamaño no reutilizado es casi similar al listado para los planes reutilizados, lo cual es otro indicativo claro de que hay un gran problema de no reutilización de planes de ejecución debido a consultas AdHoc mal parametrizadas.

Después de comprobar que realmente estamos sufriendo el problema ¿cual sería mi siguiente paso? Pues el más divertido de todos… ¡buscar al culpable! ;)

Para ello, buscaría las consultas que aparecen en la cache, de tipo AdHoc, y cruzaría la vista con la funcion sys.dm_exec_sql_text para poder sacar el texto de la SQL a partir del handle:

SELECT
   refcounts,
   size_in_bytes,
   cacheobjtype,
   text
FROM
   sys.dm_exec_cached_plans
   CROSS APPLY sys.dm_exec_sql_text(plan_handle)
WHERE objtype LIKE 'Adhoc'

Os muestro una salida parcial para que veais el problema (sin mostrar toda la consulta y revelar parte del modelo de datos de mi cliente, evidentmente):

image

Como se puede ver, las consultas son iguales, variando exclusivamente en los parámetros; el hecho de que sigan recompilandose y no se reutilicen es significativo, y un potencial problema de rendimiento (como ya se comentó en los otros posts).

¡Ya tenemos nuestros culpables! ¡¡Yeah!!

Resumiendo

A modo de resumen, yo abordaría el estudio del siguiente modo:

  1. Revisión del informe Server Dashboard para buscar presencia de consultas AdHoc
  2. Consulta sobre sys.dm_exec_cached_plans para ver el ratio AdHoc/No AdHoc
  3. Consulta para ver la memoria fugada en planes no reutilizados
  4. Si procede, revisar las consultas problematicas.

Fácil, sencillo y para toda la familia :)

Después de ver esta información, tendremos una buena idea de si estamos siendo víctimas de algún tipo de problema derivado de la presencia masiva de consultas AdHoc en nuestro sistema. Si fuera así, habrá que comprobar la presencia de consultas dinámicas sin preparar, consultas sin parametrizar desde el cliente, etc.

Espero que os haya resultado interesante la entrada, y os animo a que probéis en vuestros servidores SQL Server: ¡seguro que más de uno de vosotros se lleva una sorpresa!

Rock Tip:

Los últimos rock tips, si bien han sido temazos, se han alejado un poco de mi corazoncito hardrockero ochentero, así que es buen momento de retomarlo. Y para ocasiones especiales, nada mejor que los grandísimos glam-metaleros noruegos Wig Wam.

Representantes de su país en el festival de Eurovisión de 2005, con el temazo ‘In My Dreams’, en esta ocasión nos quedamos con su balada ‘Tell Me Where To Go’ para acompañar este post.

A todo esto, apuntaros esta fecha en vuestros calendarios: 18 de Septiembre de 2010. Ese será el día que los chicos de WigWam nos visitarán en Madrid por primera vez, en la sala Ritmo y Compás ¡No creí que mis ojos fueran a ver esto! Ya solo queda ver por estas tierras a Steel Panther y seré un hombre plenamente feliz :)

I Know You’re Here: Materiales de Optimizacion de SQL Server en el CIIN

Aquí os dejo los materiales de la sesión de optimización de SQL Server que tuve la suerte de dar el pasado viernes en tierras cántabras, gracias a la invitación de los chicos del CIIN.

Quiero aprovechar para dar las gracias también a todos los asistentes por las interesantes preguntas y la atención a lo largo de una sesión de más de 5 horas… sé que es duro, y más aún en viernes y, sobre todo, siendo el primer día de las fiestas de Santander! :)

Para los que no hayáis podido ver la sesión el viernes ni presencialmente ni a través de LiveMeeting, pero tengáis interés en la optimización de SQL Server, os dejo aquí el enlace al evento grabado para que lo podáis descargar y ver tranquilamente a vuestro ritmo.

Si se me quedó alguna pregunta por responder, o tenéis más dudas, no dejéis de consultármelas a través del blog o de mi dirección de email!

Rock Tip:

I Know You’re Here”, del grandísimo Steve Vai, es el tema que he escogido en esta ocasión como Rock Tip. Más que nada porque siempre es un placer ver que la gente sacrifica su tiempo personal por acudir a los eventos de la comunidad, por intentar aprender y compartir. Porque sé que siempre estáis ahi, por eso me pareción un Rock Tip apropiado ;)

¡¿Que decir de Steve Vai?! Creo que a todos los que nos gusta la música y nos apasiona la guitarra nos proporciona el mismo feeling: envidia cochina! XD Sus creaciones te pueden gustar más o menos, pero su estilo, su técnica y su virtuosísmo no te van a dejar indiferente. Como lo describiera Brian May en el concierto de leyendas de la guitarra, en Sevilla en 1991: “.. el genio, el maestro de la guitarra de la era espacial.. mr. Steve Vai”. Ahí es nada.

Pinball Map: Cuidado con los Mapeos en NHibernate

Ermm… si, chicos y chicas, sonará raro, pero hoy voy a hablar de NHibernate. Sé que me arriesgo a la condenación eterna en las llamas del infierno, pero yo soy así de altruista y me la juego por vosotros! ;)

Uno de los clientes con los que he estado esta semana(*) estaba experimentando problemas de rendimiento en el acceso a datos en su aplicación, sospechando que el causante pudiera ser una vista poco optimizada. Afortunadamente, el cliente tenía muy bien localizada la operativa más costosa, hasta el punto que podíamos llegar con gran facilidad a la consulta problemática, que era la siguiente:

EXEC sp_executesql N'
SELECT
   this_.ID as ID90_0_,
   this_.BorradoLogico as BorradoL2_90_0_,
   this_.Descripcion as Descripc3_90_0_,
   this_.Estado as Estado90_0_,
   this_.VisibleWeb as VisibleWeb90_0_,
   this_.CodigoDB2 as CodigoDB6_90_0_,
   this_.FechaPrevistaInicioComercializacion as FechaPre7_90_0_,
   this_.FechaFinComercializacion as FechaFin8_90_0_, 
   this_.Comentarios as Comentar9_90_0_,
   this_.URLFoto as URLFoto90_0_,
   this_.Orden as Orden90_0_,
   this_.URLFotoMiniatura as URLFoto12_90_0_,
   this_.SufijoID as SufijoID90_0_,
   this_.MarcaID as MarcaID90_0_,
   this_.VersionId as VersionId90_0_
FROM 
   VistaProblematica this_
WHERE
   this_.CodigoDB2 in (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9) and
   this_.SufijoID in (@p10, @p11, @p12, @p13, @p14) and this_.BorradoLogico = @p15',
N'@p0 nvarchar(5),@p1 nvarchar(5),@p2 nvarchar(5),@p3 nvarchar(5),@p4 nvarchar(5),@p5 nvarchar(5),@p6 nvarchar(5),@p7 nvarchar(5),@p8 nvarchar(5),@p9 nvarchar(5),@p10 bigint,@p11 bigint,@p12 bigint,@p13 bigint,@p14 bigint,@p15 bit',
    @p0 = N'040  ', @p1 = N'1F7  ', @p2 = N'1G3  ', @p3 = N'209  ',
    @p4 = N'3R3  ', @p5 = N'8S1  ', @p6 = N'8S6  ', @p7 = N'8T4  ',
    @p8 = N'8T8  ', @p9 = N'9AK  ', @p10 = 1819, @p11 = 1820, @p12 = 1821,
    @p13 = 1822, @p14 = 3614, @p15 = 0

De un primer vistazo, podemos ver que la consulta esta generada automáticamente por NHibernate. No es que sea inherentemente malo, solo lo digo a título informativo :)

Procedimos a comprobar los tiempos de esta consulta con SET STATISTICS TIME ON:

SQL Server Execution Times:
   CPU time = 703 ms,  elapsed time = 796 ms
.

A la vista del tamaño de filas y la vista, sí que es cierto que el tiempo de ejecución de casi un segundo parece ser demasiado elevado. Por ello, procedimos a analizar el número de operaciones de E/S de cada elemento involucrado en la consulta, mediante SET STATISTICS IO ON:

(50 row(s) affected)
Table 'Worktable'. Scan count 51, logical reads 92037, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Versiones'. Scan count 53, logical reads 316, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Modelos'. Scan count 106, logical reads 424, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Sufijos'. Scan count 9852, logical reads 20996, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Colores'. Scan count 10, logical reads 292, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Como se puede ver, hay bastante actividad de E/S en casi todas las tablas, pero lo que me llamó la atención por encima de todo fue la elevada actividad en las worktables

¿¡Worktables?! ¡¿Que mi madre es eso?!

Pues las worktables son un concepto muy importante en SQL Server, y a la vez, muy poco conocido. En muchas ocasiones son la causa de la degradación de rendimiento de un procedimiento almacenado al suministrarle diferentes parámetros, el origen de los temidos Sort Warning y Hash Warning, y la raíz de todos los males de la huma… perdón, que me dejé llevar :)

Las worktables en realidad son un mal necesario; no son más que unas tablas temporales que se crean en tempDb para las operaciones de Sort y Hash que no han conseguido suficiente memoria como para hacerlas sobre el Buffer Pool, u operaciones grandes de cursores o tipos de datos LOB… en definitiva, son una solución de emergencia para cuando deseamos realizar una operación que va a requerir gran cantidad de memoria.

Habría muchos ejemplos de su utilización, pero os voy a poner uno muy sencillo: Imaginad que tenemos un procedimiento almacenado para realizar un proceso que hace una JOIN de las filas de dos tablas en un periodo de tiempo determinado. Este procedimiento recibe dos parámetros: fecha de inicio y fecha de fin.

Ahora imaginad que la primera vez que se despliega ese procedimiento, se ejecuta para un rango de un solo día. Ese procedimiento va a compilarse tratando de optimizar la ejecución para ese rango de fechas: SQL Server podría decidir realizar la JOIN mediante un operador HASH JOIN debido a que el número de filas no es demasiado elevado, y reservará la cantidad de memoria justa para realizar la operacion de HASH para esas filas.

Supongamos que al día siguiente, esta consulta se realiza para un rango temporal de un mes entero; SQL Server ya tiene el plan cacheado, por lo que va a tratar de reutilizarlo, pero la asignación de memoria para la operación de HASH era la justa para las filas de un día. Al comenzar a ejecutar la consulta, el procesador de consultas detectará que no tiene memoria suficiente y decidirá terminar la operación sobre una Worktable en TempDb. Esto es mucho más lento, pero al menos garantiza que la operación pueda finalizar.

Volviendo al Turrón:

A la vista de la actividad de E/S en Worktables, decidí confirmar mis sospechas, por lo que adjunté una traza de profiler:

profiler

¡Bingo! Podemos comprobar que aparece un evento de Hash Warning al ejecutar la consulta.

Bien, ya sabemos la razón de nuestro problema de rendimiento: el uso de tempDb. Ahora solo nos queda saber por qué esta consulta esta ejecutándose en TempDb.

Después de un par de ideas no muy afortunadas basadas en la recompilación de esa consulta, decidí estudiar los tipos de datos de la definición de esa tabla, para ver si coincidían con los que se pasaban como parámetros en el sp_executesql.

A continuación os muestro las columnas de la vista en cuestion. Permitidme que antes os avise de que lo que vais a ver a continuación puede herir la sensibilidad de más de un desarrollador o arquitecto de datos(**) :)

Definicion

Como podéis ver, la columna CodigoDB2 está definida como char(5), pero en el código de la consulta que genera NHibernate nos pasa el parámetro como nvarchar(5), lo cual aumenta el peso de la consulta considerablemente, y puede estar provocando que la memoria reservada para la consulta no sea suficiente y tenga que volcar la operación sobre worktables en tempDb.

Hicimos la prueba, cambiando los tipos de datos sobre la consulta generada por NHibernate en el sp_executesql, quedando la misma así:

EXEC sp_executesql N'
SELECT
   this_.ID as ID90_0_,
   this_.BorradoLogico as BorradoL2_90_0_,
   this_.Descripcion as Descripc3_90_0_,
   this_.Estado as Estado90_0_,
   this_.VisibleWeb as VisibleWeb90_0_,
   this_.CodigoDB2 as CodigoDB6_90_0_,
   this_.FechaPrevistaInicioComercializacion as FechaPre7_90_0_,
   this_.FechaFinComercializacion as FechaFin8_90_0_, 
   this_.Comentarios as Comentar9_90_0_,
   this_.URLFoto as URLFoto90_0_,
   this_.Orden as Orden90_0_,
   this_.URLFotoMiniatura as URLFoto12_90_0_,
   this_.SufijoID as SufijoID90_0_,
   this_.MarcaID as MarcaID90_0_,
   this_.VersionId as VersionId90_0_
FROM
   VistaProblematica this_
WHERE
   this_.CodigoDB2 in (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9) and
   this_.SufijoID in (@p10, @p11, @p12, @p13, @p14) and this_.BorradoLogico = @p15',
N'@p0 char(5),@p1 char(5),@p2 char(5),@p3 char(5),@p4 char(5),@p5 char(5),@p6 char(5),@p7 char(5),@p8 char(5),@p9 char(5),@p10 bigint,@p11 bigint,@p12 bigint,@p13 bigint,@p14 bigint,@p15 bit',
    @p0 = N'040  ', @p1 = N'1F7  ', @p2 = N'1G3  ', @p3 = N'209  ',
    @p4 = N'3R3  ', @p5 = N'8S1  ', @p6 = N'8S6  ', @p7 = N'8T4  ',
    @p8 = N'8T8  ', @p9 = N'9AK  ', @p10 = 1819, @p11 = 1820, @p12 = 1821,
    @p13 = 1822, @p14 = 3614, @p15 = 0

Tras realizar los cambios, volví a ejecutar la consulta con la información de tiempos y actividad de E/S, y obtuve los siguientes resultados:

SQL Server Execution Times:
   CPU time = 78 ms,  elapsed time = 78 ms.

(50 row(s) affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Colores'. Scan count 10, logical reads 292, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Sufijos'. Scan count 2, logical reads 27, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Versiones'. Scan count 4, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Modelos'. Scan count 8, logical reads 32, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

¡No está nada mal! Hemos conseguido que la consulta se ejecute diez veces más rápido, sin hacer ninguna modificación real sobre la misma. Toda esa pérdida de tiempo se estaba produciendo por el uso de tempdb como consecuencia de la diferencia entre la memoria estimada y la necesaria.

Al final, el cliente arregló el mapeo del tipo de datos, estableciendo el sql-type de modo explícito, lo que eliminó este problema puntual. La gran noticia es que, con toda seguridad, este cambio en el mapeo afectará a muchas otras consultas, por lo que se espera un incremento generalizado del rendimiento en todas las consultas.

Conclusiones:
  • Es muy importante garantizar la corrección de los tipos de datos en los mapeos de cualquier solución de persistencia.
  • Revisad el uso de vuestras tempdb, la creación de worktables y la presencia de eventos de tipo Hash Warning o Sort Warning en vuestros sistemas; es muy posible que os llevéis alguna sorpresa!
  • Estaba claro que si yo iba a hablar acerca de NHibernate, no iba a ser enteramente para bien… ;)

Keep Rockin’!

(*): Soy perfectamente consciente de lo mal que puede llegar a sonar esa frase!
(**): Tengo que dejarlo claro: el modelo de datos NO es mío!!! :)
Rock Tip:

En esta ocasión, voy a dar un poquito más de caña de lo habitual. Los que seguís este blog y sus Rock Tips(tm) sabéis que casi siempre me decanto por un puntito hardrockero melódico, que es lo que suelo escuchar gran parte del tiempo. Sin embargo, en esta ocasión creo que os voy a sorprender con uno de mis grupos favoritos, aunque son de un palo bastante mas heavy: se trata de los suecos ‘In Flames’, grupo fundado y liderado hasta hace poco por ese genio que es Jesper Strömblad.

Cuando estaba escribiendo este post sobre el mapeo en Hibernate, no pude evitar que se me viniera a la cabeza su tema ‘Pinball Map’; quizá fue simplemente por la palabra map, o quizá porque necesitaba un poco de buen death metal melódico sueco para desintoxicarme de NHibernate xD Sea como sea, me apetece compartirlo con vosotros, así que.. enjoy! ;)

Rainbow In The Dark: Materiales de la sesión de Optimización de SQL Server

El pasado Jueves 27 tuve la oportunidad de pasar un par de horas muy agradables en compañía de la gente de MAD.NUG, hablando de optimización de SQL Server. En resumen, vine a explicar un poco el proceso que yo sigo a la hora de enfrentarme a un problema de rendimiento en un entorno SQL Server que, en la práctica totalidad de los casos, me resulta desconocido.

Empezamos repasando algunos de los informes básicos que el producto pone a nuestra disposición, para hacernos una idea de la salud general del servidor, y fuimos bajando de nivel y obteniendo más detalle gracias a la utilización de alguna de las muchas DMVs del sistema. Repasamos y explicamos las esperas del servidor (sys.dm_os_wait_stats) y aproveché para contar un par de batallitas sobre optimizaciones extremas mediante la eliminación de CPUs :)

Por último, dedicamos la última parte de la sesión a estudiar algunos escenarios curiosos de rendimiento, como el uso inapropiado de UDFs en los predicados de las consultas, el tan conocido empleo de cursores, los peligros de las consultas Ad Hoc y por último, un escenario muy interesante de ordenación en tempDb por problemas de estimación de memoria y parameter sniffing.

He de admitir que, para ser una sesión de SQL Server, me lo pasé bien! y es que no solo de WinDbg vive el hombre ;)

Aprovecho para dejaros aquí los materiales de la sesión, tanto las PPTs como los scripts que utilicé durante la misma.

Gracias a todos los asistentes (muy buenas preguntas!) y por supuesto, a la organización de Mad.Nug :)

Keep Rockin’!

Rock Tip:

Podría tratar de encontrar algún rebuscado nexo entre el Rock Tip de hoy y el texto del post; como que espero que los consejos que compartí con vosotros os sirvan en alguna ocasión como ese ‘arcoíris en la oscuridad’ para resolver algún problema en vuestros entornos SQL Server, o que algunos de esos scripts valen más que el oro al otro lado del arcoíris…

Pero esta vez no. Esta vez no necesito justificación para mi Rock Tip. Hoy se trata de un homenaje, y para eso no son necesarias excusas. Ronnie James Dio, la voz del heavy metal, el padre de la mano cornuta (los cuernos heavies de toda la vida!) nos abandonó el día 16 del pasado mes. Un hombre que, pese a sus 67 años, seguía subiéndose a al escenario con una tremenda energía, con una voz asombrosa y sobre todo, con unas ganas de entretener y de ganarse el cariño de sus fans noche tras noche.

Se nos ha ido un grande, pero está claro que será recordado siempre por su legión de seguidores desde los años 70, los chavales de hoy y, sin duda, los que vendrán.

Aquí os dejo una versión del “Rainbow In The Dark” en directo en el Wacken de 2004, donde podemos ver que el gran Ronnie James Dio seguía estando como un chaval y disfrutando como si aún estuviera en Black Sabbath allá por 1979!

Looks that Kill: Componentes de DevExpress y ThreadAbortException
Aviso a Navegantes: El siguiente post no va a ser políticamente correcto. Es más, creo que este año los chicos de Developer Express Inc. no me van a enviar un jamoncito ni una botella de vino por Navidad, precisamente. Y sin embargo, me siento en la obligación de compartir esto con vosotros… así que ¡allá vamos!

Últimamente he pasado bastante tiempo en tierras Navarras: alegrándome la vista con el verdecito que ya voy echando tanto de menos, disfrutando como un enano de los increibles pintxos de Pamplona y, de cuando en cuando, tirando de WinDbg para resolver algunos problemas de rendimiento que tiene uno de nuestros clientes aquí :)

Y es que en estas semanas he visto muchos escenarios interesantes: las tradicionales fugas de memoria por manejadores de eventos en páginas ASP.NET, problemas con el tamaño de sesión y el histórico de ViewStates, un curioso caso de excepciones indeseadas en cada invocación a un Servicio Web con transacciones que espero poder postear en breve… vamos, ¡que no me he aburrido!

Uno de estos problemas, y uno de los más llamativos, tenía que ver con la increible cantidad de excepciones lanzadas en el frontal web de la aplicación; estamos hablando de varias decenas de miles a la hora! A estas alturas no hace falta que os diga que el mero hecho de lanzar una excepción (aunque esta sea capturada por nuestro código) supone un impacto negativo en el rendimiento, y debemos tratar de evitar estas situaciones.

Si bien el contador de rendimiento # of Excepts Thrown en .NET CLR Exceptions era suficiente para detectar el problema, necesitabamos algo más para averiguar el tipo de excepiones lanzadas y poder investigar mejor el problema real. Como no podía ser de otra manera, ese algo más fue WinDbg :)

Después de adjuntarme al proceso, le pedí a WinDbg que se detuviera cada vez que saltara una excepcion .NET (sxe clr) y que en cada una de ellas, me mostrara la información de la excepción (!pe), la pila de llamadas administrada (!clrstack) y que prosiguiera la ejecución sin manejar la excepción (gn):

sxe –c “!pe; !clrstack; gn” clr

La salida demostró que había dos tipos que conformaban la práctica totalidad de las excepciones:

  • MissingManifestResourceException:

Esta excepción no me preocupaba demasiado, porque en número era muy inferior a la que veremos a continuación. Sin embargo, es curioso el hecho de que siempre sucedía en unas DLLs de DevExpress.

A continuación os pongo una ocurrencia del problema (omitiendo la pila de llamadas, que en este caso no es relevante):

(d18.11b4): CLR exception - code e0434f4d (first chance)
Missing image name, possible paged-out or corrupt data.
Exception object: 0000000240991ed8
Exception type: System.Resources.MissingManifestResourceException
Message: Could not find any resources appropriate for the specified culture or the neutral culture.  Make sure "Resources.DevExpress_XtraScheduler_v9_3_Core.resources" was correctly embedded or linked into assembly "App_GlobalResources.2gule58b" at compile time, or that all the satellite assemblies required are loadable and fully signed.
InnerException: <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80131532

Después de revisar la solución, comprobamos que todas las referencias a todas las DLLs de DevExpress estaban aparentemente bien, por lo que decidimos involucrar al soporte de Developer Express en este caso.

  • ThreadAbortException:

Esta excepción ya suena peor ¿verdad? Hacía no mucho, en este mismo cliente, nos habíamos encontrado con el conocido bug de la plataforma en el método Response.End(), pero…. ¡no adelantemos acontecimientos!

Primero veamos un ejemplo de una de las excepciones que capturamos y su volcado de pila:

(d18.11b4): CLR exception - code e0434f4d (first chance)
Exception object: 0000000200f83940
Exception type: System.Threading.ThreadAbortException
Message: Thread was being aborted.
InnerException: <none>
StackTrace (generated):
SP               IP               Function
000000000911E8E0 0000000000000001 System.Threading.Thread.AbortInternal()
000000000911E8E0 000006427834300A System.Threading.Thread.Abort(System.Object)
000000000911E930 00000642BC9131D6 System.Web.HttpResponse.End()
000000000911E980 0000064281AC2EA6 DevExpress.Web.ASPxClasses.Internal.HttpUtils.EndRespons

StackTraceString: <none>
HResult: 80131530
OS Thread Id: 0x11b4 (23)
Child-SP         RetAddr          Call Site
000000000911e980 00000642803d1965 DevExpress.Web.ASPxClasses.Internal.HttpUtils.EndResponse()
000000000911e9c0 00000642803d049d DevExpress.Web.ASPxClasses.Internal.ResourceManager.ProcessRequest()
000000000911ea80 00000642bc8f2eb0 DevExpress.Web.ASPxClasses.ASPxHttpHandlerModule.BeginRequestHandler(System.Object, System.EventArgs)
000000000911eab0 00000642bc8e449b System.Web.HttpApplication+SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
000000000911eb10 00000642bc8f2215 System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef)
000000000911ebb0 00000642bc8e3553 System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception)
000000000911ec60 00000642bc8e7874 System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object)
000000000911ecc0 00000642bc8e745c System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest)
000000000911ed50 00000642bc8e608c System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest)
000000000911ed90 000006427f602012 System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32)

Como se puede ver, uno de los métodos de las librerias de DevExpress, DevExpress.Web.ASPxClasses.Internal.HttpUtils.EndResponse está invocando a System.Web.HttpResponse.End(), el cual a su vez lanza una excepción de tipo ThreadAbortException.

Mmm… antes hablabamos de un caso conocido de excepción ThreadAbortException en las llamadas a HttpResponse.End(), así que una rápida busqueda nos lleva al KB312629, que si no conoceis os recomiendo que leais y comprobéis si vuestras aplicaciones se ven afectadas por él.

Una comprobación del código fuente del método de DevExpress (con la siempre inestimable ayuda de .NET Reflector), nos revela que los desarrolladores del producto son conscientes del problema, implementando un catch para el ThreadAbortException:

public static void EndResponse()
{
    HttpContext.Current.ApplicationInstance.CompleteRequest();
    try
    {
        GetResponse().End();
    }
    catch (ThreadAbortException)
    {
    }
}

Sin embargo, esta solución no evita el lanzamiento descontrolado de las excepciones que, como ya se ha comentado, a pesar de ser controladas, siguen teniendo un impacto sobre el rendimiento de la aplicación, ¡por no hablar del ruido que meten a la hora del monitorizar el sistema!

Con esta información en la mano, elaboré un documento detallado (mucho más que este post) para que mi cliente pudiera abrir un caso de soporte con la gente de DevExpress, esperando que resolvieran el problema que nos estaba impactando severamente en producción, mediante la implementación condicional de una llamada a HttpContext.Current.ApplicationInstance.CompleteRequest() en lugar de invocar al método HttpResponse.End().

La respuesta de la gente de DevExpress no se hizo esperar mucho (punto para ellos!), y se resume en lo siguiente:

  1. No van a cambiar el código, ni a dejar la opcion al desarrollador de asumir el cambio con el CompleteRequest.
  2. Del problema de las MissingManifestResourceException simplemente nos comentaron que esas excepciones son esperadas y que no nos preocupemos.
  3. Con las dos respuestas (a todas luces insuficientes y debatibles) llegó la notificación del cierre del caso. Sin derecho a réplica.

En esta ocasion, lo que más rabia me da no es la parte técnica; puedo entender los problemas que tengan para realizar el cambio (aunque no es de recibo que su código levante tantas excepciones), hasta casi puedo entender que su código levanta excepciones buscando recursos localizados inexistentes… pero por lo que no paso es por la dejadez y el mal trato al usuario en el soporte técnico.

Asi que… ¡Avisados estais! :) Vamos a por el mini-resumen del día…

Resumiendo:

  1. ¿Habéis monitorizado su vuestra aplicacion tira excepciones? Si no es así, revisad los contadores de rendimiento y tirad de WinDbg… ¡a lo mejor os llevais una sorpresa!
  2. Comprobad si teneis ThreadAbortException, y si es así, evaluad el workaround descrito en el KB.
  3. ¿Estais planteandoos adquirir una licencia para controles de usuario para un nuevo proyecto? ¡Aseguraos de tener un buen soporte, no sabeis lo útil que puede llegar a ser!

Keep Rockin’!

Rock Tip:

No podía ser de otra manera, este post solo lo podían presentar los chicos de Mötley Crüe con su “Looks that Kill”. Apropiado, porque en este caso la vistosa interfaz de usuario de la aplicación es la que contiene un pequeño veneno, como hemos visto ;)

Posted: 1/6/2010 1:12 por Pablo Alvarez | con 7 comment(s) |
Archivado en:
Bang Bang!: Optimización Económica en SQL Server

Soy un impresentable. No hay manera de que actualice el blog, y cuando lo hago, es para promocionarme de mala manera. Y, como no podía ser de otra manera, esta es una de esas ocasiones :)

Esta vez tengo que agradecer a Gisela, Luis y el resto de chicos de Madrid.NET la oportunidad de pasar un ratito friki serio, formal y productivo hablando de optimización económica de SQL Server. La idea será aprender a localizar de dónde vienen los problemas, en lugar de tratar de optimizar las consultas en base a su duration elevado de modo sistemático… vamos, la venerable metodología ASM (A Salto deMata(tm)).

La primera parte del evento abordará las técnicas de revision que yo suelo emplear en mi trabajo en el equipo DOT de Plain Concepts, mientras que la segunda parte se centrará en una serie de casos particulares de optimización que me encuentro en algunos clientes y que, a pesar de que alguno de ellos es muy frecuente, siguen siendo problemas bastante desconocidos para los que no nos pasamos delante de un SQL Server todo el dia :)

Si el plan os resulta interesante o apetecible, ya sabeis.. ¡nos vemos el jueves! El evento será en las oficinas de Microsoft en La Finca, y los datos del registro los teneis en el siguiente enlace:

https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032451680&Culture=es-ES

Keep rockin’!

Rock Tip:

Hoy me toca hacer algo que no me gusta, y que creo que solo hice en otra ocasión en este blog: voy a repetir un Rock Tip. Y voy a hacer esto por tres razones totalmente justificadas:

  1. El nombre es apropiado… vamos a hacer nuestro el lema de ’Biggest Bang for the Buck’, así que el Bang Bang! nos viene que ni pintado…
  2. Estamos hablando de la que posiblemente sea la mejor canción del mejor grupo de la historia. Punto pelota. xD
  3. El blog es mio y…. mmmm, creo que recurro con demasiada frecuencia a este punto :)

Por todo esto, hoy le ha tocado el turno a Bang Bang, un tema del primer disco de Danger Danger. Si os apetece oir una cancion happy, que anima al instante, probadla. Como la gente que me conoce sabe de sobra, esta banda neoyorkina es mi grupo favorito, así que no me extenderé en detalles porque podría llegar a aburriros :)

Si os interesa la musica de guitarristas para guitarristas, principalmente instrumental, no os perdais los trabajos en solitario de su guitarrista, Andy Timmons, que para mi es el guitar hero con mas estilo que ha parido madre :) Por cierto, el señor Timmons ha estado esta pasada semana en Madrid… así que también lo celebraremos con un temazo suyo: Cry for You.

Photograph: SQL Server Crash Dump Analysis

Just a small post to remind that this week I’ll be presenting at SQLBits VI, in UK! I’m going to talk about SQL Server crash dump analysis – some post-mortem debugging techniques that help us to discover what went wrong and why our SQL Server crashed, using mostly WinDbg. We will also do a little tour on managed debugging, showing how these techniques and tools can be useful when trying to detect anomalous behaviors on our .NET Applications accessing the database.

 SQLBitsLogo

 

It goes without saying that I’m really excited to be able to share event with such an incredible list of speakers! There are other incredible tracks at the same time: Conor Cunningham will come all the way from Redmond to London to share with us some of the internals and secrets the Query Optimizer, James will deliver a very interesting session on sequential I/Os (I wish I could invite some of my customers to that session!) and Satya will share his huge experience with us in a best practices session. With this great sessions at the same time, I understand many of you that could be interested in attending my session won’t be able to! So if you cannot attend my session, just catch me anytime on the venue and I will be more than happy to have a nice chat about post-mortem debugging, WinDbg or whatever… by the way, I’m the one with the cowboy boots and long hair! :)

See you at SQL Bits on Thursday!

Rock Tip:

Each time that I write a new entry on this blog I use to spend a ridiculous amount of time looking for a song that fits the post; you know I don’t want my Rock Tips to be just a bunch of songs that I like without a reason. Well, this time the job was really easy! Think about it: the session is going to be in the UK… so a band from the country is definitely a must. And I’m going to be talking about memory snapshot analysis… about ‘photographs’ of the process memory… so, without further ado, let me introduce you the Rock Tip of the day: Photograph, by Def Leppard.

One of that classic 80’s tunes that I love so much, and definitely one of my fave from this always impressive bands. This time I’ve chosen to link the song to a live performance of the band, and not to the promotional video of the single, so you can enjoy the impressive work on the backing vocals, the acting and the always missed guitar playing of the late Steve Clark.

Just Push Play: Depuración Avanzada (2ª Parte) en Second Nug

Lo se, lo se… ¡os tengo muy abandonados! y de hecho, la entrada de hoy no va a cambiar mucho la situación, ya que será muy breve; un simple ejercicio de autopromoción :)

Solo quería recordaros que mañana, Martes 15 de Diciembre, tengo el placer de poder dar la continuación del evento de Depuración Avanzada que dí en colaboración con los chicos y chicas de Second Nug.

A continuación teneis los detalles de registro para el evento de mañana:

Debugging

PISTA: Pinchad en la imagen :)

En el abordaremos alguna demo de depuración con WinDbg que nos quedó pendiente en la primera sesión, así como depuracion con CLR 4.0. Por ultimo, terminaremos echando un vistazo a las neuvas características de depuración de Visual Studio 2010, que son sencillamente espectaculares. De echo, para una vez que escribo, voy a tirar la casa por la vetnana y también os recuerdo el link a la descarga de Visual Studio 2010 Beta 2, para que podais jugar con él :)

Keep Rockin’!!!

Rock Tip:

Ya sabéis, los chicos de Aerosmith están de actualidad por el espectáculo de prensa rosa que están protagonizando últimamente; la útlima barbaridad que yo he escuchado es que Lenny Kravitz quiere sustituir a Steven Tyler a la voz!! Yo me quedo con la idea de que es todo un truco publicitario, la necesidad de hacer un poco de ruido. Y si es así, les ha funcionado porque aqui estoy yo para recordaros a todos lo grande que es esta banda!

En este caso, hemos querido dedicarle el tema Just Push Play… un tema que nos va a venir al pelo al analizar los volcados de memoria desde el Visual Studio 2010, ¡ya veréis! :)

Beat The Bullet: Aplazado el Evento de SQL Server en Coruña

No os podéis imaginar lo que me fastidia tener que daros esta noticia, pero me he visto obligado a tener que posponer mi sesión de Optimización de SQL Server que los chicos de .NUGG habían organizado en Coruña.

Estamos intentando moverla al Viernes de la semana que viene; se que es precipitado, pero si esperamos más, nos metemos en periodo de vacaciones. Espero editar este post a lo largo de la tarde y daros la confirmación definitiva.

A todos los que estabais apuntados para el evento, por favor, aceptad mis disculpas. A veces estas cosas pasan.

Parece que esa Estrella Galicia va a tener que esperar un poquito :) Keep Rockin’!

Rock Tip:

Como ya sabréis, Beat The Bullet es una expresión que viene a querer decir algo así como ‘aguantar estoicamente cuando algo va mal’, una expresión que ya emplee en otra entrada de este blog, curiosamente para hacer referencia a otra sesión en Coruña. En aquella ocasión, el Beat the Bullet hacia mención al mejor tema del mejor grupo de la historia, el Beat the Bullet de los Danger Danger.

Como no me gusta repetir temas en los Rock Tips, en esta ocasión haré mención a otro temazo con el mismo nombre, el Beat the Bullet de Vain.

Speed of Light: Evento sobre Optimización de SQL Server en Coruña

Este Viernes 17 voy a tener la oportunidad de volver a pasarme por tierras gallegas. En esta ocasión, los chicos de .NUGG han tenido a bien invitarme a presentar una sesión sobre Optimización de SQL Server, así que antes de nada, permitidme que agradezca a Edu y a Euge su invitación y la organización del evento! Gracias chicos!

Para ser sinceros, esta sesión no deja de ser una deuda contraída con los asistentes a mi última sesión en Coruña sobre Depuración y Optimización con WinDbg. En ella se demostró un claro interés por la optimización de SQL Server, y prometí volver con un temario centrado en exclusiva en este tema. Como lo prometido es deuda, y como necesitaba alguna excusa para tomarme una Estrella Galicia en buena compañía, este viernes nos veremos en Coruña!

Aquí os dejo los detalles de la sesión, a la que os podéis registrar pulsando aquí. ¡¡Nos vemos en Coruña!!

Speed of Light: Optimización de SQL Server

Fecha: viernes, 17 de julio de 2009
Horario: 17:00 - 19:00
Lugar:
- UNIVERSIDADE DA CORUÑA
- Aula 1, Edificio Xoana Capdevielle
- Campus de Elviña s/n A Coruña 15071

Descripción:

Esta sesión está dedicada a detallar las técnicas y herramientas que nos permitirán exprimir el rendimiento de nuestro SQL Server al máximo, haciendo un repaso por la teoría implicada, y pasando directamente a ejemplos del Mundo Real™. Se abordarán buenas prácticas de indizado, las consideraciones de almacenamiento más relevantes, el análisis de planes de ejecución, así como la utilización de nuevas características de SQL Server 2008 para optimizar cargas de trabajo.

1.- Arquitectura general de SQL Server
2.- Metodología Waits & Queues: Monitorización de Esperas en SQL Server
3.- Optimización de Almacenamiento
4.- Optimización del Uso de Memoria
5.- Aislamiento Transaccional
6.- Optimización de Consultas

Rock Tip:

Esta vez, tanto la sesión como el post comparten rock tip, que no podía ser otro que el ‘Speed of Light’ de los speed-metaleros fineses Statovarius. Un tema clásico, con una formación clásica en la banda, antes de que al señor Timo Tolkki le diera por seguir las instrucciones que le dictaba una patata en llamas, a fingir un asalto y acuchillamiento en plena gira, o a hacer cambios ridículos en la formación de la banda con fines promocionales.

¡Vamos a poner nuestros servidores SQL Server a la velocidad de la luz! Keep Rockin’!

Más artículos Página siguiente >