<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://geeks.ms/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Surviving the Night : Rendimiento</title><link>http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx</link><description>Etiquetas: Rendimiento</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP1 (Build: 31106.3070)</generator><item><title>Why Do I?: Memoria para los Workspaces en SQL Server (I)</title><link>http://geeks.ms/blogs/palvarez/archive/2012/12/30/why-do-i-memoria-para-los-workspaces-en-sql-server-i.aspx</link><pubDate>Sun, 30 Dec 2012 02:51:35 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:207906</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=207906</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2012/12/30/why-do-i-memoria-para-los-workspaces-en-sql-server-i.aspx#comments</comments><description>&lt;p&gt;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 Server&lt;sup&gt;1&lt;/sup&gt;, 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.&lt;/p&gt;  &lt;p&gt;Durante las sesiones de estudio me gusta tomar notas de lo que voy repasando, así como preparar unas &lt;em&gt;slides&lt;/em&gt; 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.&lt;/p&gt;  &lt;p&gt;En esta ocasión, voy a hablaros de una zona de memoria especial de SQL Server, llamada &lt;em&gt;Workspace&lt;/em&gt; 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 &lt;em&gt;Hash Warnings&lt;/em&gt;, &lt;em&gt;Sort Warnings&lt;/em&gt;, etc…) y su relación con esta misteriosa y desconocida área.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;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 &lt;/em&gt;&lt;/p&gt;  &lt;h1&gt;&lt;/h1&gt;  &lt;h3&gt;La Memoria en SQL Server&lt;/h3&gt;  &lt;p&gt;Esta claro que antes de zambullirnos en la manera en la que SQL Server gestiona la memoria &lt;em&gt;workspace&lt;/em&gt;, 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…&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;La memoria de SQL Server se divide en dos zonas principalmente:&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;&lt;strong&gt;Buffer Pool:&lt;/strong&gt; 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.&lt;/li&gt;      &lt;li&gt;&lt;strong&gt;Non-Buffer Pool:&lt;/strong&gt; También llamada &lt;em&gt;mem-to-leave&lt;/em&gt;, es la zona de memoria en al que se realizan las reservas superiores a 8Kb.&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;El parámetro de configuración &lt;b&gt;max server memory (MB)&lt;/b&gt; limita sólo Buffer Pool&lt;/li&gt;    &lt;li&gt;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)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Si no es así, os recomiendo que busquéis alguna introducción básica al tema y luego continuéis leyendo por aquí :)&lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Aunque estas cosas son bastante genéricas, en realidad solo son correctas para un SQL Server 2008 R2 con arquitectura x64.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Este maravilloso diagrama &lt;em&gt;“Windows 8 Modern UI”&amp;#160; &lt;/em&gt;que os he preparado, sin bordes, ni colorido, ni &lt;em&gt;ná de ná&lt;/em&gt;, muestra la relación entre los componentes de asignación de memoria en SQL Server 2008 R2:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_63E186F2.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_5E92A041.png" width="540" height="308" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Un rápido resumen podría ser el siguiente:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Las reservas de memoria de 8Kb o menores se almacenan en el Buffer Pool (mediante en &lt;em&gt;single-page allocator&lt;/em&gt;)&lt;/li&gt;    &lt;li&gt;Las reservas de memoria mayores de 8Kb se dirigen al área mal llamada &lt;em&gt;Mem-to-leave &lt;/em&gt;a través del &lt;em&gt;multi-page allocator&lt;/em&gt;.&lt;/li&gt;    &lt;li&gt;Las reservas de memoria del código de CLR que se ejecuta dentro del proceso de SQL Server se dirigen también al &lt;em&gt;mem-to-leave &lt;/em&gt;mediante el &lt;em&gt;VAS allocator&lt;/em&gt;.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;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 &lt;em&gt;single-page allocator&lt;/em&gt; y el &lt;em&gt;multi-page allocator, &lt;/em&gt;habiendo sido sustituidos ambos por un único y genérico gestor de reservas de memoria, como se puede ver en el siguiente diagrama:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_0F45A7ED.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_55566500.png" width="540" height="308" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;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 &lt;b&gt;max server memory (MB)&lt;/b&gt; y el &lt;b&gt;min server memory (MB)&lt;/b&gt;. &lt;/p&gt;  &lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;Memory Manager&lt;/em&gt; (la zona verde), las varias &lt;em&gt;caches &lt;/em&gt;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!! &lt;/p&gt;  &lt;p&gt;&lt;em&gt;&amp;lt;sonido de truenos y lluvia distante, por favor….. …… ¡gracias!&amp;gt;&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;h3&gt;Los Iteradores&lt;/h3&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;A nivel interno, todos exponen los mismos tres métodos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;     &lt;p&gt;&lt;strong&gt;Init()&lt;/strong&gt;: 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 &lt;em&gt;binds/rebinds&lt;/em&gt; en el plan de ejecución.&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;strong&gt;GetNext()&lt;/strong&gt;: 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? ;)&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;strong&gt;Close()&lt;/strong&gt;: 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.&lt;/p&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Aquí tenéis la referencia de los disponibles en SQL Server 2012: &lt;/em&gt;&lt;a title="http://msdn.microsoft.com/es-es/library/ms191158.aspx" href="http://msdn.microsoft.com/es-es/library/ms191158.aspx"&gt;&lt;em&gt;http://msdn.microsoft.com/es-es/library/ms191158.aspx&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Sigamos: &lt;strong&gt;todos&lt;/strong&gt; los iteradores (&lt;em&gt;Table Scan&lt;/em&gt;, &lt;em&gt;index Scan, Compute Scalar&lt;/em&gt;, etc…) en un plan de ejecución necesitan &lt;em&gt;algo&lt;/em&gt; de memoria para levantar su infraestructura básica, para mantener el estado del iterador y para &lt;strong&gt;almacenar&lt;/strong&gt; &lt;strong&gt;las filas que están siendo procesadas&lt;/strong&gt;. Este último punto es el más interesante, veamos por qué.&lt;/p&gt;  &lt;p&gt;Los iteradores pueden ser de dos tipos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;La mayoría de los iteradores que nos vamos a encontrar en un plan de ejecución son de tipo &lt;strong&gt;streaming&lt;/strong&gt;; 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.&lt;/li&gt;    &lt;li&gt;Hay unos pocos operadores que son de tipo &lt;strong&gt;parcialmente&lt;/strong&gt; o &lt;strong&gt;completamente&lt;/strong&gt; &lt;strong&gt;bloqueantes&lt;/strong&gt; (&lt;strong&gt;blocking&lt;/strong&gt;). Estos deben de mantener un subconjunto (o la totalidad) de las filas en memoria hasta que finaliza su proceso, en cuyo momento el &lt;strong&gt;GetNext()&lt;/strong&gt; devuelve un bloque de &lt;em&gt;n&lt;/em&gt; filas, en lugar de una única filas.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Evidentemente los iteradores parcialmente o completamente bloqueantes consumen mucha más memoria. Un ejemplo básico es el operador &lt;strong&gt;sort&lt;/strong&gt;, que es &lt;strong&gt;complentamente bloqueante&lt;/strong&gt; 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.&lt;/p&gt;  &lt;p&gt;La buena noticia es que son muy pocos los operadores bloqueantes, y se pueden categorizar en estos tres tipos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Hash Match:&lt;/strong&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Hash Match&lt;/li&gt;      &lt;li&gt;Hash Match (Aggregate)&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;&lt;strong&gt;Sort:&lt;/strong&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Sort&lt;/li&gt;      &lt;li&gt;Sort (Top N Sort)&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;&lt;strong&gt;Exchange:&lt;/strong&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Distribute Streams&lt;/li&gt;      &lt;li&gt;Gather Streams&lt;/li&gt;      &lt;li&gt;Repartition Streams&lt;/li&gt;   &lt;/ul&gt; &lt;/ul&gt;  &lt;p&gt;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 &lt;em&gt;workspace&lt;/em&gt; de la consulta (&lt;em&gt;workspace memory grants&lt;/em&gt;).&lt;/p&gt;  &lt;h3&gt;Algunas Preguntas&lt;/h3&gt;  &lt;p&gt;Lo primero que nos podemos preguntar es, &lt;strong&gt;¿de dónde sacamos esa memoria?&lt;/strong&gt; 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 ;)&lt;/p&gt;  &lt;p&gt;La siguiente pregunta es: &lt;strong&gt;¿cuanta memoria se va a reservar de nuestro Buffer Pool para la ejecución de la consulta?&lt;/strong&gt; De esto se encargará nuestro amigo, el &lt;em&gt;Query Optimizer&lt;/em&gt;, que tratará de averiguar el &lt;em&gt;Ideal Grant&lt;/em&gt;, 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 &lt;em&gt;Ideal Grant&lt;/em&gt; 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.&lt;/p&gt;  &lt;p&gt;Es interesante aquí darse cuenta de que estamos trabajando con &lt;strong&gt;estimaciones&lt;/strong&gt;. A modo de ejemplo, supongamos un escenario de una consulta simple con un ORDER BY: el &lt;em&gt;Query Optimizer&lt;/em&gt; 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.&lt;/p&gt;  &lt;p&gt;Para ver la información de la memoria asignada al &lt;em&gt;workspace&lt;/em&gt; 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 &lt;em&gt;MemoryGrantInfo&lt;/em&gt;:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_5411CC21.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;margin:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_7DA59754.png" width="540" height="362" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;SQL Server impone un limite máximo a la memoria destinada a los &lt;em&gt;workspaces&lt;/em&gt; (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. &lt;/p&gt;  &lt;p&gt;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?&lt;/p&gt;  &lt;p&gt;Las respuestas, ¡en la próxima entrada! ;)&lt;/p&gt;  &lt;h3&gt;Resumiendo&lt;/h3&gt;  &lt;p&gt;&lt;font style="background-color:#ffffff;"&gt;&lt;/font&gt;&lt;font style="background-color:#ffff00;"&gt;&lt;/font&gt;&lt;font&gt;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 &lt;em&gt;workspaces&lt;/em&gt; 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.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;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. &lt;/p&gt;  &lt;h5&gt;&lt;em&gt;&lt;sup&gt;&lt;font style="font-weight:normal;"&gt;1&lt;/font&gt;&lt;/sup&gt;&lt;font style="font-weight:normal;"&gt;: 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 :)&lt;/font&gt;&lt;/em&gt;&lt;/h5&gt;  &lt;h5&gt;Rock Tip:&lt;/h5&gt;  &lt;p&gt;Si en &lt;a href="http://geeks.ms/blogs/palvarez/archive/2012/11/21/brothers-in-arms-we-are-hiring.aspx"&gt;la última entrada&lt;/a&gt; os ponía un temazo de W.E.T., en esta ocasión no me voy a ir muy lejos: &lt;a href="http://www.youtube.com/watch?v=5dYJE352Kco"&gt;‘Why do I?’&lt;/a&gt; es el tema que abre &lt;strong&gt;Artwork&lt;/strong&gt;, el disco de debut de los suecos &lt;a href="http://www.woa.se/"&gt;Work of Art&lt;/a&gt;. I digo que no me voy muy lejos, ya que el guitarrista, principal compositor y lider de esta banda no es otro que &lt;strong&gt;Robert Säll&lt;/strong&gt;, uno de los lideres de W.E.T. de quien ya os hablaba anteriormente.&lt;/p&gt;  &lt;p&gt;¿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 &amp;amp; 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.&lt;/p&gt;      &lt;p&gt;&lt;em&gt;Keep Rockin’!&lt;/em&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=207906" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Hot: Inserciones Masivas en MongoDB vs SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2011/11/17/hot-inserciones-masivas-en-mongodb-vs-sql-server.aspx</link><pubDate>Thu, 17 Nov 2011 02:09:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:201723</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>11</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=201723</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2011/11/17/hot-inserciones-masivas-en-mongodb-vs-sql-server.aspx#comments</comments><description>&lt;p&gt;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. &lt;/p&gt;  &lt;p&gt;Lamentablemente, todos mis planes se torcieron tan pronto como descubrí que &lt;strong&gt;&lt;a href="http://geeks.ms/blogs/unai/"&gt;Unai&lt;/a&gt;&lt;/strong&gt; estaba en la misma ciudad que yo. Como no podía ser de otro modo, ente &lt;em&gt;pintxo&lt;/em&gt; y &lt;em&gt;pintxo&lt;/em&gt; nuestros temas técnicos favoritos empezaron a surgir en nuestras conversaciones. Y, como cualquiera que conozca a nuestro &lt;em&gt;Unai &lt;/em&gt;podrá intuir, no pasó demasiado tiempo antes de que MongoDB secuestrara todos nuestros hilos conversacionales y se convirtiera en nuestro particular &lt;em&gt;trending topic&lt;/em&gt;, 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 &lt;a href="http://img.guitarchina.com/imgeshop/ibanez/rg3550dy01.jpg"&gt;mis guitarras&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;zuritos&lt;/em&gt; 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.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;La Apuesta&lt;/h3&gt;  &lt;p&gt;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!&lt;/p&gt;  &lt;p&gt;Tras duras negociaciones de paz, &lt;em&gt;Unai&lt;/em&gt; 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 &lt;em&gt;Bulk Inserts&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;La carga de trabajo que nos pusimos de objetivo fue 500.000 registros, cada uno conteniendo un GUID y 20 caracteres textuales (no unicode).&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Descargo de Responsabilidad:&lt;/strong&gt; 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.&lt;/em&gt;&lt;/p&gt;  &lt;h3&gt;Construcción del Entorno de Prueba&lt;/h3&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;A continuación os dejo el script inicial, aunque como veréis más adelante hemos realizado algún que otro cambio:&lt;/p&gt;  &lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;DATABASE&lt;/span&gt; DemoInserts &lt;span class="kwrd"&gt;ON&lt;/span&gt; &lt;span class="kwrd"&gt;PRIMARY&lt;/span&gt; 
( 
    NAME = &lt;span class="str"&gt;&amp;#39;DemoInserts&amp;#39;&lt;/span&gt;, 
    FILENAME = &lt;span class="str"&gt;&amp;#39;c:\Databases\DemoInserts.MDF&amp;#39;&lt;/span&gt;, 
    &lt;span class="kwrd"&gt;SIZE&lt;/span&gt; = 200, 
    FILEGROWTH = 10% 
) 
LOG &lt;span class="kwrd"&gt;ON&lt;/span&gt; 
( 
    NAME = N&lt;span class="str"&gt;&amp;#39;DemoInserts_Log&amp;#39;&lt;/span&gt;, 
    FILENAME = N&lt;span class="str"&gt;&amp;#39;c:\Databases\DemoInserts_Log.LDF&amp;#39;&lt;/span&gt;, 
    &lt;span class="kwrd"&gt;SIZE&lt;/span&gt; = 200, 
    FILEGROWTH = 10% 
) 
&lt;span class="kwrd"&gt;GO&lt;/span&gt; 
&lt;span class="kwrd"&gt;ALTER&lt;/span&gt; &lt;span class="kwrd"&gt;DATABASE&lt;/span&gt; DemoInserts &lt;span class="kwrd"&gt;SET&lt;/span&gt; RECOVERY SIMPLE 
&lt;span class="kwrd"&gt;GO&lt;/span&gt; 
&lt;span class="kwrd"&gt;USE&lt;/span&gt; DemoInserts 
&lt;span class="kwrd"&gt;GO&lt;/span&gt; 
&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; [Test] 
( 
    ID UNIQUEIDENTIFIER &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt; &lt;span class="kwrd"&gt;DEFAULT&lt;/span&gt; NEWID(), 
    Payload &lt;span class="kwrd"&gt;VARCHAR&lt;/span&gt;(20) &lt;span class="kwrd"&gt;NULL&lt;/span&gt;, 
)&lt;/pre&gt;


&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;h3&gt;¡Vamos a Insertar Filas!&lt;/h3&gt;

&lt;h3&gt;&lt;/h3&gt;

&lt;p&gt;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),&amp;#160; tal como se puede ver en el siguiente fragmento de código:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SET&lt;/span&gt; NOCOUNT &lt;span class="kwrd"&gt;ON&lt;/span&gt;

&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @cont &lt;span class="kwrd"&gt;INT&lt;/span&gt; 
&lt;span class="kwrd"&gt;SET&lt;/span&gt; @cont = 0

&lt;span class="kwrd"&gt;WHILE&lt;/span&gt; (@cont &amp;lt; 500000) 
&lt;span class="kwrd"&gt;BEGIN&lt;/span&gt; 
   INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; Test(ID, Payload) &lt;span class="kwrd"&gt;VALUES&lt;/span&gt; (&lt;span class="kwrd"&gt;DEFAULT&lt;/span&gt;, REPLICATE(&lt;span class="str"&gt;&amp;#39;X&amp;#39;&lt;/span&gt;, 20)) 
   &lt;span class="kwrd"&gt;SET&lt;/span&gt; @cont = @cont + 1 
&lt;span class="kwrd"&gt;END&lt;/span&gt;&lt;/pre&gt;


&lt;p&gt;El tiempo de ejecución de la inserción en mi equipo fue de &lt;strong&gt;3 minutos y 8 segundos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTA:&lt;/strong&gt; Todos los tiempos tomados en este artículo se han calculado realizando el promedio de tres ejecuciones sucesivas.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;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 ;)&lt;/p&gt;

&lt;h3&gt;Manos a la Obra… ¿Dónde esta mi cuello de botella?&lt;/h3&gt;

&lt;p&gt;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 &lt;em&gt;Task Manager &lt;/em&gt;me permitió ver que la CPU no eran en este caso el problema:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_465CBF54.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_387A538C.png" width="444" height="323" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;‘quick and dirty’&lt;/em&gt;!:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Linq;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Text;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Threading.Tasks;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Data.SqlClient;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Diagnostics;

&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ConsoleApplication5
{
    &lt;span class="kwrd"&gt;class&lt;/span&gt; Program
    {

        &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Main(&lt;span class="kwrd"&gt;string&lt;/span&gt;[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            var mainTask = &lt;span class="kwrd"&gt;new&lt;/span&gt; TaskFactory().StartNew(() =&amp;gt;
            {
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
            });

            mainTask.ContinueWith((t) =&amp;gt;
            {
                watch.Stop();
                Console.WriteLine(watch.Elapsed);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            Console.ReadLine();
        }

        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; DoWork(&lt;span class="kwrd"&gt;int&lt;/span&gt; rows)
        {
            &lt;span class="kwrd"&gt;string&lt;/span&gt; connStr = &lt;span class="str"&gt;&amp;quot;Data Source=.;Initial Catalog=DemoInserts&lt;br /&gt;&lt;/span&gt;&lt;span class="str"&gt;;Integrated Security=true;Application Name=TestInserts&amp;quot;&lt;/span&gt;;

            &lt;span class="kwrd"&gt;int&lt;/span&gt; countIterations = 0;
            &lt;span class="kwrd"&gt;while&lt;/span&gt; (countIterations &amp;lt; rows)
            {
                &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlConnection conn = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlConnection(connStr))
                {
                    conn.Open();

                    &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlCommand cmd = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.CommandText = &lt;span class="str"&gt;&amp;quot;INSERT INTO Test(Id,Payload) VALUES (NEWID(), REPLICATE(&amp;#39;X&amp;#39;,20))&amp;quot;&lt;/span&gt;;
                        cmd.ExecuteNonQuery();
                        countIterations++;
                    }
                }
            }
        }
    }
}&lt;/pre&gt;


&lt;p&gt;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. &lt;/p&gt;

&lt;p&gt;La inserción de las dichosas 500.000 filas por este mecanismo se produjo en &lt;strong&gt;1 minuto y 7 segundos&lt;/strong&gt;; 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 &lt;em&gt;Task Manager&lt;/em&gt; 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:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_4BF78D60.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_3E44D158.png" width="444" height="323" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;em&gt; el incremento de la actividad de CPU no implica necesariamente un mejor rendimiento en las inserciones. &lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_3915A1BA.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_0977832E.png" width="404" height="245" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Atiende que graficazaaa… ¿verdad que una de estas ayuda a darle un look más profesional a cualquier artículo? ;))&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Como se puede apreciar, el &lt;em&gt;sweet spot&lt;/em&gt; de este proceso de inserción en mi equipo se encuentra en los 8 hilos, con un tiempo de ejecución de &lt;strong&gt;43 segundos&lt;/strong&gt;. 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:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_2F58783A.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_18A60426.png" width="443" height="320" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;&amp;#160;&lt;/h3&gt;

&lt;h3&gt;¿Por donde seguimos ahora?&lt;/h3&gt;

&lt;p&gt;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 &lt;a href="http://sqlserverperformance.wordpress.com/"&gt;Glenn Berry&lt;/a&gt;, personalizado por &lt;a href="http://www.sqlskills.com/BLOGS/PAUL/"&gt;Paul Randal&lt;/a&gt; para la &lt;a href="http://www.sqlskills.com/BLOGS/PAUL/post/Wait-statistics-or-please-tell-me-where-it-hurts.aspx"&gt;agregación de información de esperas por recursos en SQL Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El resultado de la prueba, si bien era de esperar, resulta revelador:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_2C233DFA.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_06B94A8C.png" width="454" height="56" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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í:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; Test(ID, Payload) &lt;span class="kwrd"&gt;VALUES&lt;/span&gt;
(NEWID(), &lt;span class="str"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;),
(NEWID(), &lt;span class="str"&gt;&amp;#39;BBB&amp;#39;&lt;/span&gt;),
...
(NEWID(), &lt;span class="str"&gt;&amp;#39;XXX&amp;#39;&lt;/span&gt;)&lt;/pre&gt;


&lt;p&gt;Modifique un poco la aplicación para realizar estas sentencias, quedando el código del siguiente modo:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Linq;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Text;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Threading.Tasks;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Data.SqlClient;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Diagnostics;

&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ConsoleApplication5
{
    &lt;span class="kwrd"&gt;class&lt;/span&gt; Program
    {

        &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Main(&lt;span class="kwrd"&gt;string&lt;/span&gt;[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            var mainTask = &lt;span class="kwrd"&gt;new&lt;/span&gt; TaskFactory().StartNew(() =&amp;gt;
            {
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
            });

            mainTask.ContinueWith((t) =&amp;gt;
            {
                watch.Stop();
                Console.WriteLine(watch.Elapsed);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            Console.ReadLine();
        }

        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; DoWork()
        {
            &lt;span class="kwrd"&gt;string&lt;/span&gt; connStr = &lt;span class="str"&gt;&amp;quot;Data Source=.;Initial Catalog=DemoInserts;Integrated Security=true;&lt;br /&gt;&lt;/span&gt;&lt;span class="str"&gt;Application Name=DemoInserts&amp;quot;&lt;/span&gt;;

            &lt;span class="kwrd"&gt;int&lt;/span&gt; countIterations = 0;
            &lt;span class="kwrd"&gt;while&lt;/span&gt; (countIterations &amp;lt; 63) &lt;span class="rem"&gt;// 63&lt;/span&gt;
            {
                &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlConnection conn = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlConnection(connStr))
                {
                    conn.Open();

                    &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlCommand cmd = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlCommand())
                    {
                        cmd.Connection = conn;

                        &lt;span class="kwrd"&gt;int&lt;/span&gt; countValues = 0;
                        var builder = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder();

                        builder.Append(&lt;span class="str"&gt;&amp;quot;INSERT INTO Test(Id,Payload) VALUES &amp;quot;&lt;/span&gt;);

                        &lt;span class="kwrd"&gt;while&lt;/span&gt; (countValues &amp;lt; 1000)
                        {
                            builder.Append(&lt;span class="str"&gt;&amp;quot;(NEWID(), REPLICATE(&amp;#39;X&amp;#39;,20)) &amp;quot;&lt;/span&gt;);

                            &lt;span class="kwrd"&gt;if&lt;/span&gt; (countValues != 999)
                                builder.Append(&lt;span class="str"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;);

                            countValues++;
                        }

                        cmd.CommandText = builder.ToString();
                        cmd.ExecuteNonQuery();
                        countIterations++;
                    }
                }
            }
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Con esta nueva versión, usando ocho hilos, baje el tiempo a… (redoble de tambor, por favor)&lt;strong&gt; 4 segundos escasos!!!! &lt;/strong&gt;&lt;/p&gt;

&lt;p align="left"&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_16E51285.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6E663070.png" width="454" height="64" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(*): Por si alguien se lo pregunta, yo tampoco tengo ni puñetera idea de por qué el hard-limit de mil filas.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;Y por hoy ha estado bien, ¿no? ;)&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;lazy_writer&lt;/strong&gt; (&lt;em&gt;ya sabéis, puede ser dentro de 3 milisegundos, o dentro de 3 años!&lt;/em&gt;), os he querido adelantar la técnica por si queréis investigarla por vosotros mismos.&lt;/p&gt;

&lt;p&gt;A modo de resumen, voy a finalizar con otra gráfica de estas elegantes y vistosas, que hoy tengo el día ejecutivo ;)&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_650CC175.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6A7B3219.png" width="404" height="244" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;&lt;font style="font-weight:normal;"&gt;&lt;/font&gt;&lt;/h5&gt;

&lt;h5&gt;&lt;font style="font-weight:normal;"&gt;Por cierto, si os preguntáis que tal lo hizo MongoDb… vais a tener que esperar a que Unai lo publique en &lt;a href="http://geeks.ms/blogs/unai/"&gt;su blog&lt;/a&gt;. No obstante, os puedo adelantar que fue capaz de bajar de estos 4 segundos….&lt;/font&gt;&lt;/h5&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;..&lt;/p&gt;

&lt;p&gt;&lt;font style="font-weight:normal;"&gt;.&lt;/font&gt;&lt;/p&gt;

&lt;p&gt;&lt;font style="font-weight:normal;"&gt;&lt;em&gt;¡¡¡¡Pero yo también!!!! Muahahahaha!!! ;)&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep Rockin’!&lt;/em&gt;&lt;/p&gt;

&lt;h5&gt;Rock Tip:&lt;/h5&gt;

&lt;p&gt;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 :(&lt;/p&gt;

&lt;p&gt;En esta ocasión me traigo a uno de mis descubrimientos del año, los geniales &lt;a href="http://en.wikipedia.org/wiki/Reckless_love"&gt;Reckless Love&lt;/a&gt;. Banda finlandesa liderada por el increíble &lt;em&gt;Olly Herman&lt;/em&gt;, quien fuera vocalista de &lt;a href="http://en.wikipedia.org/wiki/Crashd%C3%AFet"&gt;Crashdiet&lt;/a&gt; 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 &lt;em&gt;dance&lt;/em&gt; y discotequero, puedo afirmar que verles en directo es la experiencia mas cercana a ver al &lt;a href="http://en.wikipedia.org/wiki/David_Lee_Roth"&gt;David Lee Roth&lt;/a&gt; sin recurrir a un De Lorean con condensador de fluzo.&lt;/p&gt;

&lt;p&gt;El tema que he escogido es el single de su segundo (y último hasta la fecha) disco: &lt;a href="http://www.youtube.com/watch?v=LV8wPokZNig"&gt;Hot&lt;/a&gt;. 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. &lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=201723" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/MongoDB/default.aspx">MongoDB</category></item><item><title>Tell Me Where To Go: Estudio de Consultas AdHoc en SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2010/07/27/da-consultas-adhoc.aspx</link><pubDate>Tue, 27 Jul 2010 15:39:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:179905</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>6</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=179905</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/07/27/da-consultas-adhoc.aspx#comments</comments><description>&lt;p&gt;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 :)&lt;/p&gt;  &lt;p&gt;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. &lt;/p&gt;  &lt;p&gt;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; &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/05/18/these-days-un-d-237-a-en-la-vida-de-una-consulta.aspx"&gt;aquí&lt;/a&gt; podéis encontrar una breve descripción del ciclo de vida de las consultas en SQL Server, y &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/06/13/out-of-time-a-vueltas-con-los-planes-compilados-en-sql-server-2008.aspx"&gt;aquí&lt;/a&gt; podéis leer sobre la opción de optimización para cargas de trabajo AdHoc en SQL Server 2008. &lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;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.&lt;/em&gt; En esta ocasión solo me centraré en el proceso de estudio de estas consultas.&lt;/p&gt;  &lt;h4&gt;Vistazo General&lt;/h4&gt;  &lt;p&gt;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).&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_7A4F165B.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;margin-left:0px;border-left-width:0px;margin-right:0px;" title="image" border="0" alt="image" align="right" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_3D6A6EBC.png" width="137" height="244" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;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 (&lt;em&gt;Standard Reports&lt;/em&gt;), y especialmente por los definidos sobre el nodo de instancia y los definidos sobre los nodos de bases de datos específicas.&lt;/p&gt;  &lt;p&gt;En esta caso concreto, nos interesa un informe llamado &lt;strong&gt;Server Dashboard&lt;/strong&gt;, 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 &lt;em&gt;Reports –&amp;gt; Standard Reports –&amp;gt; Server Dashboard&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;&lt;i&gt;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.&lt;/i&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_037B2BD0.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6C6CAA93.png" width="534" height="366" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;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&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;A continuación os muestro, a modo de ejemplo, un pantallazo del informe en mi máquina local:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_4AA0D202.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_1D3F3C32.png" width="534" height="463" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;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 (&lt;em&gt;msdb&lt;/em&gt;, &lt;em&gt;PerfDW&lt;/em&gt;, &lt;em&gt;ReportServer&lt;/em&gt; y &lt;em&gt;db_ProcedimientosAdministrativos&lt;/em&gt;), sino con tareas AdHoc.&lt;/p&gt;  &lt;p&gt;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?&lt;/p&gt;  &lt;h4&gt;… pues va a ser que no :)&lt;/h4&gt;  &lt;p&gt;Cierto es… aunque tengamos muy poca presencia de consultas &lt;em&gt;AdHoc &lt;/em&gt;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:&lt;/p&gt;  &lt;p&gt;- ¡¿Cómo es posible?! &lt;em&gt;(…se oyen las voces desde más alla del Manzanares…)      &lt;br /&gt;&lt;/em&gt;- Das ist Unglaublich!! &lt;em&gt;(…algun avezado lector desde la puerta de Brandemburgo…)&lt;/em&gt;     &lt;br /&gt;- Cómo ye, ¡ho! &lt;em&gt;(…estremecedores palabras que oigo entre Mieres y La Moreda…)&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;¡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.&lt;/p&gt;  &lt;h4&gt;Entrando en Detalle&lt;/h4&gt;  &lt;p&gt;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: &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160; COUNT(*)       &lt;br /&gt;FROM sys.dm_exec_cached_plans       &lt;br /&gt;WHERE objtype LIKE &amp;#39;Adhoc&amp;#39; &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;En el caso concreto de este servidor, esta consulta ha devuelto &lt;strong&gt;15636&lt;/strong&gt; &lt;strong&gt;planes adhoc&lt;/strong&gt;. ¡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: &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160; COUNT(*)       &lt;br /&gt;FROM sys.dm_exec_cached_plans       &lt;br /&gt;WHERE objtype NOT LIKE &amp;#39;Adhoc&amp;#39;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;La consulta anterior nos indica que hay &lt;strong&gt;706 planes de ejecución de consultas no AdHoc&lt;/strong&gt;. Evidentemente, el ratio entre unas y otras es extremadamente preocupante, y signo evidente de que el sistema está generando constantemente consultas AdHoc. &lt;/p&gt;  &lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;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 &lt;a href="http://sqlblog.com/blogs/davide_mauri/archive/2010/07/23/viewing-how-much-memory-is-used-by-not-reused-query-plan.aspx"&gt;Davide Mauri&lt;/a&gt;, que me viene de perlas en estos casos:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;WITH Planes AS      &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Reutilizado = CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN usecounts &amp;gt; 1 THEN &amp;#39;Plan Reutilizado (Mb)&amp;#39;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE &amp;#39;Plan No Reutilizado (Mb)&amp;#39;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; size_in_bytes AS [Tamaño (Mb)],       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cacheobjtype AS TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; objtype AS Tipo       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; sys.dm_exec_cached_plans       &lt;br /&gt;),       &lt;br /&gt;Agregado AS       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Reutilizado,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Tipo,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; [Tamaño (Mb)] = SUM([Tamaño (Mb)] / 1024. / 1024.)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Planes       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GROUP BY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Reutilizado,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Tipo       &lt;br /&gt;),       &lt;br /&gt;Pivote AS       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Agregado a       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PIVOT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ( SUM([Tamaño (Mb)]) FOR Reutilizado IN ([Plan Reutilizado (Mb)], [Plan No Reutilizado (Mb)]) ) p       &lt;br /&gt;)       &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Tipo,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Tamaño Reutilizado (Mb)] = SUM([Plan Reutilizado (Mb)]),       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Tamaño No Reutilizado (Mb)] = SUM([Plan No Reutilizado (Mb)])       &lt;br /&gt;FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Pivote       &lt;br /&gt;GROUP BY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Tipo,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH ROLLUP       &lt;br /&gt;HAVING       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; (Tipo IS NULL AND TipoObjetoCache IS NULL ) OR       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; (Tipo IS NOT NULL AND TipoObjetoCache IS NOT NULL )&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Volviendo al caso de mi cliente, la ejecución de esta consulta devolvió los siguientes resultados:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_32497988.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_09CA9774.png" width="534" height="229" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;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. &lt;/p&gt;  &lt;p&gt;Después de comprobar que realmente estamos sufriendo el problema ¿cual sería mi siguiente paso? Pues el más divertido de todos… &lt;em&gt;¡buscar al culpable!&lt;/em&gt; ;)&lt;/p&gt;  &lt;p&gt;Para ello, buscaría las consultas que aparecen en la cache, de tipo AdHoc, y cruzaría la vista con la funcion &lt;em&gt;sys.dm_exec_sql_text&lt;/em&gt; para poder sacar el texto de la SQL a partir del handle:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160; refcounts,       &lt;br /&gt;&amp;#160;&amp;#160; size_in_bytes,       &lt;br /&gt;&amp;#160;&amp;#160; cacheobjtype,       &lt;br /&gt;&amp;#160;&amp;#160; text       &lt;br /&gt;FROM       &lt;br /&gt;&amp;#160;&amp;#160; sys.dm_exec_cached_plans       &lt;br /&gt;&amp;#160;&amp;#160; CROSS APPLY sys.dm_exec_sql_text(plan_handle)       &lt;br /&gt;WHERE objtype LIKE &amp;#39;Adhoc&amp;#39;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;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):&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_74CBB961.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6DCC06DC.png" width="534" height="212" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;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). &lt;/p&gt;  &lt;p&gt;¡Ya tenemos nuestros culpables! ¡¡Yeah!!&lt;/p&gt;  &lt;h4&gt;Resumiendo&lt;/h4&gt;  &lt;p&gt;A modo de resumen, yo abordaría el estudio del siguiente modo:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Revisión del informe&lt;em&gt; &lt;strong&gt;Server Dashboard&lt;/strong&gt;&lt;/em&gt; para buscar presencia de consultas &lt;em&gt;AdHoc&lt;/em&gt; &lt;/li&gt;    &lt;li&gt;Consulta sobre &lt;em&gt;&lt;strong&gt;sys.dm_exec_cached_plans&lt;/strong&gt;&lt;/em&gt; para ver el ratio &lt;em&gt;AdHoc/No AdHoc&lt;/em&gt; &lt;/li&gt;    &lt;li&gt;Consulta para ver la memoria fugada en planes no reutilizados &lt;/li&gt;    &lt;li&gt;Si procede, revisar las consultas problematicas. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Fácil, sencillo y para toda la familia :) &lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;AdHoc&lt;/em&gt; en nuestro sistema. Si fuera así, habrá que comprobar la presencia de consultas dinámicas sin preparar, consultas sin parametrizar desde el cliente, etc.&lt;/p&gt;  &lt;p&gt;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!&lt;/p&gt;  &lt;p&gt;&lt;b&gt;Rock Tip:&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;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 &lt;i&gt;glam-metaleros&lt;/i&gt; noruegos &lt;a href="http://en.wikipedia.org/wiki/Wig_Wam"&gt;Wig Wam&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Representantes de su país en el festival de Eurovisión de 2005, con el temazo ‘&lt;a href="http://www.youtube.com/watch?v=0XIyCKgA8z8"&gt;In My Dreams&lt;/a&gt;’, en esta ocasión nos quedamos con su balada ‘&lt;a href="http://www.youtube.com/watch?v=f-csCNH5Ds8"&gt;Tell Me Where To Go&lt;/a&gt;’ para acompañar este post. &lt;/p&gt;  &lt;p&gt;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 &lt;a href="http://en.wikipedia.org/wiki/Steel_Panther"&gt;Steel Panther&lt;/a&gt; y seré un hombre plenamente feliz :)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=179905" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Pinball Map: Cuidado con los Mapeos en NHibernate</title><link>http://geeks.ms/blogs/palvarez/archive/2010/06/08/pinball-map-cuidado-con-los-mapeos-en-nhibernate.aspx</link><pubDate>Tue, 08 Jun 2010 11:25:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:177786</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>31</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=177786</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/06/08/pinball-map-cuidado-con-los-mapeos-en-nhibernate.aspx#comments</comments><description>&lt;p&gt;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! ;)&lt;/p&gt;  &lt;p&gt;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:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;EXEC sp_executesql N&amp;#39;     &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160; this_.ID as ID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.BorradoLogico as BorradoL2_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Descripcion as Descripc3_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Estado as Estado90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VisibleWeb as VisibleWeb90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 as CodigoDB6_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaPrevistaInicioComercializacion as FechaPre7_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaFinComercializacion as FechaFin8_90_0_,&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; this_.Comentarios as Comentar9_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFoto as URLFoto90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Orden as Orden90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFotoMiniatura as URLFoto12_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID as SufijoID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.MarcaID as MarcaID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VersionId as VersionId90_0_       &lt;br /&gt;FROM&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; VistaProblematica this_       &lt;br /&gt;WHERE       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 in (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9) and       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID in (@p10, @p11, @p12, @p13, @p14) and this_.BorradoLogico = @p15&amp;#39;,      &lt;br /&gt;N&amp;#39;@p0 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p1 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p2 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p3 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p4 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p5 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p6 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p7 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p8 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p9 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p10 bigint,@p11 bigint,@p12 bigint,@p13 bigint,@p14 bigint,@p15 bit&amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p0 = N&amp;#39;040&amp;#160; &amp;#39;, @p1 = N&amp;#39;1F7&amp;#160; &amp;#39;, @p2 = N&amp;#39;1G3&amp;#160; &amp;#39;, @p3 = N&amp;#39;209&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p4 = N&amp;#39;3R3&amp;#160; &amp;#39;, @p5 = N&amp;#39;8S1&amp;#160; &amp;#39;, @p6 = N&amp;#39;8S6&amp;#160; &amp;#39;, @p7 = N&amp;#39;8T4&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p8 = N&amp;#39;8T8&amp;#160; &amp;#39;, @p9 = N&amp;#39;9AK&amp;#160; &amp;#39;, @p10 = 1819, @p11 = 1820, @p12 = 1821,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p13 = 1822, @p14 = 3614, @p15 = 0&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;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 :)&lt;/p&gt;  &lt;p&gt;Procedimos a comprobar los tiempos de esta consulta con SET STATISTICS TIME ON:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="cons"&gt;SQL Server Execution Times:     &lt;br /&gt;&amp;#160;&amp;#160; CPU time = &lt;font color="#ff0000"&gt;703 ms&lt;/font&gt;,&amp;#160; elapsed time = &lt;font color="#ff0000"&gt;796 ms&lt;/font&gt;&lt;/font&gt;.&lt;/p&gt;  &lt;p&gt;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:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="consoles"&gt;(50 row(s) affected)     &lt;br /&gt;&lt;font color="#ff0000"&gt;Table &amp;#39;Worktable&amp;#39;. 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.&lt;/font&gt;      &lt;br /&gt;Table &amp;#39;Versiones&amp;#39;. 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.      &lt;br /&gt;Table &amp;#39;Modelos&amp;#39;. 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.      &lt;br /&gt;Table &amp;#39;Sufijos&amp;#39;. 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.      &lt;br /&gt;Table &amp;#39;Colores&amp;#39;. 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.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;worktables&lt;/em&gt;…&lt;/p&gt;  &lt;h5&gt;¿¡Worktables?! ¡¿Que mi madre es eso?!&lt;/h5&gt;  &lt;p&gt;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 :)&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;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. &lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;h5&gt;Volviendo al Turrón:&lt;/h5&gt;  &lt;p&gt;A la vista de la actividad de E/S en Worktables, decidí confirmar mis sospechas, por lo que adjunté una traza de profiler:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/profiler_5F00_70E247EA.png"&gt;&lt;img style="border-bottom:0px;border-left:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;" title="profiler" border="0" alt="profiler" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/profiler_5F00_thumb_5F00_75948A3F.png" width="540" height="282" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;¡Bingo! Podemos comprobar que aparece un evento de &lt;em&gt;Hash Warning&lt;/em&gt; al ejecutar la consulta. &lt;/p&gt;  &lt;p&gt;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. &lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;sp_executesql. &lt;/em&gt;&lt;/p&gt;  &lt;p&gt;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(**) :)&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/Definicion_5F00_1677D71A.png"&gt;&lt;img style="border-bottom:0px;border-left:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;" title="Definicion" border="0" alt="Definicion" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/Definicion_5F00_thumb_5F00_7BCB3B00.png" width="373" height="321" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Como podéis ver, la columna CodigoDB2 está definida como &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;, pero en el código de la consulta que genera NHibernate nos pasa el parámetro como &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;, 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.&lt;/p&gt;  &lt;p&gt;Hicimos la prueba, cambiando los tipos de datos sobre la consulta generada por NHibernate en el &lt;em&gt;sp_executesql&lt;/em&gt;, quedando la misma así:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;EXEC sp_executesql N&amp;#39;     &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160; this_.ID as ID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.BorradoLogico as BorradoL2_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Descripcion as Descripc3_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Estado as Estado90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VisibleWeb as VisibleWeb90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 as CodigoDB6_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaPrevistaInicioComercializacion as FechaPre7_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaFinComercializacion as FechaFin8_90_0_,&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; this_.Comentarios as Comentar9_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFoto as URLFoto90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Orden as Orden90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFotoMiniatura as URLFoto12_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID as SufijoID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.MarcaID as MarcaID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VersionId as VersionId90_0_       &lt;br /&gt;FROM       &lt;br /&gt;&amp;#160;&amp;#160; VistaProblematica this_       &lt;br /&gt;WHERE       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 in (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9) and       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID in (@p10, @p11, @p12, @p13, @p14) and this_.BorradoLogico = @p15&amp;#39;,      &lt;br /&gt;N&amp;#39;@p0 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p1 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p2 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p3 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p4 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p5 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p6 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p7 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p8 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p9 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p10 bigint,@p11 bigint,@p12 bigint,@p13 bigint,@p14 bigint,@p15 bit&amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p0 = N&amp;#39;040&amp;#160; &amp;#39;, @p1 = N&amp;#39;1F7&amp;#160; &amp;#39;, @p2 = N&amp;#39;1G3&amp;#160; &amp;#39;, @p3 = N&amp;#39;209&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p4 = N&amp;#39;3R3&amp;#160; &amp;#39;, @p5 = N&amp;#39;8S1&amp;#160; &amp;#39;, @p6 = N&amp;#39;8S6&amp;#160; &amp;#39;, @p7 = N&amp;#39;8T4&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p8 = N&amp;#39;8T8&amp;#160; &amp;#39;, @p9 = N&amp;#39;9AK&amp;#160; &amp;#39;, @p10 = 1819, @p11 = 1820, @p12 = 1821,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p13 = 1822, @p14 = 3614, @p15 = 0&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;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:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;SQL Server Execution Times:     &lt;br /&gt;&amp;#160;&amp;#160; CPU time = &lt;font color="#ff0000"&gt;78 ms&lt;/font&gt;,&amp;#160; elapsed time = &lt;font color="#ff0000"&gt;78 ms&lt;/font&gt;.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;(50 row(s) affected)     &lt;br /&gt;&lt;font color="#ff0000"&gt;Table &amp;#39;Worktable&amp;#39;. 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.       &lt;br /&gt;&lt;/font&gt;Table &amp;#39;Colores&amp;#39;. 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.      &lt;br /&gt;Table &amp;#39;Sufijos&amp;#39;. 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.      &lt;br /&gt;Table &amp;#39;Versiones&amp;#39;. 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.      &lt;br /&gt;Table &amp;#39;Modelos&amp;#39;. 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.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;¡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.&lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;h5&gt;Conclusiones:&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;Es muy importante garantizar la corrección de los tipos de datos en los mapeos de cualquier solución de persistencia.&lt;/li&gt;    &lt;li&gt;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!&lt;/li&gt;    &lt;li&gt;Estaba claro que si yo iba a hablar acerca de NHibernate, no iba a ser enteramente para bien… ;)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Keep Rockin’!&lt;/p&gt; &lt;em&gt;(*): Soy perfectamente consciente de lo mal que puede llegar a sonar esa frase!    &lt;br /&gt;(**): Tengo que dejarlo claro: el modelo de datos NO es mío!!! :)&lt;/em&gt;  &lt;h5&gt;Rock Tip:&lt;/h5&gt;  &lt;p&gt;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 ‘&lt;a href="http://en.wikipedia.org/wiki/In_Flames"&gt;In Flames&lt;/a&gt;’, grupo fundado y liderado hasta hace poco por ese genio que es Jesper Strömblad.&lt;/p&gt;  &lt;p&gt;Cuando estaba escribiendo este post sobre el mapeo en Hibernate, no pude evitar que se me viniera a la cabeza su tema ‘&lt;a href="http://www.youtube.com/watch?v=wCvq8VN9owY"&gt;Pinball Map&lt;/a&gt;’; 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! ;)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=177786" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Rainbow In The Dark: Materiales de la sesión de Optimización de SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2010/06/01/rainbow-in-the-dark-materiales-de-la-sesi-243-n-de-optimizaci-243-n-de-sql-server.aspx</link><pubDate>Tue, 01 Jun 2010 18:13:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:177557</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=177557</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/06/01/rainbow-in-the-dark-materiales-de-la-sesi-243-n-de-optimizaci-243-n-de-sql-server.aspx#comments</comments><description>&lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;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 (&lt;em&gt;sys.dm_os_wait_stats&lt;/em&gt;) y aproveché para contar un par de batallitas sobre optimizaciones extremas mediante la eliminación de CPUs :)&lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;parameter sniffing&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;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 ;) &lt;/p&gt;  &lt;p&gt;Aprovecho para dejaros &lt;a href="http://geeks.ms/media/p/177541.aspx"&gt;aquí&lt;/a&gt; los materiales de la sesión, tanto las PPTs como los scripts que utilicé durante la misma.&lt;/p&gt;  &lt;p&gt;Gracias a todos los asistentes (muy buenas preguntas!) y por supuesto, a la organización de Mad.Nug :) &lt;/p&gt;  &lt;p&gt;Keep Rockin’!&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;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…&lt;/p&gt;  &lt;p&gt;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 &lt;em&gt;mano cornuta&lt;/em&gt; (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. &lt;/p&gt;  &lt;p&gt;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.&lt;/p&gt;  &lt;p&gt;Aquí os dejo una versión del “&lt;a href="http://www.youtube.com/watch?v=1Sxc25uWqI8&amp;amp;feature=related"&gt;Rainbow In The Dark&lt;/a&gt;” en directo en el Wacken de 2004, donde podemos ver que el gran &lt;a href="http://es.wikipedia.org/wiki/Ronnie_James_Dio"&gt;Ronnie James Dio&lt;/a&gt; seguía estando como un chaval y disfrutando como si aún estuviera en Black Sabbath allá por 1979!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=177557" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Eventos/default.aspx">Eventos</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Bang Bang!: Optimización Económica en SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2010/05/25/bang-bang-optimizaci-243-n-econ-243-mica-en-sql-server.aspx</link><pubDate>Tue, 25 May 2010 20:41:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:177282</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>4</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=177282</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/05/25/bang-bang-optimizaci-243-n-econ-243-mica-en-sql-server.aspx#comments</comments><description>&lt;p&gt;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 :)&lt;/p&gt;  &lt;p&gt;Esta vez tengo que agradecer a &lt;a href="http://geeks.ms/blogs/gtorres/default.aspx"&gt;Gisela&lt;/a&gt;, &lt;a href="http://geeks.ms/blogs/lfraile/"&gt;Luis&lt;/a&gt; y el resto de chicos de Madrid.NET la oportunidad de pasar un ratito &lt;strike&gt;friki&lt;/strike&gt; serio, formal y productivo hablando de optimización &lt;em&gt;económica &lt;/em&gt;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 &lt;em&gt;duration &lt;/em&gt;elevado de modo sistemático… vamos, la venerable metodología ASM (A Salto deMata(tm)).&lt;/p&gt;  &lt;p&gt;La primera parte del evento abordará las técnicas de revision que yo suelo emplear en mi trabajo en el equipo &lt;strong&gt;DOT&lt;/strong&gt; 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 :)&lt;/p&gt;  &lt;p&gt;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:&lt;/p&gt;  &lt;p&gt;&lt;a href="https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032451680&amp;amp;Culture=es-ES"&gt;https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032451680&amp;amp;Culture=es-ES&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Keep rockin’!&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Rock Tip: &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;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:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;El nombre es apropiado… vamos a hacer nuestro el lema de ’&lt;em&gt;Biggest Bang for the Buck&lt;/em&gt;’, así que el &lt;em&gt;Bang Bang!&lt;/em&gt; nos viene que ni pintado… &lt;/li&gt;    &lt;li&gt;Estamos hablando de la que posiblemente sea la mejor canción del mejor grupo de la historia. Punto pelota. xD &lt;/li&gt;    &lt;li&gt;El blog es mio y…. mmmm, creo que recurro con demasiada frecuencia a este punto :) &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Por todo esto, hoy le ha tocado el turno a &lt;a href="http://www.youtube.com/watch?v=doE883HTXpo"&gt;Bang Bang&lt;/a&gt;, un tema del primer disco de &lt;a href="http://en.wikipedia.org/wiki/Danger_Danger"&gt;Danger Danger&lt;/a&gt;. 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 :)&lt;/p&gt;  &lt;p&gt;Si os interesa la musica de guitarristas para guitarristas, principalmente instrumental, no os perdais los trabajos en solitario de su guitarrista, &lt;a href="http://www.andytimmons.com/"&gt;Andy Timmons&lt;/a&gt;, 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: &lt;a href="http://www.youtube.com/watch?v=KaVVZYOvMFY"&gt;Cry for You&lt;/a&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=177282" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Eventos/default.aspx">Eventos</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>The Hunter: Cazando bloqueos e interbloqueos</title><link>http://geeks.ms/blogs/palvarez/archive/2009/02/07/the-hunter-cazando-bloqueos-e-interbloqueos.aspx</link><pubDate>Sat, 07 Feb 2009 16:53:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:142225</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>11</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=142225</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2009/02/07/the-hunter-cazando-bloqueos-e-interbloqueos.aspx#comments</comments><description>&lt;p&gt;Después de una semana bastante dura, he podido sacar un rato para escribiros sobre un tema realmente interesante y con mucha relevancia desde el punto de vista del rendimiento y estabilidad de nuestras aplicaciones: la correcta utilización de las diversas primitivas de sincronización en nuestro código.&lt;/p&gt;  &lt;p&gt;Bueno… en realidad os estoy engañando un poco ;) Hoy me voy a centrar en el viejo y venerable &lt;strong&gt;lock&lt;/strong&gt;, y en algunas peculiaridades suyas. Quizá algún día escriba mas entradas sobre otras primitivas de sincronización, o quizá no… por ello, y si os interesan estos aspectos de la programación concurrente, permitidme que os recomiende &lt;a href="http://www.amazon.com/Concurrent-Programming-Windows-Microsoft-Development/dp/032143482X"&gt;el excelente libro de Joe Duffy&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;Me vais a permitir que emplee como excusa para introducir el tema un escenario que me encontré esta semana en un cliente nuestro….&lt;/p&gt;  &lt;h4&gt;El Problema&lt;/h4&gt;  &lt;p&gt;Este cliente solicito nuestros servicios por problemas masivos de estabilidad en sus aplicación, concretamente en los servicios WCF que atacan los clientes. Durante la investigación comprobamos que teníamos muchos frentes abiertos: pérdidas de memoria, problemas con el ODP de Oracle, etc. Pero uno de los principales problemas se manifestaban a través de bloqueos masivos, que provocaban que la aplicación dejara de dar servicio durante unos minutos, a pesar de hallarse en una situación de bajo uso de CPU.&lt;/p&gt;  &lt;p&gt;Procedimos a capturar un volcado de memoria del proceso &lt;strong&gt;w3wp&lt;/strong&gt; asociado al servicio que mostraba el problema de rendimiento, a través de &lt;strong&gt;adplus.vbs&lt;/strong&gt;, y se pudo comprobar rápidamente que había hilos bloqueados. &lt;/p&gt;  &lt;p&gt;Empecé pidiéndole a tándem &lt;em&gt;WinDbg + SOS.dll&lt;/em&gt; que me mostraran un volcado de todos los hilos administrados (.NET) de la aplicación en el momento de la captura del volcado, con el comando &lt;strong&gt;~*e !ClrStack&lt;/strong&gt;, y pude comprobar que había múltiples hilos con el mismo call stack:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Courier New"&gt;OS Thread Id: 0x82c (81)      &lt;br /&gt;Child-SP&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; RetAddr&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Call Site       &lt;br /&gt;&lt;strong&gt;&lt;font color="#ff0000"&gt;0000000005a0edf0 00000642801b10c3 X.Sesion.SesionServidor.setValorConexion(System.String)&lt;/font&gt;&lt;/strong&gt; 0000000005a0ee40 00000642801b0fcd X.CredencialServidor.asignarIdSesion(System.String)       &lt;br /&gt;0000000005a0ee70 00000642801b0de1 X.SessionInfoInterceptor.OnReceive(System.ServiceModel.Channels.Message ByRef)       &lt;br /&gt;0000000005a0eeb0 00000642801b0d03 X.InspectingChannelBase`1[[System.__Canon, mscorlib]].OnReceive(System.ServiceModel.Channels.Message ByRef)       &lt;br /&gt;&lt;em&gt;[ – Corto aqui para ahorrar espacio -- ] &lt;/em&gt;      &lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;0000000005a0f480 000006427f67d4a2 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32, UInt32, System.Threading.NativeOverlapped*)&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Mucha casualidad sería que en el momento de capturar el volcado de memoria realmente 11 hilos estuvieran ejecutando el mismo método - &lt;strong&gt;setValorConexion&lt;/strong&gt; - por lo que la intuición que teníamos acerca de un problema de bloqueos iba materializándose.&lt;/p&gt;  &lt;p&gt;Hicimos una rápida comprobación mediante el comando&lt;strong&gt; !syncblk&lt;/strong&gt; de las&lt;em&gt; sos.dll&lt;/em&gt;, que nos revelo la presencia de un bloqueo:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;0:000&amp;gt; !syncblk     &lt;br /&gt;&lt;/strong&gt;Index&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SyncBlock MonitorHeld Recursion Owning Thread Info&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; SyncBlock Owner    &lt;br /&gt;&amp;#160;&amp;#160; &lt;strong&gt;&lt;font color="#ff0000"&gt;40 00000000001a3a60&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 13&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 1 000000000d99a370&amp;#160;&amp;#160; 748&amp;#160; 65&amp;#160;&amp;#160; 0000000160058648 System.RuntimeType       &lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;-----------------------------    &lt;br /&gt;Total&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 901    &lt;br /&gt;CCW&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 2    &lt;br /&gt;RCW&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 54    &lt;br /&gt;ComClassFactory 0    &lt;br /&gt;Free&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 787&lt;/p&gt;  &lt;p&gt;La línea marcada en rojo nos indica que tenemos un bloqueo sobre un objeto de tipo System.RuntimeType y que el padre del bloqueo es el hilo 65. Comprobamos a continuación los detalles del objeto de sincronización del bloqueo:&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;0:000&amp;gt; !do 0000000160058648&lt;/strong&gt;     &lt;br /&gt;Name: System.RuntimeType    &lt;br /&gt;MethodTable: 00000642788c5790    &lt;br /&gt;EEClass: 00000642788c56d0    &lt;br /&gt;Size: 40(0x28) bytes    &lt;br /&gt;Type MethodTable: 000006428023ef40    &lt;br /&gt;Type Name: &lt;font color="#ff0000"&gt;&lt;strong&gt;X.Comun.Seguridad.Sesion.SesionServidor&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Vemos que, aparentemente, la intención del desarrollador de la aplicación era escribir un bloqueo sobre una parte del código que afectara solo a la sesión actual, pero ¿no os llama algo la atención?&lt;/p&gt;  &lt;p&gt;Fijaros en que el bloqueo no se obtuvo sobre un objeto de tipo &lt;strong&gt;X.Comun.Seguridad.Sesion.SesionServidor&lt;/strong&gt;, sino sobre un &lt;strong&gt;System.RuntimeType&lt;/strong&gt;. Si pensamos a nivel de código C#, lo más posible es que le usuario haya introducido el siguiente código para obtener el bloqueo:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;lock (&lt;strong&gt;typeof(SesionServidor)&lt;/strong&gt;)      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160; // Hacer algo...      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;O bien algo como lo siguiente:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;lock (&lt;strong&gt;this.GetType()&lt;/strong&gt;)      &lt;br /&gt;{      &lt;br /&gt;&amp;#160;&amp;#160; // Hacer algo...      &lt;br /&gt;}&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Esto explica el porque vemos un bloque sobre un &lt;em&gt;System.RuntimeType&lt;/em&gt;, y más importante aún… explica la razón de los bloqueos. Pero antes de desvelar el misterio, es hora de….&lt;/p&gt;  &lt;h4&gt;Un poquito de teoría&lt;/h4&gt;  &lt;p&gt;&lt;em&gt;DISCLAIMER: No puedo evitarlo, me gustan los internals y el saber el porque de las cosas ;) Aun así, entiendo que no todo el mundo es igual, por lo que si quieres saltarte esta parte lo entenderé perfectamente. Lo que si te recomendaría es que te leyeras las conclusiones, ya que ahí resumo las recomendaciones mas importantes que se derivan de lo que voy a explicar aquí.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;El establecer estos bloqueos sobre un objeto de tipo &lt;i&gt;System.RuntimeType&lt;/i&gt; o &lt;i&gt;System.Type&lt;/i&gt; debe ser considerado, no solo como una mala practica, sino directamente como un bug. Hay dos motivos principales por los que esto se puede considerar un bug de consideración:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Al emplear un objeto publico como objeto de sincronización, la aplicación se está exponiendo a que cualquier usuario o desarrollador emplee ese mismo tipo para adquirir un bloqueo, provocando en ese momento el interbloqueo.     &lt;br /&gt;&lt;/li&gt;    &lt;li&gt;Los tipos &lt;i&gt;System.RuntimeType&lt;/i&gt; y &lt;i&gt;System.Type&lt;/i&gt; son tipos &lt;i&gt;Marshall-By-Bleed&lt;/i&gt;, lo que quiere decir que se comparten incluso a través de la frontera de los &lt;em&gt;AppDomains&lt;/em&gt;. Si cualquier otro hilo, en cualquier otro proceso del sistema (ya sea desarrollado por nosotros o por terceros) adquiere un bloqueo sobre &lt;strong&gt;cualquier&lt;/strong&gt; tipo a través de un typeof() o de un GetType(), se podrá producir esta situación de interbloqueo.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;El segundo punto es particularmente importante, ya que implica que las posibilidades de que ocurra un interbloqueo son mucho más elevadas, al tener que tomar en consideración los bloqueos que todas las aplicaciones administradas que se estén ejecutando en la máquina vayan a tomar, hayan sido desarrolladas por nosotros o no. Además, su localización y depuración va a resultar extremadamente costosa, debido a que algunos participantes del bloqueo podrían no aparecer en los volcados de memoria, al estar en otro proceso diferente.&lt;/p&gt;  &lt;h4&gt;Volviendo a nuestra Historia&lt;/h4&gt;  &lt;p&gt;Cuando comprobamos que había un bloqueo sobre un &lt;em&gt;System.RuntimeType&lt;/em&gt;, decidimos hacer una revisión sobre el código de nuestro cliente, y pudimos comprobar como había cientos de ocurrencias, literalmente, de bloqueos sobre &lt;strong&gt;typeof()&lt;/strong&gt; y &lt;strong&gt;GetType()&lt;/strong&gt;. &lt;/p&gt;  &lt;p&gt;Evidentemente esto incrementa las posibilidades de interbloqueo de una manera salvaje, y sobre todo, no controlada, ya que nos pone a merced de otros desarrollos que se ejecuten en el mismo servidor. &lt;/p&gt;  &lt;p&gt;¿Cual es la solución? Pues generalmente es revertir al viejo y clásico mecanismo de bloquear sobre un &lt;em&gt;object&lt;/em&gt;, que sea un atributo de clase privado y estático para hacerlo robusto a hilos. Es el viejo sistema, si… y el que deberemos emplear en el 99,9% de los casos. :)&lt;/p&gt;  &lt;h4&gt;Conclusiones&lt;/h4&gt;  &lt;p&gt;Me gustaría terminar con una serie de recomendaciones:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Nunca bloquees por un objeto que no sea estático:&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;Eso solo serviría para proteger los miembros de esa instancia concreta, y rara vez es esto lo que deseamos con un lock.&lt;/p&gt; &lt;/blockquote&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Nunca hagas un lock sobre un objeto publico:&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;Como se explico previamente, el lock sobre el objeto publico significa que otra parte de la aplicación puede hacer un lock sobre el mismo objeto y ocasionar un interbloqueo.&lt;/p&gt; &lt;/blockquote&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Sobre todo, la mas importante de todas… Nunca hagas un lock sobre un System.RuntimeType o System.Type:&lt;/strong&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;Ya sabéis, si leísteis la parte teórica, que estos tipos son &lt;em&gt;Marshal-by-bleed&lt;/em&gt;, lo que significa que se comparten entre dominios de aplicación diferentes. El riesgo de interbloqueo es enorme, y no puedo pensar en un solo escenario en que tenga sentido hacer ese bloqueo. &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;NOTA: Evidentemente, todos mis &lt;em&gt;nunca &lt;/em&gt;son matizables xDD&lt;/p&gt;  &lt;p&gt;NOTA 2: Os dejo, que me voy a ver a &lt;a href="http://es.wikipedia.org/wiki/Medina_Azahara_(grupo)"&gt;Medina Azahara&lt;/a&gt; :P&lt;/p&gt;  &lt;h5&gt;Rock Tip:&lt;/h5&gt;  &lt;p&gt;El Rock Tip de esta entrada le corresponde a &lt;a href="http://www.youtube.com/watch?v=cwdsoXXH2S0&amp;amp;feature=related"&gt;The Hunter&lt;/a&gt;, un grandísimo tema de &lt;a href="http://es.wikipedia.org/wiki/Dokken"&gt;Dokken&lt;/a&gt;, grupo que, si la memoria no me traiciona, ¡nunca había aparecido en mi blog! Para intentar resarcirme de esta afrenta a los dioses del metal he decidido incluirlos en esta entrada por los siguientes motivos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;em&gt;The Hunter&lt;/em&gt; me pareció una metáfora bastante acertada al trabajo realizado para localizar los problemas de interbloqueos, y para el trabajo del DOT en general :) &lt;/li&gt;    &lt;li&gt;El disco (perdón, ¡discazo!) en el que este tema vio la luz se llama &lt;a href="http://en.wikipedia.org/wiki/Under_Lock_And_Key"&gt;Under Lock and Key&lt;/a&gt;…. &lt;strong&gt;under lock&lt;/strong&gt;…. ehh.. bueno, dejadlo, me hizo gracia en su momento xD &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Lo dicho, grupo muy recomendable; tremenda pareja hacían el vocalista Don Dokken y el guitarrista George Lynch. No os perdáis temas como &lt;a href="http://www.youtube.com/watch?v=dmt6RaKu8aE&amp;amp;feature=related"&gt;‘In my dreams’&lt;/a&gt;, &lt;a href="http://www.youtube.com/watch?v=EqBGVD9gtds"&gt;‘Alone Again’&lt;/a&gt; o &lt;a href="http://www.youtube.com/watch?v=0UOL98v1z_8&amp;amp;feature=related"&gt;‘Dream Warriors’&lt;/a&gt;, que sin duda están entre lo mejorcito que nos trajo el hard rock/AOR ochentero americano.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=142225" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/WinDbg/default.aspx">WinDbg</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/DOT/default.aspx">DOT</category></item><item><title>Children of the Damned: Cuidado con vuestras aplicaciones MDI</title><link>http://geeks.ms/blogs/palvarez/archive/2009/02/02/children-of-the-damned-cuidado-con-vuestras-aplicaciones-mdi.aspx</link><pubDate>Mon, 02 Feb 2009 17:25:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:141764</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>24</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=141764</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2009/02/02/children-of-the-damned-cuidado-con-vuestras-aplicaciones-mdi.aspx#comments</comments><description>&lt;p&gt;A pesar del titulo de esta entrada, no voy a entrar a discutir si las aplicaciones con interfaz MDI son usables o no, si son feas como el demonio o si le dan un bonito toque retro a nuestras aplicaciones. No, eso se lo dejo a nuestros chicos de UX. &lt;/p&gt;  &lt;p&gt;Yo me pondré el uniforme del DOT, ya que os voy a hablar de un escenario que nos encontramos recientemente en un cliente nuestro y que reunió, en único caso, los tres ingredientes que toda buena sesión de depuración y optimización deben de tener: problemas de gestión de memoria, un bug &lt;em&gt;escandaloso&lt;/em&gt; y grandes dosis de cafeína :)&lt;/p&gt;  &lt;p&gt;Al turrón…&lt;/p&gt;  &lt;h4&gt;El Problema&lt;/h4&gt;  &lt;p&gt;Nuestro cliente requirió nuestros servicios de optimización para optimizar una aplicación que iba a pasar a producción en menos de un mes. Como os podéis imaginar éste requisito supone todo un reto, ya que nos podemos ir olvidando de las mejores recomendaciones a nivel de rendimiento – las que implican cambios de arquitectura – y nos obliga a centrarnos en la búsqueda de rendimiento línea a línea, byte a byte. Divertidísimo, seguro, pero no tanto cuando solo tienes una semana, ¡y las máquinas cliente que van a ejecutar la aplicación cuentan con 512Mb en el mejor de los casos!&lt;/p&gt;  &lt;p&gt;Bien, con estas premisas os podéis imaginar que uno de nuestros objetivos principales fue estudiar el consumo de memoria de la aplicación, monitorizando los aspectos básicos como el número de recolecciones de basura en cada generación, la memoria total en el Managed Heap, y la valiosísima información mostrada por el comando &lt;strong&gt;!DumpHeap –stat&lt;/strong&gt; de las SOS.dll para comprobar el histograma de distribución de Tipos en el Managed Heap. También agregamos a nuestro arsenal el &lt;a href="http://memprofiler.com/"&gt;.NET Memory Profiler&lt;/a&gt;, un producto que, a pesar de ser poco más que un maquillaje sobre el venerable &lt;a href="http://www.microsoft.com/downloads/details.aspx?familyid=a362781c-3870-43be-8926-862b40aa0cd0&amp;amp;displaylang=en"&gt;CLR Profiler&lt;/a&gt; en muchos aspectos, me ha sorprendido muy gratamente por algunas de sus funcionalidades, su facilidad de uso y de lectura de datos. En breves espero hacer una pequeña reseña del mismo, pero os invito a que os descarguéis la versión de evaluación y empecéis a jugar con ella.&lt;/p&gt;  &lt;p&gt;Nuestras primeras investigaciones arrojaron unos números elevadísimos de recolecciones de basura, mucha fragmentación de memoria y, en definitiva, una gran cantidad de indicaciones de que la aplicación estaba sufriendo una gran presión de memoria. Modificamos la aplicación levemente para agregarle un botón que invocaba a &lt;strong&gt;GC.Collect()&lt;/strong&gt; y &lt;strong&gt;GC.WaitForPendingFinalizers()&lt;/strong&gt;, y lanzamos una serie de pruebas monitorizando como se comportaba la memoria y si estábamos fugando en algún sitio.&lt;/p&gt;  &lt;p&gt;… y descubrimos algo interesante ;) La interfaz de la aplicación consistía en una ventana MDI padre, que instanciaba una ventana MDI hija para cada tipo de operativa de la aplicación. Estas ventanas tenían un coste masivo en memoria, pues contenían gran cantidad de DataSets, DataTables, etc. pudiendo alcanzar los 60Mb en memoria, pero el cliente confiaba en que al cerrar la ventana esta memoria se eliminara. Hicimos una prueba muy sencilla, por el método clásico ;)&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Abrimos la aplicación y comprobamos el estado de la memoria en vacio. &lt;/li&gt;    &lt;li&gt;Abrimos un formulario, trabajamos un poquito con el. &lt;/li&gt;    &lt;li&gt;Cerramos el formulario. &lt;/li&gt;    &lt;li&gt;Pulsamos el botón mágico que invoca al GC y monitorizamos el estado de la memoria. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Al fin de la prueba pudimos comprobar como la memoria consumida por el formulario no se liberaba nunca, a pesar de las invocaciones explicitas al &lt;strong&gt;GC.Collect()&lt;/strong&gt;. Estaba claro que había alguna referencia al formulario hijo que no se liberaba, y eso impedía la recolección del mismo, con todos sus instancias internas de DataTables, etc. Como ya hemos visto en otros casos en este blog, tiramos de &lt;strong&gt;!DumpHeap &lt;/strong&gt;para encontrar la dirección del formulario en cuestión (&lt;em&gt;MDIPropuesta&lt;/em&gt;) y, a partir de su dirección, miramos las roots con &lt;strong&gt;!GCRoot&lt;/strong&gt;: &lt;/p&gt;  &lt;p&gt;&lt;strong&gt;0:011&amp;gt; !DumpHeap -stat      &lt;br /&gt;&lt;/strong&gt;total 59745 objects     &lt;br /&gt;Statistics:     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MT&amp;#160;&amp;#160;&amp;#160; Count&amp;#160;&amp;#160;&amp;#160; TotalSize Class Name     &lt;br /&gt;[…]     &lt;br /&gt;03c94a3c&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 1&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 496 XXX.Dominio.Activo.Propuestas.IU.Formularios.MDIPropuesta     &lt;br /&gt;[…]     &lt;br /&gt;    &lt;br /&gt;&lt;strong&gt;0:011&amp;gt; !DumpHeap -mt 03c94a3c&lt;/strong&gt;     &lt;br /&gt;Address&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MT&amp;#160;&amp;#160;&amp;#160;&amp;#160; Size     &lt;br /&gt;&lt;strong&gt;01b23528&lt;/strong&gt; 03c94a3c&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 496&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;total 1 objects     &lt;br /&gt;Statistics:     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MT&amp;#160;&amp;#160;&amp;#160; Count&amp;#160;&amp;#160;&amp;#160; TotalSize Class Name     &lt;br /&gt;03c94a3c&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 1&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 496 XXX.Dominio.Activo.Propuestas.IU.Formularios.MDIPropuesta     &lt;br /&gt;Total 1 objects     &lt;br /&gt;    &lt;br /&gt;&lt;strong&gt;0:011&amp;gt; !GCRoot 01b23528&lt;/strong&gt;     &lt;br /&gt;eax:Root:01555428(System.Windows.Forms.Application+ComponentManager+ComponentHashtableEntry)-&amp;gt;     &lt;br /&gt;015247f8(System.Windows.Forms.Application+ThreadContext)-&amp;gt;     &lt;br /&gt;015d3910(&lt;strong&gt;XXX.Dominio.Activo.Propuestas.IU.Formularios.ShellMDI&lt;/strong&gt;)-&amp;gt;     &lt;br /&gt;015d3b08(System.Windows.Forms.PropertyStore)-&amp;gt;     &lt;br /&gt;01632b94&lt;strong&gt;(System.Windows.Forms.PropertyStore+ObjectEntry[])&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Como podemos ver, tenemos una referencia en el &lt;strong&gt;PropertyStore&lt;/strong&gt; de la clase &lt;strong&gt;ShellMDI&lt;/strong&gt;, la ventana MDI principal. Por alguna razón, no esta soltando su referencia a la ventana hija al salir esta de contexto, y en esta aplicación esta provocando que entre 40 y 60 preciados Mb no se liberen, provocando gran cantidad de GCs efímeras y la promoción de objetos de corta vida a generación 2 en muchos escenarios.&lt;/p&gt;  &lt;h4&gt;La Solución&lt;/h4&gt;  &lt;p&gt;Buscando un poco por la red pudimos comprobar que se trata de un problema conocido y reportado en &lt;a title="https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=357705&amp;amp;wa=wsignin1.0" href="https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=357705&amp;amp;wa=wsignin1.0"&gt;https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=357705&amp;amp;wa=wsignin1.0&lt;/a&gt;. Actualmente no hay parche oficial de Microsoft que conozcamos, pero aprovechándome de la información recogida en otros foros y documentos, os resumo el problema y la solución.&lt;/p&gt;  &lt;p&gt;Hasta la versión 2.0 RTM del framework .NET, aparecía el siguiente fragmento de código para actualizar el icono cuando una ventana hija MDI es desactivada:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;if( this.FormerlyActiveMdiChild != null )      &lt;br /&gt;{       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; this.FormerlyActiveMdiChild.UpdateWindowIcon(true);       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;strong&gt;this.FormerlyActiveMdiChild = null;        &lt;br /&gt;&lt;/strong&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;En la versión 2.0 SP1 y siguientes, Microsoft modifico el código para resolver un problema en el caso de que la ventana se estuviera cerrando en ese momento:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;if( this.FormerlyActiveMdiChild != null &amp;amp;&amp;amp;      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;strong&gt;!this.FormerlyActiveMdiChild.IsClosing&lt;/strong&gt; )       &lt;br /&gt;{       &lt;br /&gt;&lt;/code&gt;&lt;code&gt;this.FormerlyActiveMdiChild.UpdateWindowIcon(true);      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;strong&gt;this.FormerlyActiveMdiChild = null;&lt;/strong&gt;       &lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Este cambio en el código provoca que, en ese escenario, no se ponga a NULL la propiedad FormerlyActiveMDIChild, manteniendo el padre siempre una referencia a su ultima ventana hija activa, provocando la fuga de memoria comentada.&lt;/p&gt;  &lt;p&gt;Como solución, adoptamos una publicada orginalmente en el blog &lt;a href="http://www.scitech.se/blog/"&gt;.NET and Memory&lt;/a&gt;, que consiste en poner la propiedad FormerlyActiveMdiChild a NULL a manopla mediante reflexión en el evento OnMdiChildActivate:&lt;/p&gt;  &lt;p&gt;&lt;code&gt;protected override void OnMdiChildActivate( EventArgs e )      &lt;br /&gt;{       &lt;br /&gt;&amp;#160; base.OnMdiChildActivate( e );       &lt;br /&gt;&amp;#160; try       &lt;br /&gt;&amp;#160; {       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; typeof( Form ).InvokeMember( &amp;quot;FormerlyActiveMdiChild&amp;quot;,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BindingFlags.Instance | BindingFlags.SetProperty |       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; BindingFlags.NonPublic, null,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; this, new object[] { null } );       &lt;br /&gt;&amp;#160; }       &lt;br /&gt;&amp;#160; catch( Exception )       &lt;br /&gt;&amp;#160; {       &lt;br /&gt;&amp;#160; }       &lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;  &lt;p&gt;Una vez implementado este cambio, comprobamos como la aplicación comenzó a comportarse como se esperaba, liberando la memoria en las siguientes invocaciones al &lt;strong&gt;GC.Collect()&lt;/strong&gt; posteriores al cierre del formulario.&lt;/p&gt;  &lt;p&gt;En nuestro caso particular, persistieron otros problemas de presión de memoria… pero como ya decía el cronista de Conan, eso, amigos… eso es otra historia ;)&lt;/p&gt;  &lt;h4&gt;Conclusión&lt;/h4&gt;  &lt;p&gt;Seré breve: Si vuestras aplicaciones emplean el framework .NET 2.0 SP1 o superior y estáis empleando una interfaz MDI, con toda probabilidad seréis victimas de este problema. No hay fix oficial de Microsoft, así que os recomiendo el workaround sugerido.&lt;/p&gt;  &lt;p&gt;No es una perdida de memoria acumulativa, solo perderéis la memoria del ultimo formulario abierto, pero aún así puede ser determinante como en el caso que os he expuesto aquí. &lt;/p&gt;  &lt;h4&gt;Notas Finales&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;Supongo que a estas alturas no es necesario recordarlo, pero nunca esta de más: las continuas referencias en este articulo a &lt;strong&gt;GC.Collect()&lt;/strong&gt; se han empleado solo con fines de depuración y análisis. Nunca debéis invocar explícitamente al GC a no ser que &lt;a href="http://www.wintellect.com/cs/blogs/jeffreyr/default.aspx"&gt;Jeffrey Richter&lt;/a&gt;, &lt;a href="http://blogs.msdn.com/maoni"&gt;Maoni&lt;/a&gt; o &lt;a href="http://geeks.ms/members/lguerrero/default.aspx"&gt;Luis Guerrero&lt;/a&gt; os lo pidan en persona :) &lt;/li&gt;    &lt;li&gt;Cada vez que invocáis a &lt;strong&gt;GC.Collect()&lt;/strong&gt;, Dios mata a un &lt;a href="http://www.elrellano.com/videos_online/2769/un-mal-dia.html"&gt;gatito&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;De verdad, no lo hagáis. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;em&gt;Children of the Daaaaaaaaamneed!!! Chiiiiildren of the Damneeeeeeed!!&lt;/em&gt; (entónese con estilo &lt;a href="http://en.wikipedia.org/wiki/Bruce_Dickinson"&gt;Bruce Dickinson&lt;/a&gt; o estilo &lt;a href="http://en.wikipedia.org/wiki/Adam_West_(Family_Guy)"&gt;alcade Adam West&lt;/a&gt;, es indiferente).&lt;/p&gt;  &lt;p&gt;La aventurilla de los formularios ‘hijos’ de ese ‘maldito’ MDIParent me sirven como excusa perfecta para recomendaros ese &lt;a href="http://es.youtube.com/watch?v=igEvUYY--7k"&gt;‘Children of the Damned’&lt;/a&gt;, temazo aparecido en el &lt;a href="http://en.wikipedia.org/wiki/The_Number_of_the_Beast_(album)"&gt;The Number of the Beast&lt;/a&gt; de los Iron Maiden alla por el año 1982. &lt;/p&gt;  &lt;p&gt;Por cierto, esta canción la empleaba &lt;a href="http://en.wikipedia.org/wiki/Sebastian_Bach"&gt;Sebastian Bach&lt;/a&gt; en sus tiempos de &lt;a href="http://en.wikipedia.org/wiki/Skid_Row_(heavy_metal_band)"&gt;Skid Row&lt;/a&gt; para calentar y para las pruebas de sonido; ¡no os perdáis sus excelentes versiónes!.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=141764" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/WinDbg/default.aspx">WinDbg</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/DOT/default.aspx">DOT</category></item><item><title>Für Immer: The Return of the WeakReference</title><link>http://geeks.ms/blogs/palvarez/archive/2008/11/07/f-252-r-immer-the-return-of-the-weakreference.aspx</link><pubDate>Fri, 07 Nov 2008 22:55:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:112213</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>13</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=112213</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2008/11/07/f-252-r-immer-the-return-of-the-weakreference.aspx#comments</comments><description>&lt;p&gt;¿A que el titulo del post parece de una película de Hollywood? Bueno, pues por ahí van los tiros, y es que recientemente he tenido la oportunidad de tirar bastante de WinDbg para resolver problemas de rendimiento o estabilidad en algunos de nuestros clientes, y uno de estos escenarios ha sido particularmente interesante y lo he vivido casi como un episodio de CSI. ¡Y luego me preguntan por qué me gusta mi trabajo! :)&lt;/p&gt;
&lt;p&gt;Si me gustaría dejar claro que este post no forma parte del tutorial de WinDbg que tenía empezado; voy a suponer ciertos conocimientos de la herramienta y de .NET ya que mi objetivo es compartir esta sesión de depuración con vosotros, y no utilizarlo como ejemplo de formación. &lt;/p&gt;
&lt;p&gt;Ah... y al más puro estilo de las películas y series americanas, os diré que todos nombres han sido cambiados para preservar la identidad de los implicados. En este caso, si veis nombres de métodos o clases ridículas, simplemente achacadlo a mi falta de imaginación o creatividad.&lt;/p&gt;
&lt;p&gt;Al turrón...&lt;/p&gt;
&lt;h4&gt;El problema&lt;/h4&gt;
&lt;p&gt;El cliente nos comenta que sus servicios presentan graves problemas de estabilidad, provocando que con cierta frecuencia (últimamente esta frecuencia era diaria) estos servicios dejaran de responder y tuvieran que reciclarse.&lt;/p&gt;
&lt;p&gt;Evidentemente, lo que el cliente deseaba en este caso era localizar la raíz del problema y descubrir alguna solución o &lt;em&gt;workaround&lt;/em&gt; para mejorar la disponibilidad de sus servicios.&lt;/p&gt;
&lt;h4&gt;El Análisis&lt;/h4&gt;
&lt;p&gt;Lo primero que hice fue capturar un volcado de memoria del proceso una vez que éste se encontraba detenido, mediante el script &lt;strong&gt;adplus.vbs&lt;/strong&gt; en modo &lt;em&gt;hang&lt;/em&gt;. Una vez con el volcado en mi poder, me senté a analizarlo con calma con el WinDbg.&lt;/p&gt;
&lt;p&gt;Lo primero que hice fue examinar el estado de los hilos, y pude comprobar que multitud de ellos estaban detenidos en espera por otros recursos, como reflejaban las cimas de sus pilas con WaitForMultipleObjects y WaitForSingleObject. Me llamo especial atención este calll stack:&lt;/p&gt;
&lt;p&gt;&lt;font size="1" face="Courier New"&gt;26 Id: fec.c20 Suspend: 1 Teb: 000007ff`fff7c000 Unfrozen&lt;br /&gt;&lt;br /&gt;Child-SP RetAddr Call Site&lt;br /&gt;&lt;/font&gt;&lt;font size="1"&gt;&lt;font face="Courier New"&gt;&lt;strong&gt;&lt;font color="#ff0000"&gt;00000000`0588fad8 00000000`77d6cffb ntdll!ZwWaitForMultipleObjects+0xa&lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;00000000`0588fae0 00000000`77d6bb01 kernel32!WaitForMultipleObjectsEx+0x1cf&lt;br /&gt;00000000`0588fc00 00000642`7f48455e kernel32!WaitForMultipleObjects+0x11&lt;br /&gt;00000000`0588fc40 00000642`7f5e5fab mscorwks!SVR::WaitForFinalizerEvent+0x7e&lt;br /&gt;00000000`0588fc70 00000642`7f498474 mscorwks&lt;br /&gt;SVR::GCHeap::FinalizerThreadWorker+0x6b&lt;br /&gt;00000000`0588fcb0 00000642`7f5ca069 mscorwks!FusionBind::VerifyBindingStringW+0x78&lt;br /&gt;00000000`0588fd00 00000642`7f47297d mscorwks!EEClass::FindPropertyMethod+0x175&lt;br /&gt;00000000`0588fdd0 00000642`7f45cc3a mscorwks&lt;br /&gt;MDInternalRW::GetMethodSpecProps+0xc9&lt;br /&gt;00000000`0588fe10 00000642`7f5eb824 mscorwks!ManagedThreadBase_NoADTransition+0x42&lt;br /&gt;00000000`0588fe70 00000642`7f4545bc mscorwks!SVR::GCHeap::FinalizerThreadStart+0x74&lt;br /&gt;00000000`0588feb0 00000000`77d6b6da mscorwks!Thread::intermediateThreadProc+0x78&lt;br /&gt;00000000`0588ff80 00000000`00000000 kernel32!BaseThreadStart+0x3a&lt;/font&gt;&lt;/font&gt; 
&lt;p&gt;Se trata de un hilo especial, pues éste hilo se corresponde al finalizador, responsable de finalizar los objetos en las recolecciones de basura. ¿Como sabemos que es el finalizador? Es fácil, por el call stack... ¡métodos como el GCHeap::FinalizerThreadStart nos dan una buena pista! Aún así, si queremos asegurarnos podemos lanzar un&lt;strong&gt; !Threads&lt;/strong&gt;, que en este caso devolvió la siguiente salida:&lt;/p&gt;
&lt;p&gt;&lt;font size="1" face="Courier New"&gt;0:000&amp;gt; !threads&lt;br /&gt;ThreadCount: 24&lt;br /&gt;UnstartedThread: 0&lt;br /&gt;BackgroundThread: 19&lt;br /&gt;PendingThread: 0&lt;br /&gt;DeadThread: 5&lt;br /&gt;Hosted Runtime: yes&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PreEmptive &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ID OSID&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ThreadOBJ&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; State&amp;nbsp;&amp;nbsp; GC&amp;nbsp;&amp;nbsp;&amp;nbsp; ... APT Exception&lt;br /&gt;&amp;nbsp; 16&amp;nbsp;&amp;nbsp;&amp;nbsp; 1 12b8 00000000001563a0&amp;nbsp;&amp;nbsp; 1808220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; &lt;strong&gt;&lt;font color="#ff0000"&gt;26&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp; c20 0000000000171c10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; b220 Enabled ... MTA (Finalizer)&lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;&amp;nbsp; 27&amp;nbsp;&amp;nbsp;&amp;nbsp; 3 1244 00000000001799c0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1220 Enabled ... Ukn&lt;br /&gt;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp; 4&amp;nbsp; efc 000000000019b010&amp;nbsp;&amp;nbsp;&amp;nbsp; 80a220 Enabled ... MTA (Threadpool Completion Port)&lt;br /&gt;&amp;nbsp; 14&amp;nbsp;&amp;nbsp;&amp;nbsp; 7 13e8 000000000659c480&amp;nbsp;&amp;nbsp; 880a220 Enabled ... MTA (Threadpool Completion Port)&lt;br /&gt;&amp;nbsp; 30&amp;nbsp;&amp;nbsp;&amp;nbsp; 9 1248 00000000065bd650&amp;nbsp;&amp;nbsp; 200b220 Enabled ... MTA&lt;br /&gt;&amp;nbsp; 31&amp;nbsp;&amp;nbsp;&amp;nbsp; b&amp;nbsp; 7bc 00000000065897d0&amp;nbsp;&amp;nbsp; 200b220 Enabled ... MTA&lt;br /&gt;&amp;nbsp; 32&amp;nbsp;&amp;nbsp;&amp;nbsp; d&amp;nbsp; 9dc 0000000006618760&amp;nbsp;&amp;nbsp;&amp;nbsp; 80a220 Enabled ... MTA (Threadpool Completion Port)&lt;br /&gt;&amp;nbsp; 37&amp;nbsp;&amp;nbsp;&amp;nbsp; e&amp;nbsp; c60 00000000065d83e0&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; 40&amp;nbsp;&amp;nbsp; 11&amp;nbsp; 2b4 00000000066718b0&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; 41&amp;nbsp;&amp;nbsp; 12 162c 00000000065f7990&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; 42&amp;nbsp;&amp;nbsp; 13 149c 00000000067692f0&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; 43&amp;nbsp;&amp;nbsp; 14 1280 0000000009261760&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; 44&amp;nbsp;&amp;nbsp; 15&amp;nbsp; a84 0000000006686060&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;&amp;nbsp; 45&amp;nbsp;&amp;nbsp; 19 14fc 00000000095e7880&amp;nbsp;&amp;nbsp; 200b220 Enabled ... MTA&lt;br /&gt;&amp;nbsp; 46&amp;nbsp;&amp;nbsp;&amp;nbsp; 5&amp;nbsp; e50 00000000092c2a40&amp;nbsp;&amp;nbsp; 200b220 Enabled ... MTA&lt;br /&gt;&amp;nbsp; 47&amp;nbsp;&amp;nbsp; 1a&amp;nbsp; a04 00000000066a4c60&amp;nbsp;&amp;nbsp; 200b220 Enabled ... MTA&lt;br /&gt;&amp;nbsp; 48&amp;nbsp;&amp;nbsp; 16 11ec 0000000033046980&amp;nbsp;&amp;nbsp; 880b220 Enabled ... MTA (Threadpool Completion Port)&lt;br /&gt;&amp;nbsp; 50&amp;nbsp;&amp;nbsp; 25&amp;nbsp; d64 000000002f4ca930&amp;nbsp;&amp;nbsp; 180b220 Enabled ... MTA (Threadpool Worker)&lt;br /&gt;XXXX&amp;nbsp;&amp;nbsp; 18&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 000000002cb93a40&amp;nbsp;&amp;nbsp; 8801820 Enabled ... Ukn (Threadpool Completion Port)&lt;br /&gt;XXXX&amp;nbsp;&amp;nbsp; 1d&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 0000000016b345f0&amp;nbsp;&amp;nbsp; 8801820 Enabled ... Ukn (Threadpool Completion Port)&lt;br /&gt;XXXX&amp;nbsp;&amp;nbsp; 1b&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 00000000154fa520&amp;nbsp;&amp;nbsp; 8801820 Enabled ... Ukn (Threadpool Completion Port)&lt;br /&gt;XXXX&amp;nbsp;&amp;nbsp; 20&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 00000001c0750a10&amp;nbsp;&amp;nbsp; 8801820 Enabled ... Ukn (Threadpool Completion Port)&lt;br /&gt;XXXX&amp;nbsp;&amp;nbsp; 1e&amp;nbsp;&amp;nbsp;&amp;nbsp; 0 00000001c0bc0010&amp;nbsp;&amp;nbsp; 8801820 Enabled ... Ukn (Threadpool Completion Port)&lt;/font&gt; 
&lt;p&gt;Como vemos, la salida nos muestra claramente que el hilo 26 es el finalizador, lo que confirma nuestra sospecha de antes. &lt;/p&gt;
&lt;p&gt;Sabiendo que el finalizador está bloqueado no nos es difícil llegar a la conclusión de que la memoria no debe andar muy fina. Para ver su estado general, lanzamos un &lt;strong&gt;!dumpheap -stat&lt;/strong&gt;, que nos devuelve la siguiente información:&lt;/p&gt;
&lt;p&gt;&lt;font size="1" face="Courier New"&gt;0:031&amp;gt; !dumpheap -stat&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 0&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1222297 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 1&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1273253 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 2&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1126040 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 3&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1243940 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 4&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1344846 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 5&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1194789 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 6&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1279943 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Heap 7&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 1137244 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;------------------------------&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;total 9822352 objects&lt;/font&gt; 
&lt;p&gt;&lt;font size="1" face="Courier New"&gt;Statistics:&lt;/font&gt; 
&lt;p&gt;&lt;font size="1" face="Courier New"&gt;MT Count TotalSize Class Name&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;00000642bcf6ddd8 1 24 System.Web.Configuration.FormsProtectionEnum&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;(..)&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;000006428044b780 286785 36708480 XX.Contratos.Entidad.Maestros.CompaniaEntidad&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;0000064280564b40 389036 37347456 XX.Contratos.Entidad.Maestros.TipoBasicoEntidad&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;0000064278909f18 9736 37949592 System.Int32[]&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;(..)&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;000006427890a150 330357 117567984 System.Object[]&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;0000064280588ec0 235248 120446976 XX.Contratos.Entidad.Proveedores.FacturaProveedorEntidad&lt;br /&gt;&lt;/font&gt;&lt;font color="#ff0000" size="1" face="Courier New"&gt;000000000015c1a0 368 182038624 Free&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;00000642788c1ad0 4208597 243318792 System.String&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Total 9822352 objects&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Fragmented blocks larger than 0.5 MB:&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;Addr Size Followed by&lt;br /&gt;&lt;/font&gt;&lt;font size="1" face="Courier New"&gt;&lt;strong&gt;00000000a6d9fb88&lt;/strong&gt; &lt;font color="#ff0000"&gt;67.6MB 00000000ab140920 System.Collections.ArrayList&lt;br /&gt;&lt;/font&gt;&lt;/font&gt;&lt;font color="#ff0000" size="1" face="Courier New"&gt;00000000c63b7740 66.5MB 00000000ca63b0d0 System.ServiceModel.Activation.HostedHttpContext&lt;/font&gt; 
&lt;p&gt;He recortado mucho la salida del &lt;strong&gt;!dumpheap&lt;/strong&gt; para que coja correctamente en el blog, y os he marcado los detalles mas relevantes. Por una parte, la gran cantidad de objetos que tenemos en todos los heaps, así como esos dos objetos enormes que tenemos en los &lt;strong&gt;Large Object Heaps (LOH)&lt;/strong&gt;. Y por último, y más significativo, es la gran cantidad de memoria que aparece como &lt;strong&gt;Free&lt;/strong&gt;, que se corresponde con memoria que ha sido marcada para limpieza, pero que aun no ha sido recogida por el GC. Generalmente, un alto porcentaje (a partir de un 30%) de memoria Free vs. la memoria total de todos los heaps suele ser indicativo de problemas de fragmentación de memoria, a menudo producida por objetos &lt;em&gt;pinned &lt;/em&gt;(objetos estáticos, que van a permanecer siempre en la misma posición en el managed heap).&lt;/p&gt;
&lt;p&gt;Vamos a concretar un poco más mirando nuestra cola de finalización, con &lt;strong&gt;!FinalizeQueue&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;font size="1" face="Courier New"&gt;0:031&amp;gt; !FinalizeQueue&lt;br /&gt;SyncBlocks to be cleaned up: 0&lt;br /&gt;MTA Interfaces to be released: 0&lt;br /&gt;STA Interfaces to be released: 0&lt;br /&gt;----------------------------------&lt;br /&gt;------------------------------&lt;br /&gt;Heap 0&lt;br /&gt;generation 0 has 6 finalizable objects (000000002ff5c8c8-&amp;gt;000000002ff5c8f8)&lt;br /&gt;generation 1 has 15 finalizable objects (000000002ff5c850-&amp;gt;000000002ff5c8c8)&lt;br /&gt;generation 2 has 9480 finalizable objects (000000002ff4a010-&amp;gt;000000002ff5c850)&lt;br /&gt;Ready for finalization 0 objects (000000002ff5c8f8-&amp;gt;000000002ff5c8f8)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 1&lt;br /&gt;generation 0 has 36 finalizable objects (00000000392f56d8-&amp;gt;00000000392f57f8)&lt;br /&gt;generation 1 has 13 finalizable objects (00000000392f5670-&amp;gt;00000000392f56d8)&lt;br /&gt;generation 2 has 10956 finalizable objects (00000000392e0010-&amp;gt;00000000392f5670)&lt;br /&gt;Ready for finalization 0 objects (00000000392f57f8-&amp;gt;00000000392f57f8)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 2&lt;br /&gt;generation 0 has 42 finalizable objects (00000001c3cf9ee8-&amp;gt;00000001c3cfa038)&lt;br /&gt;generation 1 has 91 finalizable objects (00000001c3cf9c10-&amp;gt;00000001c3cf9ee8)&lt;br /&gt;generation 2 has 11334 finalizable objects (00000001c3ce39e0-&amp;gt;00000001c3cf9c10)&lt;br /&gt;Ready for finalization 0 objects (00000001c3cfa038-&amp;gt;00000001c3cfa038)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 3&lt;br /&gt;generation 0 has 109 finalizable objects (00000001c3d23208-&amp;gt;00000001c3d23570)&lt;br /&gt;generation 1 has 15 finalizable objects (00000001c3d23190-&amp;gt;00000001c3d23208)&lt;br /&gt;generation 2 has 12986 finalizable objects (00000001c3d09bc0-&amp;gt;00000001c3d23190)&lt;br /&gt;Ready for finalization 0 objects (00000001c3d23570-&amp;gt;00000001c3d23570)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 4&lt;br /&gt;generation 0 has 8 finalizable objects (00000001c3af48b8-&amp;gt;00000001c3af48f8)&lt;br /&gt;generation 1 has 64 finalizable objects (00000001c3af46b8-&amp;gt;00000001c3af48b8)&lt;br /&gt;generation 2 has 12035 finalizable objects (00000001c3adcea0-&amp;gt;00000001c3af46b8)&lt;br /&gt;Ready for finalization 0 objects (00000001c3af48f8-&amp;gt;00000001c3af48f8)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 5&lt;br /&gt;generation 0 has 71 finalizable objects (000000001f6c9070-&amp;gt;000000001f6c92a8)&lt;br /&gt;generation 1 has 87 finalizable objects (000000001f6c8db8-&amp;gt;000000001f6c9070)&lt;br /&gt;generation 2 has 11207 finalizable objects (000000001f6b2f80-&amp;gt;000000001f6c8db8)&lt;br /&gt;Ready for finalization 0 objects (000000001f6c92a8-&amp;gt;000000001f6c92a8)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 6&lt;br /&gt;generation 0 has 146 finalizable objects (00000001c3c3a1c0-&amp;gt;00000001c3c3a650)&lt;br /&gt;generation 1 has 25 finalizable objects (00000001c3c3a0f8-&amp;gt;00000001c3c3a1c0)&lt;br /&gt;generation 2 has 12829 finalizable objects (00000001c3c21010-&amp;gt;00000001c3c3a0f8)&lt;br /&gt;Ready for finalization 0 objects (00000001c3c3a650-&amp;gt;00000001c3c3a650)&lt;br /&gt;------------------------------&lt;br /&gt;Heap 7&lt;br /&gt;generation 0 has 9 finalizable objects (00000001c3d4bd48-&amp;gt;00000001c3d4bd90)&lt;br /&gt;generation 1 has 26 finalizable objects (00000001c3d4bc78-&amp;gt;00000001c3d4bd48)&lt;br /&gt;generation 2 has 14299 finalizable objects (00000001c3d2fda0-&amp;gt;00000001c3d4bc78)&lt;br /&gt;Ready for finalization 0 objects (00000001c3d4bd90-&amp;gt;00000001c3d4bd90)&lt;br /&gt;Statistics:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; MT&amp;nbsp;&amp;nbsp;&amp;nbsp; Count&amp;nbsp;&amp;nbsp;&amp;nbsp; TotalSize Class Name&lt;br /&gt;00000642bcf589f8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 24 System.Web.Configuration.ImpersonateTokenRef&lt;br /&gt;0000064278963e28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 24 System.Threading.OverlappedDataCache&lt;br /&gt;00000642bcf4d948&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 System.Web.PerfInstanceDataHandle&lt;br /&gt;00000642b78f1b28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 Bid+AutoInit&lt;br /&gt;00000642789b0608&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 System.Security.Cryptography.SafeHashHandle&lt;br /&gt;00000642788cd1b8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle&lt;br /&gt;00000642788cd110&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle&lt;br /&gt;0000064274f044d8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 System.Net.SafeLocalFree&lt;br /&gt;0000064274ee4c20&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 Microsoft.Win32.SafeHandles.SafeFileMapViewHandle&lt;br /&gt;0000064274ee4ad0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle&lt;br /&gt;0000064274ee0598&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32 Microsoft.Win32.SafeHandles.SafeProcessHandle&lt;br /&gt;0000064278964118&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 40 System.Threading.RegisteredWaitHandleSafe&lt;br /&gt;0000064278963da8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 40 System.Threading.OverlappedDataCacheLine&lt;br /&gt;0000064274ed8928&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 48 Microsoft.CSharp.CSharpCodeProvider&lt;br /&gt;00000642bcf56b88&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 56 System.Web.Compilation.CompilationMutex&lt;br /&gt;00000642802cd880&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 56 Microsoft.Practices.EnterpriseLibrary.Caching.Cache&lt;br /&gt;00000642788ffea8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 64 Microsoft.Win32.SafeHandles.SafePEFileHandle&lt;br /&gt;00000642788e3160&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 64 System.Security.Cryptography.SafeProvHandle&lt;br /&gt;0000064274ef89b0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 64 System.Net.SafeCloseSocket+InnerSafeCloseSocket&lt;br /&gt;0000064274f05b90&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 80 System.Net.SafeRegistryHandle&lt;br /&gt;00000642788f9c68&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 96 Microsoft.Win32.SafeHandles.SafeTokenHandle&lt;br /&gt;0000064274f05798&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 96 System.Net.SafeCloseSocketAndEvent&lt;br /&gt;00000642bc59aea0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 128 System.Transactions.SafeIUnknown&lt;br /&gt;00000642802b2380&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 176 Oracle.DataAccess.Client.ConnectionPool&lt;br /&gt;00000642788e1428&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 208 System.Runtime.Remoting.Contexts.Context&lt;br /&gt;00000642802b9b00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 256 Oracle.DataAccess.Types.OpoDecCtx&lt;br /&gt;00000642802374c0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 320 Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Storage.ConfigurationChangeFileWatcher&lt;br /&gt;00000642bcf4f7a0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 392 System.Web.DirMonCompletion&lt;br /&gt;00000642bc593f80&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 11&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 440 System.Transactions.FinalizedObject&lt;br /&gt;00000642788d2110&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 14&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 448 Microsoft.Win32.SafeHandles.SafeRegistryHandle&lt;br /&gt;0000064274eda4c0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 480 System.CodeDom.Compiler.TempFileCollection&lt;br /&gt;00000642bcf53138&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 14&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 560 System.Web.ProcessImpersonationContext&lt;br /&gt;00000642788d5b40&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 640 System.Threading.ReaderWriterLock&lt;br /&gt;00000642bcf4eb78&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 20&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 800 System.Web.ApplicationImpersonationContext&lt;br /&gt;00000642788ec748&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 896 Microsoft.Win32.SafeHandles.SafeWaitHandle&lt;br /&gt;00000642788c9250&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 896 Microsoft.Win32.SafeHandles.SafeFileHandle&lt;br /&gt;00000642788ec928&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 28&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1120 System.Threading.TimerBase&lt;br /&gt;00000642bcf6f9f0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 32&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1280 System.Web.ClientImpersonationContext&lt;br /&gt;0000064274ee3290&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 10&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1680 System.Diagnostics.PerformanceCounter&lt;br /&gt;00000642bc59af50&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 60&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1920 System.Transactions.Oletx.CoTaskMemHandle&lt;br /&gt;0000064278963bb8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 16&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1920 System.Threading.OverlappedData&lt;br /&gt;00000642788c2360&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 21&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2184 System.Threading.Thread&lt;br /&gt;00000642802b6180&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 12&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2496 Oracle.DataAccess.Client.OracleDataReader&lt;br /&gt;00000642788df980&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 27&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 3240 System.IO.FileStream&lt;br /&gt;00000642800c9200&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 190&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 6080 Oracle.DataAccess.Client.UTF8CommandText&lt;br /&gt;00000642800c8a40&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 46&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 9936 Oracle.DataAccess.Client.OracleConnection&lt;br /&gt;00000642800c6f00&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 55&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 14960 Oracle.DataAccess.Client.OracleCommand&lt;br /&gt;00000642789a9290&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 1062&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 76464 System.Reflection.Emit.DynamicResolver&lt;br /&gt;00000642bcf89bd0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 7542&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 301680 System.Web.HttpResponseUnmanagedBufferElement&lt;br /&gt;00000642800c90c0&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 8992&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 503552 Oracle.DataAccess.Client.MetaData&lt;br /&gt;&lt;strong&gt;&lt;font color="#ff0000"&gt;00000642788cf7d0&amp;nbsp;&amp;nbsp;&amp;nbsp; 77605&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 2483360 System.WeakReference&lt;br /&gt;&lt;/font&gt;&lt;/strong&gt;Total 95889 objects&lt;/font&gt; 
&lt;p&gt;Wow!! A parte de la enorme cantidad de objetos en Generación 2 en todos los heaps, algo bastante alarmante de por si... ¡fijaros en esas 77605 instancias de &lt;strong&gt;System.WeakReferences&lt;/strong&gt; pendientes de finalización!&lt;/p&gt;
&lt;p&gt;Er... WeakReferences... mm... ¿de que no suenan? Bueno, a los &lt;em&gt;geekeros de pro&lt;/em&gt;, seguro que nos trae a la mente estos dos geniales posts: &lt;a href="http://geeks.ms/blogs/rcorral/archive/2008/04/23/fugando-memoria-con-net.aspx"&gt;Fugando Memoria con .NET&lt;/a&gt; y &lt;a href="http://geeks.ms/blogs/dsalgado/archive/2008/04/24/reto-i-de-rodrigo-corral-d.aspx"&gt;Un poco de WinDbg aplicado&lt;/a&gt;, de los grandes Rodrigo Corral y David Salgado :)&lt;/p&gt;
&lt;p&gt;Mi primera pregunta fue ¿de dónde demonios salen todas esas WeakReferences? Tras tirar un poco del hilo con &lt;strong&gt;!GCRoot&lt;/strong&gt; no encontré claramente su origen, por lo que saque la artillería pesada: La funcionalidad de &amp;#39;buscar en toda la solución&amp;#39; de Visual Studio :D Pues bien... ninguna coincidencia con WeakReference. Fue entonces cuando se me encendió la bombillita y recordé que nuestro cliente empleaba Enterprise Library 3.0, así que fui raudo y veloz como una exhalación a buscar mi WeakReference perdida en la EntLib... y nada :(&lt;/p&gt;
&lt;p&gt;Finalmente decidí dejarme de artillería pesada, y hacer las cosas bien nuevamente :_) Así que recurrí al comando &lt;strong&gt;!TraverseHeap&lt;/strong&gt;, con el cual genere un fichero de salida con el cual alimenté al &lt;strong&gt;CLR Profiler&lt;/strong&gt;. Gracias a esta jugada pude ver que las WeakReferences estaban siendo creadas en un módulo antiguo del cliente, reutilizado de una versión anterior de su producto. ¡Genial! Busco en el código de esa versión y... ¡ni rastro de la WeakReference!&lt;/p&gt;
&lt;p&gt;Llegados a este punto la sesión de depuración ya se había convertido en algo personal; ahi estaba el volcado. Yo, cerca de él. Y entre nosotros dos, un viejo camino polvoriento, y una planta rodadora solitaria... (¡Hey... ya os dije que esto era una pelicula de Hollywood!). Decidí volver al dump y volcarme la .dll del cliente que contenía las WeakReferences a disco, gracias al comando &lt;strong&gt;.writemem&lt;/strong&gt;, para así poder estudiarla con &lt;strong&gt;Reflector&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Una vez abierto el código con Reflector, pude comprobar como el constructor del objeto en cuestión contenía este código:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;public Presupuesto(int CodigoCompañia)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;strong&gt;ArrayList list = __ENCList;&lt;br /&gt;&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; lock (list)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;strong&gt;__ENCList.Add(new WeakReference(this));&lt;br /&gt;&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; this.mCodigoPais = Program.PaisActual;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; this.mCodigoCompañia = CodigoCompañia;&lt;br /&gt;}&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Er... ¿WTF? XD ¿Qué demonios es esa colección llamanda __ENCList y que está haciendo en nuestro modulo? ¡¡Pero si esa colección no esta en el código fuente del cliente!!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;[Saco mi guía del autoestopista galáctico, leo el &amp;#39;Don&amp;#39;t Panic&amp;#39; e inmediatamente me siento reconfortado... procedamos...]&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Una búsqueda por internet me hizo descubrir que esa colección __ENCList la crea automágicamente Visual Studio para Vb.NET en modo DEBUG, y en C# activando una opción llamada &lt;strong&gt;Edit And Continue&lt;/strong&gt;. Si, señores y señoras, al parecer el &lt;strong&gt;Edit And Continue&lt;/strong&gt; instrumenta el código. ¡En nuestro caso esta instrumentación acabó en producción ya que se desplegó la .DLL en modo DEBUG! &lt;/p&gt;
&lt;h4&gt;Conclusiones&lt;/h4&gt;
&lt;p&gt;La primera conclusión es clara y concisa... ¡repetid conmigo! &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;No voy a poner en producción código compilado en modo DEBUG.&amp;quot;&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Bien, después de interiorizar esta frase y adquirir el firme propósito de repetirla 5 veces cada mañana al despertarnos durante un mes, podemos extraer otras conclusiones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WinDbg mola:&lt;/li&gt;&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Si no fuera gracias a la captura del volcado de memoria, hubiera sido muy, pero que muy difícil dar con este problema. ¡Recordad que el código &amp;#39;ofensor&amp;#39; no se encontraba ni siquiera en los fuentes del cliente! Solo pudimos &amp;#39;cazarlo&amp;#39; porque recurrimos a la imagen en memoria del módulo, que no se correspondía con el código fuente.&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Invertir un tiempo en formar a nuestros desarrolladores en herramientas de depuración siempre compensa:&lt;/li&gt;&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Si vais a acudir al Tech-Ed de Barcelona, me gustaría recordaros que &lt;a href="http://blogs.msdn.com/tess/"&gt;Tess Ferrandez&lt;/a&gt; va a andar por ahí y va a dar un par de sesiones; podría ser un buen punto de partida para este tipo de formación en depuración avanzada. No hace falta que os la presente, a estas alturas todo el mundo conoce ya a la diosa del WinDbg!&lt;/p&gt;
&lt;p&gt;Si no podéis acudir al Tech-Ed, no os preocupéis... siempre os quedará el DOT ;)&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;No poner en producción código compilado en modo DEBUG.&lt;/li&gt;&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;¿Que la he dicho ya? Nunca está de más repetirlo.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En alemán &lt;em&gt;Für Immer &lt;/em&gt;significa &lt;em&gt;para siempre&lt;/em&gt;. &lt;em&gt;Für Immer&lt;/em&gt; estaría nuestro finalizador esperando, bloqueado, y haciendo que la memoria reservada por nuestra aplicación creciera y creciera, &lt;em&gt;Für Immer &lt;/em&gt;estaría nuestro cliente experimentando las continuas caídas del servicio, y &lt;a href="http://es.youtube.com/watch?v=jUHrVeKYGfo&amp;amp;feature=related"&gt;&amp;#39;Für Immer&amp;#39;&lt;/a&gt;... es también uno de los temas más conocidos de la magnísima señora &lt;a href="http://en.wikipedia.org/wiki/Doro_Pesch"&gt;Doro Pesch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nuestra amiga Dorotea es una de las más grandes féminas que pululan por el panorama del hard rock y heavy internacional. Su estilo es inconfundible; si bien hace poco &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/10/04/back-to-the-bullet-sesi-243-n-de-depuraci-243-n-y-optimizaci-243-n.aspx"&gt;os hablaba de Sandi Saraya&lt;/a&gt; y de su dulce voz, con Doro nos encontramos con un enfoque totalmente diferente, caracterizado por autentica actitud heavy y una voz con más fuerza y garra que muchisimos vocalistas masculinos. Desde sus tiempos en &lt;a href="http://en.wikipedia.org/wiki/Warlock_(band)"&gt;Warlock&lt;/a&gt; hasta sus últimos trabajos en solitario, nunca ha perdido un ápice de su estilo.. nunca se ha vendido. ¡Tan solo eso me hace quitarme mi sombrero de cowboy ante esta señora!&lt;/p&gt;
&lt;p&gt;En serio, si os va el heavy o el hard rock y nunca habéis escuchado nada de Doro, Tio Doval os recomienda muy encarecidamente que no os perdáis su discografía. Os sugiero empezar con esto: &lt;a href="http://es.youtube.com/watch?v=OmnVNxEmBn4&amp;amp;feature=related"&gt;&amp;#39;All we Are&amp;#39;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Keep Rockin&amp;#39;!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=112213" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/WinDbg/default.aspx">WinDbg</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/DOT/default.aspx">DOT</category></item><item><title>Back to the Bullet: Sesión de Depuración y Optimización</title><link>http://geeks.ms/blogs/palvarez/archive/2008/10/04/back-to-the-bullet-sesi-243-n-de-depuraci-243-n-y-optimizaci-243-n.aspx</link><pubDate>Fri, 03 Oct 2008 23:55:12 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:103384</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>6</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=103384</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2008/10/04/back-to-the-bullet-sesi-243-n-de-depuraci-243-n-y-optimizaci-243-n.aspx#comments</comments><description>&lt;p&gt;El jueves pasado tuve la oportunidad de pasar unas horas hablando sobre depuración avanzada y optimización de aplicaciones junto a mi compañero y amigo &lt;a href="http://geeks.ms/blogs/luisguerrero"&gt;Luis Guerrero&lt;/a&gt;, del DOT Team de Plain Concepts. La respuesta ha sido muy buena, tanto en correos de agradecimiento, así como comentarios y preguntas (que espero responder a lo largo de este fin de semana! ;-) ), así que lo suyo es que, a pesar de lo abandonadito que tengo el blog, al menos dedique una pequeña entrada para colgar los materiales y agradeceros la presencia en esta sesión tan especial :)&lt;/p&gt; &lt;p&gt;&lt;a href="http://geeks.ms/files/folders/palvarez/entry103378.aspx"&gt;Aquí&lt;/a&gt; tenéis la presentación de Introducción a la Depuración Avanzada, y &lt;a href="http://geeks.ms/files/folders/palvarez/entry103379.aspx"&gt;aquí&lt;/a&gt; la de optimización de SQL Server, aunque esta ultima no la llegamos a ver por falta de tiempo. &lt;/p&gt; &lt;p&gt;Os dejo aquí los enlaces a las sesiones grabadas:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032390753&amp;amp;Culture=es-ES"&gt;Sesión de la Mañana&lt;/a&gt;&lt;/li&gt; &lt;li&gt;&lt;a href="http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032390757&amp;amp;Culture=es-ES"&gt;Sesión de la Tarde&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/ndicesFiltradosenSQLServer2008_7CEB/revoluciona_2.png"&gt;&lt;img style="border-right-width:0px;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" border="0" alt="revoluciona" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/ndicesFiltradosenSQLServer2008_7CEB/revoluciona_thumb.png" width="404" height="462" /&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Por último dar las gracias a todos los que nos acompañasteis durante la sesión, ya fuera presencialmente o a través de la sesión de Live Meeting, así como a los chicos del CIIN, que nos permitieron &amp;#39;hostear nuestro proceso en su AppDomain&amp;#39; :) . &lt;/p&gt; &lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;El rock tip de hoy no tiene mucha relación con el tema, simplemente me apeteció titular al post con &amp;#39;Back to the Bullet&amp;#39; porque hacía mucho que no sacaba un ratín para escribir en geeks.ms, y esta es mi vuelta, mi regreso al redil ;) La expresión me vino a la cabeza gracias al &lt;a href="http://www.youtube.com/watch?v=6BZ7BEWDCvM"&gt;gran tema de Saraya&lt;/a&gt; del mismo nombre, que a pesar de estar en el lado mas blando y ñoño de la música que yo pueda escuchar, no deja de ser un tema excelente. &lt;/p&gt; &lt;p&gt;Aunque el grupo tuvo éxito en su época, se separaron poco después de sacar el segundo disco; de la guapisima Sandi Saraya no os puedo contar mucho, ya que parece haberse escondido en las mismas montañas de Tora Bora, porque no ha vuelto a saberse nada de ella. El teclista de la formación, Gegg Munier, falleció hará un par de años y el guitarrista, Tony &amp;quot;Bruno&amp;quot; Rey, curiosamente fue el primer guitarrista de otro grupo que ya apareció en mas de un post mío, los inigualables Danger Danger, donde con el tiempo sería sustituido por Andy Timmons.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=103384" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Eventos/default.aspx">Eventos</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/DOT/default.aspx">DOT</category></item><item><title>Patience: Ejecución de Tareas en SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2008/08/10/patience-ejecuci-243-n-de-tareas-en-sql-server.aspx</link><pubDate>Sun, 10 Aug 2008 13:23:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:92553</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>5</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=92553</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2008/08/10/patience-ejecuci-243-n-de-tareas-en-sql-server.aspx#comments</comments><description>&lt;p&gt;En &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/05/18/these-days-un-d-237-a-en-la-vida-de-una-consulta.aspx"&gt;esta entrada&lt;/a&gt; estrenábamos una sección dedicada a la resolución de problemas de rendimiento en SQL Server, y os comentaba que tenía pensado dedicar éstas primeras entregas principalmente a la arquitectura del producto; mi idea es dedicar cada entrada a describir algunos de los conceptos determinantes para el rendimiento en la ejecución de nuestras consultas y acompañarlo de ejemplos prácticos. &lt;/p&gt;
&lt;p&gt;Pues bien, en esta ocasión &lt;em&gt;me gustaría hablaros&lt;/em&gt; de las esperas en SQL Server; un concepto fundamental de cara a la identificación de potenciales problemas de rendimiento. Y digo que me gustaría hablaros de las esperas, porque no voy a poder hacerlo :) Antes de ello, deberemos ver una introducción básica a la ejecución de tareas en SQL Server para posteriormente, en una entrada futura, introduciros a las esperas y a mi metodología de resolución de problemas de rendimiento favorita en SQL Server... (pero que bueno soy creando tensión y expectación, ¿eh? :) )&lt;/p&gt;
&lt;p&gt;Al turrón...&lt;/p&gt;
&lt;h4&gt;Ejecución de Tareas en SQL Server&lt;/h4&gt;
&lt;p&gt;El objetivo último de SQL Server es procesar tareas, y para ello necesita una serie de recursos (disco, memoria, red...), así como, al menos, un procesador. Hasta ahí nada nuevo. Lo que quizá no se conoce tanto es que SQL Server dispone internamente de unos procesadores lógicos llamados &lt;strong&gt;schedulers&lt;/strong&gt;, que van a ser clave a la hora de entender como se procesan las tareas, como interpretar las esperas y un sin fin de detalles relevantes para el rendimiento de nuestros servidores.&lt;/p&gt;
&lt;p&gt;Cuando SQL Server arranca, una de las primeras cosas que hace es consultar al sistema operativo, mediante WMI, cuantos procesadores tiene la máquina, para posteriormente crear tantos &lt;em&gt;schedulers &lt;/em&gt;como procesadores se hayan detectado. El objetivo de éstos será el de ejecutar las tareas del servidor (nuestras consultas, procesos internos de SQL Server como el &lt;em&gt;lazy writer&lt;/em&gt;, etc.), de un modo coordinado con el UMS/SQLOS del que hablaremos en otra entrada. &lt;/p&gt;
&lt;p&gt;Cada uno de esos procesadores lógicos tendrá asignada una lista de tareas, implementadas mediante &lt;em&gt;worker threads&lt;/em&gt;, que se ejecutarán a medida que el &lt;em&gt;scheduler&lt;/em&gt; vaya quedando libre. Estos &lt;em&gt;worker threads&lt;/em&gt; se implementan, por defecto, como hilos del sistema operativo (se puede cambiar este comportamiento para que funcionen como fibras, en caso de tener el servidor funcionando en &lt;em&gt;fiber mode&lt;/em&gt;), con su consiguiente consumo de memoria y recursos por cada uno de los hilos. Por esta razón, y para evitar un crecimiento masivo de hilos en el sistema, hay un parámetro de configuración del servidor llamado &lt;strong&gt;max_worker_threads&lt;/strong&gt;, que permite limitar el número máximo de hilos que SQL Server va a pedir al sistema operativo para ejecutar tareas en ellos. Éste será también el número máximo de tareas que va a tener el sistema, distribuido entre todos sus &lt;em&gt;schedulers&lt;/em&gt;.&lt;/p&gt;
&lt;h5&gt;Un par de ejemplos&lt;/h5&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched0_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="276" alt="Scheduler para una única CPU" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched0_thumb.png" width="163" align="right" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;El escenario más básico sería el que representamos en el diagrama de la derecha. En éste caso, disponemos de una máquina tradicional con una única CPU de un núcleo.&lt;/p&gt;
&lt;p&gt;El proveedor WMI devolverá que hay una CPU, y SQL Server creará un único &lt;em&gt;scheduler&lt;/em&gt; que gestionará la ejecución de todas las tareas del servidor. Habrá, por tanto, una única lista de tareas, con &lt;em&gt;n worker threads&lt;/em&gt;, donde &lt;em&gt;n&lt;/em&gt; es el número establecido en la variable de configuración &lt;strong&gt;max_worker_threads&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;El siguiente escenario (en el diagrama de abajo) representa un entorno con dos CPUs de doble núcleo. Como podemos ver, cada uno de las CPUs se enumera como dos CPUs diferentes por WMI (hay dos núcleos por cada CPU), por lo que tendremos un total de cuatro &lt;em&gt;schedulers&lt;/em&gt;. Por otra parte, vemos que habrá cuatro listas de ejecución de tareas, cada una de ellas con un limite máximo de una cuarta parte de las tareas máximas definidas por el &lt;strong&gt;max_worker_threads&lt;/strong&gt;. Esto es así porqué SQL Server trata de equilibrar la carga equitativamente entre todos sus procesadores.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/sched2_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="312" alt="sched2" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/sched2_thumb.png" width="540" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Hay un tercer escenario interesante, pero me lo reservo para un poquito más adelante en este mismo post :) Eso si, no me gustaría acabar esta sección sin comentar un par de detalles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Una tarea no puede abandonar un &lt;em&gt;scheduler&lt;/em&gt; y moverse a otro a mitad de su ejecución. Esto es, una tarea esta atada a un &lt;em&gt;scheduler&lt;/em&gt; desde su creación hasta su finalización. 
&lt;li&gt;Se puede establecer una Mascara de Afinidad para determinar que CPUs vamos a usar en SQL Server, que schedulers vamos a crear, etc. pero esta fuera del ámbito de esta entrada por lo que no lo comentaremos de momento.&lt;/li&gt;&lt;/ul&gt;
&lt;h4&gt;Consultando los Schedulers&lt;/h4&gt;
&lt;p&gt;Bien, ya hemos visto que, en teoría, existe por ahí algo que se llaman &lt;em&gt;schedulers&lt;/em&gt;, que son CPUs lógicas que sirven para controlar la ejecución de tareas en SQL Server, y que además tienen forma de cajas de color verde con un suave y artístico degradado. Pero, sin duda alguna, en este instante os estáis preguntando si hay alguna manera de ver éstas estructuras en el servidor. Y también sé que, hasta que no descubráis como, no vas a poder conciliar el sueño. Por eso mismo, y porque no puedo tolerar el sufrimiento humano, pasamos sin más preámbulos a echar un vistazo a los metadatos que SQL Server expone para que veamos la información de los &lt;em&gt;schedulers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;La siguiente query consulta sys.dm_os_schedulers, la DMV principal para obtener información acerca de los mismos:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SELECT &lt;br /&gt;&amp;nbsp;&amp;nbsp; scheduler_id, &lt;br /&gt;&amp;nbsp;&amp;nbsp; cpu_id, &lt;br /&gt;&amp;nbsp;&amp;nbsp; status&lt;br /&gt;FROM sys.dm_os_schedulers&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Mi máquina de pruebas tiene una única CPU dual core. En ella, la ejecución de la consulta muestra el siguiente resultado:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/resultado1_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="164" alt="resultado1" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/resultado1_thumb.png" width="369" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;¿Y esto? ¿No deberían aparecer solo dos &lt;em&gt;schedulers&lt;/em&gt; según lo que hemos visto arriba? Bien, lo cierto es que solo los dos que aparecen marcados en naranja son &lt;em&gt;normales&lt;/em&gt;. Los dos que aparecen con estado HIDDEN ONLINE se emplean para procesar las tareas del sistema, mientras que los VISIBLE ONLINE se dedican al proceso de tareas de usuario, y son en las que nos fijaremos normalmente de cara a identificar potenciales problemas de rendimiento.&lt;/p&gt;
&lt;p&gt;El último scheduler, categorizado como VISIBLE ONLINE (DAC), esta reservado exclusivamente para la Dedicated Administrator Connection (Conexión Dedicada de Administrador). Simplificando mucho, SQL Server 2005 incorpora, por defecto, un nodo NUMA oculto, compuesto por una pequeña cantidad de memoria y un &lt;em&gt;scheduler&lt;/em&gt;, que nos permiten retomar el control del servidor para hacer troubleshooting básico del sistema en caso de que los &lt;em&gt;schedulers&lt;/em&gt; &amp;#39;normales&amp;#39; no estén disponibles o se encuentren monopolizados. &lt;/p&gt;
&lt;p&gt;El la práctica, lo habitual es filtrar la búsqueda para que no nos muestre ningún scheduler cuyo ID sea menor a 255, con lo que nos quedamos solo con los schedulers de usuario.&lt;/p&gt;
&lt;h4&gt;Complicando un poco más el asunto...&lt;/h4&gt;
&lt;p&gt;En realidad el modelo no es tan sencillo como una lista de tareas por scheduler. En SQL Server la planificación de tareas es bastante mas compleja, con una serie de listas adicionales. Nosotros vamos a ver a continuación un modelo un poco más real, introduciendo la lista de esperas por recursos (&lt;em&gt;resource waits&lt;/em&gt;). &lt;/p&gt;
&lt;p&gt;Si os fijáis en el diagrama que tenemos a continuación, vemos como el Scheduler contiene una tarea que se haya en estado &lt;em&gt;Running&lt;/em&gt; (en ejecución). También vemos una lista de tareas listas para ejecución (&lt;em&gt;Runnable Queue&lt;/em&gt;), que contiene las tareas que el Scheduler podría ejecutar en cualquier momento, ya que no dependen de recursos externos (disco, otras transacciones, etc.). Por último, tenemos la lista de esperas por recursos (&lt;em&gt;resource waits list&lt;/em&gt;) que contiene todas las tareas que aun no se han completado pero que no pueden pasar a la CPU porque dependen de una espera por un recurso externo, como pueda ser un bloqueo, operaciones de sincronización de paralelismo, una operación de disco o de red, etc. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Lists1_2.png"&gt;&lt;img style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height="286" alt="Lists1" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Lists1_thumb.png" width="525" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;En la siguiente entrada hablaremos de las esperas, y profundizaremos en este diagrama con ejemplos prácticos. Por ahora me conformo con haber introducido los conceptos que darán pie a hablar de las esperas en SQL Server.&lt;/p&gt;
&lt;h4&gt;Canzando Mitos: Hyperthreading vs. SQL Server&lt;/h4&gt;
&lt;p&gt;No se si os acordáis, pero en un post reciente (el término reciente en mi vida &amp;#39;bloggeril&amp;#39; es, como podréis apreciar, bastante relativo) hice un ejercicio descarado de plagio y posiblemente violé tres o cuatrocientas leyes al adoptar el tema de los &amp;#39;cazadores de mitos&amp;#39; para buscar un escenario práctico real en el que se aplicara la teoría vista en la entrada. Pues bien, vamos a seguir cazando mitos... en esta ocasión será el siguiente: ¿Realmente tiene sentido deshabilitar Hyperthreading(*) en nuestros servidores SQL Server?&lt;/p&gt;
&lt;p&gt;Vamos a ver lo que sucede en una máquina con una CPU con Hyperthreading &lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched3_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="363" alt="Sched3" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched3_thumb.png" width="293" align="left" border="0" /&gt;&lt;/a&gt;habilitado. Vemos como tenemos una única CPU, con un único núcleo, y a pesar de todo, SQL Server crea dos &lt;em&gt;schedulers. ¿Por que hace esto SQL Server? &lt;/em&gt;Pues sencillamente porque, como os comenté, SQL Server le pregunta a Windows, a través de WMI, cuantos procesadores tiene la máquina... y no sé quien tomo la decisión de diseño (o quién pagó a quien, ni cuanto...) pero el caso es que para Windows, una máquina con Hyperthreading habilitado cuenta como si tuviera dos CPUs.&lt;/p&gt;
&lt;p&gt;Nosotros, que somos chicos y chicas listos, sabemos que no es cierto; sabemos que el Hyperthreading es, simplificando mucho, una mejora en en las Execution Units(EU) del procesador que permiten que, en ciertas circunstancias, se ejecute trabajo en estas EU que en otros casos estarían ociosas. &lt;strong&gt;Pero&lt;/strong&gt; (y éste es un gran pero...) realmente solo hay una CPU: las diferentes EUs comparten, entre otras cosas, las cachés de procesador L1 y L2. Y hete aquí que tales cachés son importantes en SQL Server, hasta el punto de que si se comparten puede haber serios problemas en dos partes fundamentales del servidor: el mecanismo de agujas del reloj del &lt;em&gt;lazy writer&lt;/em&gt; (que sirve para sacar del Buffer Pool las páginas menos utilizadas), y el mecanismo para adquirir spinlocks (bloqueos livianos que se solicitan mediante polling constante). Al compartir las caches, es muy posible que en operaciones paralelizadas las cachés no tengan la información que deben de tener, lo que puede provocar que se saquen del Buffer Pool páginas que no deberían irse fuera (con su consiguiente pérdida de rendimiento), o peor aún, que no se pueda adquirir un spinlock.&lt;/p&gt;
&lt;p&gt;Sin entrar en demasiado detalle, quedémonos con la idea de que SQL Server ve dos procesadores, y crea dos schedulers, pero en realidad solo es un &amp;#39;truco&amp;#39;, interesante en otro tipo de aplicaciones pero muy dañino para SQL Server. ¿Conclusión? Al menos en SQL Server 2000 y 2005 os recomiendo encarecidamente deshabilitar Hyperthreading desde la BIOS si vuestros servidores lo soportan. No se aún si SQL Server 2008 implementa algún cambio que afecte a servidores Hyperthreaded, pero en cuanto consiga hacerme con una máquina con un procesador apropiado haré la prueba.&lt;/p&gt;
&lt;p&gt;(*): &lt;em&gt;Ahhh... hyperthreading, aquella tecnología que &lt;/em&gt;&lt;a href="http://news.zdnet.co.uk/hardware/0,1000000091,39286539,00.htm"&gt;&lt;em&gt;amenaza&lt;/em&gt;&lt;/a&gt;&lt;em&gt; con volver a nuestros procesadores :_) ¡que malo soy a veces con los chicos de Intel.&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;Conclusiones&lt;/h4&gt;
&lt;p&gt;Bueno, nos ha salido otro post un poco &amp;#39;ladrillo&amp;#39;, pero hemos sentado una buena base a la que nos referiremos en posteriores entradas. Lo más importante aquí es comprender lo que son los &lt;em&gt;schedulers&lt;/em&gt;, tener una aproximación a las diferentes listas de tareas y ver como podemos estudiar los metadatos para hacernos una idea de si el sistema esta sufriendo de contención de CPU o no. &lt;/p&gt;
&lt;p&gt;Evidentemente en este artículo hemos realizado algunas simplificaciones (sobre todo en el ámbito de las diferentes listas de tareas), pero en siguientes entradas entraremos en más detalle, y si no me equivoco empezaremos precisamente con las esperas de las diferentes tareas, los estados que pueden tener y como pasan de uno a otro y, sobre todo, cómo podemos utilizar esa información para comprender por donde se nos escapa el rendimiento. &lt;/p&gt;
&lt;p&gt;Espero que os resulte interesante :)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ya os dije en la presentación de esta entrada que vamos a tener que tener un poco de paciencia antes de poder saltar a las esperas de SQL Server, por lo que me ha parecido apropiado preceder el titulo de este post con el temazo &lt;a href="http://www.youtube.com/watch?v=pEzuC5UoM8g"&gt;&amp;#39;Patience&amp;#39;&lt;/a&gt; de los &lt;a href="http://en.wikipedia.org/wiki/Guns_n%27_roses"&gt;Guns n&amp;#39; Roses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hablando de los Guns N&amp;#39; Roses (que, por cierto, son/fueron uno de mis grupos favoritos), ahí va mi órdago: sí el señor &lt;a href="http://en.wikipedia.org/wiki/Axl_Rose"&gt;William &amp;#39;Axl&amp;#39; Rose Jr.&lt;/a&gt; y sus secuaces (sean quienes sean) sacan el &lt;a href="http://en.wikipedia.org/wiki/Chinese_Democracy"&gt;&amp;#39;Chinese Democracy&amp;#39;&lt;/a&gt; durante éste año del señor de 2008, un servidor se corta el pelo. ¿Mi predicción? El disco será la banda sonora del Duke Nukem Forever xD&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=92553" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item></channel></rss>