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 😛

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 😛

  • 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’!

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 😛 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:DatabasesDemoInserts.MDF', 
    SIZE = 200, 
    FILEGROWTH = 10% 
) 
LOG ON 
( 
    NAME = N'DemoInserts_Log', 
    FILENAME = N'c:DatabasesDemoInserts_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!

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

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’…