Otra desternillante pero útil entrevista en comando tomate. Para los que no me conozcan cuento un poco lo que hago en la Universidad y para PlainConcepts. Además cuento alguna anédocta y consejos del "tomate" para los desarrolladores :)

 En breve publicaran otra entrevista más técnica sobre Spring.Net

Originalmente publicada aquí: http://comandotomate.net/archive/2008/05/07/entrevista-a-eduardo-quint-225-s.aspx

 

con no comments
Archivado en:

Tras colaborar en el Visual Studio 2008 Tour, el grupo .Nugg (.Net User Group Galicia) tiene nuevo evento:

IIS 7.0: Hágase la luz

El 16 de Mayo de 17:00 a 19:30, Iván González, MVP de IIS, nos va a contar las novedades de Internet Information Services 7.0 en Windows Server 2008 y Windows Vista.

El evento es, como todos los de .Nugg en A Coruña, en el edificio Xoana Capdevielle dentro del campus de Elviña (Universidade da Coruña).

Es necesario que os registreis.. que luego no hay sitio :p

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

Un mapita de cómo llegar, especialmente para la gente de fuera


Ver mapa más grande

Os presento una entrevista que me hizo Elisa para el Comando Tomate, que es un espacio web rojo con una misión tecnológicamente tomatera, verde y madura. Que además de entretener, educa :p
Atentos a mi destreza en las manos... lo aprendí de los mejores políticos :p  Además hablo de nHibernate.
Mi frase favorita, de esas que le salen a uno porque se quiere a si mismo (jejeje) :
"Yo conozco SQL y le (por nhibernate) he visto sacar consultas mucho mejor CASI de las que podría hacer yo"
Eduardo Quintás en Comando Tomate.

Tal como os había prometido a los que acudisteis a la charla os publico (un poco tarde, I know) los materiales de la misma.

Tenéis aquí la presentación de ADO.NET 3.0 Entity Framework Beta3 (está en pptx, 337KB) y también el código fuente de lo realizado en la charla (en zip 40KB).

Para el código fuente es necesaria la base de datos MyPeople.mdf que puede encontrase en el GettingStarted de EF BETA 3 en codeplex:
http://www.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=adonetsamples&DownloadId=23191

Most enterprise applications uses an architectural pattern that needs a stateless facade for its business logic. So they can use it in several scenarios like WebServices or WCF, ASP.NET applications, Traditional Desktop Application (Windows Forms, WPF).. etc.

If you're using Entinty Framework, your derived ObjectContext is called short lived context and there is some issues about it. My last post (in spannish language) explores best practices for better perfomance with short lived contexts in Entity Framework.

In this post I speak about re-attach modified EntityObjects, that were updated outside of an ObjectContext (in detached state). This is a very common scenario when you're using stateless facades.

So, imagine that your facade retrieves an EntityObject of your model and passes it to some visual control in the user interface. When an user or a process in IU changes your EntityObject, this object is in a detached state, so there isn't an ObjectStateManager tracking changes. When your passes it again to the facade at your Business Logic Layer this object needs to be persisted. Problem is that the new ObjectContext doesn't know about changes in properties, so when you call SaveChanges() nothing happens.

At User Interface layer:

MyEntity entity = Facade.GetEntityById(1); 
BindEntitiesToControls();

(User interaction that changes values in the detached object, by example a roundtrip in an ASP.NET app. The entity object could be in the viewstate or HttpSession) 

entity.PropertyValue = 4; 

(User clicks "Save" button)

Facade.Save(entity); 

At Business Layer Facade (naive aproximation):

public static void Save(EntityObject entity)
{
   using(MyContext ctx = new MyContext)
   {
     ctx.Attach(entity);
     ctx.SaveChanges();
   } 
}

This aprox. does NOT work!!! because the ObjectContext unknowns the changes ocurred in entity object when it was detached.

Upsss, but this is a very common scenario, so what can i do?

Take a look to this extensor method for an ObjectContext:

/// <summary>
/// Attach an EntityObject that was modified when detached
/// </summary>
/// <param name="obj"></param>
/// <param name="objectDetached">DetachedObject</param>

public static void AttachUpdated(this ObjectContext obj, EntityObject objectDetached)
{
  
if (objectDetached.EntityState == EntityState.Detached)
  
{
     
object original = null;
     
if (obj.TryGetObjectByKey(objectDetached.EntityKey, out original))
         
obj.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
      
else
      
throw new ObjectNotFoundException();
   
}
}

This extensor method first tries to load original entity into the object cache. If it's already loaded, nothing happens; if not, it is retrieved from the storage.
Then applies the objectDetached properties to the original object retrieved in last step.

Our new facade method could be:

public static void Save(EntityObject entity)
{
   using(MyContext ctx = new MyContext)
   {
     ctx.AttachUpdated(entity);
     ctx.SaveChanges();
   } 
}
 

En este post comento una serie  buenas prácticas para mejorar la eficiencia de ADO.NET Entity Framework (EF) en escenarios de aplicaciones empresariales dónde es recomendable utilizar contextos de tiempo de vida cortos (Short Lived Contexts)

AVISO: Lo aquí expuesto es aplicable a ADO.NET Entity Framework Beta 3. Es posible que en versiones posteriores esta información no sea correcta.

Introducción

Los principales patrones arquitectónicos para aplicaciones empresariales emplean una fachada que expone al UI o UIC la lógica de negocio de la misma. Esta fachada idealmente no debe mantener el estado (stateless) para que, entre otras razones, pueda utilizarse desde aplicaciones web como ASP.NET, servicios web,  WCF o clientes inteligentes ligeros en forma de aplicación de escritorio tradicional.

Si empleamos EF en nuestra capa de negocio tendremos métodos  que crean un ObjectContext local al inicio de su ejecución y liberan al final del método tras realizar una serie de operaciones de lógica de negocio mayormente breves. Se dice que estos ObjectContext son Short Lived  o de tiempo de vida corto.

Por el contrario, los contextos de tiempo de vida largo (long lived contexts), suelen estar asociados a aplicaciones de escritorio o consola tradicionales y se crean y destruyen al inicio y fin de la aplicación. Si bien también se aplica cada vez más el patrón arquitectónico anteriormente descrito en estas aplicaciones empresariales de escritorio. Así es posible distribuir la lógica de negocio y que la aplicación empresarial  con interface de escritorio (WPF, WindowsForms, Consola…) se comporte como un cliente inteligente ligero. Además podríamos aprovechar dicha lógica de negocio para ser usada con aplicaciones con interfaz web o desde servicios web o incluso WCF.

Los ORMs (Object Relational Mapping) como Entity Framework o NHibernate se emplean principalmente en estos escenarios empresariales y es curioso que la mayoría de la literatura y documentación al respecto inciden en presentar sus características en entornos con contextos de tiempo de vida largo y apenas se encuentran referencia a los Short Lived Context cuando son prácticamente el escenario arquitectónico empresarial más común.

Una de las ventajas de los ORMs es que nos dan una indirección estupenda del almacén final de datos y su consulta específica. Pero también nos añade el desconocimiento de cómo trabaja internamente y si el código que escribimos es o no eficiente. Para crear aplicaciones empresariales eficientes, algo imprescindible en entornos empresariales con alto número de peticiones, es preciso conocer cómo trabaja internamente el ORM que empleamos y el Entity Framework no es una excepción. El principal desafío es que pocos tenemos el tiempo suficiente para investigar su funcionamiento y deducir buenas prácticas al respecto.

Gracias a dos artículos publicados en ADO.NET TEAM Blog sobre rendimiento del Entity Framework [1] [2]y la fantástica FAQ de Entity Framework [3] recopilada por Danny Simmons en su blog Sys.Data.Objects dev guy se pueden extraer algunas buenas prácticas para mejorar el rendimiento en Entity Framework, especialmente para aquellos entornos con contextos de tiempo de vida cortos.

Buenas prácticas para mejorar la eficiencia en escenarios con Short Lived Contexts

Vamos a centrarnos en los siguientes aspectos:

·         Acelerar la creación de ObjectContexts

·         Afinar el ObjectStateManager

·         Optimizar la ejecución de consultas en LINQ to Entities

Acelerando la creación de ObjectContext

Uno de los principales problemas a lo que nos enfrentamos es que crear un ObjectContext es una actividad costosa, especialmente la primera vez. En una aplicación con short lived context se suele crear y desechar un ObjectContext prácticamente por petición a la lógica de negocio. En una sesión o en un petición desde el interface es fácil tener una o más invocaciones a la fachada de la lógica de negocio que utilizan varios ObjectContexts. Si multiplicamos estas instanciaciones por peticiones concurrentes, el número de ObjectContext creado se dispara. El EF proporciona alguna optimización al respecto [1]; la primera vez que se crea un ObjectContext se cachea la información de metadata del modelo que se construye a partir de los archivos CSDL, MSL y SSDL. Esta información se cachea y se comparte a nivel de dominio de aplicación (38% del tiempo). Otra gran parte del tiempo de esta primera instanciación se emplea en la creación de la vista abstracta de la base de datos en memoria (56% del tiempo), que también se cachea.  Solo el 7% del tiempo se emplea en la materialización (por ejemplo un DBReader obteniendo los datos físicos)

En la práctica tenemos que una primera ejecución de un método con una consulta a través de EF es del orden de 300 veces más lenta que las consultas sucesivas. Las consultas en caliente son relativamente rápidas y el 74% del tiempo se emplea en la materialización de la consulta y solo el 4,13% en la creación del ObjectContext.

Antipatrones

Si a alguien se le ocurre compartir un único ObjectContext por dominio de aplicación debe saber que no es una buena idea porque el EF no es thread Safe, como la gran mayoría de clases del framework, si bien podría implementarse un mecanismo de bloqueo propio. Ver [3]

Si estamos en una aplicación ASP.NET puede parecernos tentador guardar en la sesión el ObjectContext pero tampoco es una buena idea ya que el tamaño de la sesión crecería enormemente y su correcta serialización tampoco es trivial. Ver [3]

Buena práctica

La principal aproximación para rebajar el tiempo de creación del contexto es en su primera instanciación. Se puede reducir el tiempo de generación de la vista (un 58% del tiempo de creación) teniéndola compilada previamente.

Para ello deberemos ejecutar edmgen.exe con la opción /mode:ViewGeneration, esto crea un fichero de código que puede ser incluido en nuestro proyecto.

En las pruebas realizadas en [1] se obtuvo un descenso del 28% de tiempo en la primera instanciación. La desventaja  es que si hacemos cambios en los archivos de metada del EDM necesitaremos recompilar la aplicación. Pero si eso podemos asumirlo, deberíamos aplicar siempre esta optimización.

Afinando el ObjectStateManager

Cada ObjectContext tiene un ObjectStateManager que, entre otras tareas, mantiene el estado correcto de los objetos cacheados en memoria. Por defecto si se recuperan nuevas entidades del almacén de datos a través de una consulta, ESQL o LINQ  to Entities, EF solo cargará aquellas que no están aun en memoria, llevando un seguimiento efectivo de las entidades en memoria con respecto a su persistencia física en el almacén de datos.

Llevar este seguimiento o tracking es útil en aquellos contextos dónde realicemos varias consultas que impliquen un mismo conjunto de entidades.

Pero lo más normal es disponer de abundantes métodos en nuestra fachada que simplemente recuperan una enumeración de entidades para ser enlazadas a su vez a un control visual como un GridView o un DropDownList.

Buena práctica

Es posible modificar esta política de seguimiento del ObjectStateManager para un ObjectQuery concreto, desactivándola en casos como el anteriormente descrito, obteniendo una ligera mejora en los tiempos.  Un ObjectQuery tiene un atributo llamado MergeOption que es un enum con los valores: AppendOnly, NoTracking, OverwriteChanges, PreserveChanges. Con la opción MergeOption.NoTracking no se hacen comprobaciones de seguimiento.

Por ejemplo:

using (MyTravelPostEntities entities = new MyTravelPostEntities())
{
    entities.BlogPosts.MergeOption = MergeOption.NoTracking;
    IQueryable<BlogPost> postQuery = (from bp in entities.BlogPosts
                         where bp.Comments.Count() > 0
                         select bp);
    BlogPost post = postQuery.First();

De todas formas, según los tiempos obtenidos en los artículos referenciados, la comprobación de seguimiento por parte del ObjectStateManager  solo abarca de un 1% a un 3% del tiempo total de la consulta.

Optimizando las consultas en LINQ  to Entities

De la misma forma que se cachean los metadatos también se cachean las consultas a nivel de Entity Framework, por eso las segundas ejecuciones de la misma consulta son más rápidas. Al igual que el caché de metadatos, el caché de consultas tienen un ámbito global de dominio de aplicación.

Ojo, no confundir este caché de consultas de EF con el caché de planes de ejecución de consultas SQL en SQL Server u otro gestor de base de datos relacional.

Si bien una consulta en ESQL se cachea prácticamente toda, no ocurre lo mismo con una consulta en LINQ To Entities, algunas partes tienen que construirse de nuevo en las ejecuciones sucesivas; por ejemplo el árbol de expresión resultante tiene que ser calculado o compilado en memoria en tiempo de ejecución y para cada invocación de la consulta LINQ to Entities necesita ser reconstruido.

Lo ideal sería que estas consultas LINQ solo se tuvieran que compilar una vez.  Es posible hacerlo utilizando, por ejemplo, una función estática anónima[1] que actúe de delegado. Dando lugar a consultas compiladas LINQ; que son más rápidas que las consultas LINQ To Entities tradicionales siempre que se ejecuten con cierta frecuencia.

Buena práctica

Supongamos esta petición con contexto de tiempo de vida corto (obsérvese la aplicación de la buena práctica del apartado anterior):

using (PFCNetEntities ctx = new PFCNetEntities())
{
  ctx.Projects.MergeOption = ctx.MergeOption.NoTracking;
  IQueryable<ProjectRegister> query =
      from project in ctx.Projects
      where project.User.UserName == UserLogin
      select project;  pr = query.FirstOrDefault<ProjectRegister>();
} 

Para que no sea necesario volver a construir el árbol de expresión de la consulta LINQ to Entities utilizamos una expresion lambda1 estática que se ejecute al comienzo del tiempo de vida de nuestro dominio de aplicación, obsérvese que admite el paso del parámetro UserLogin para filtrar la consulta:

 

private static Func<PFCNetEntities, String, IQueryable<ProjectRegister>>
    compiledProjectRegisterByUserQuery = CompiledQuery.Compile(
        (PFCNetEntities ctx, string UserLogin) =>
             (from project in ctx.ProjectRegister
              where project.User.UserName == UserLogin
              select project)); 

public ProjectRegister GetProjectRegisterByUser(string UserLogin)
{
     ProjectRegister pr = null;
 
    using (PFCNetEntities ctx = new PFCNetEntities())
     {
        ctx.ProjectRegister.MergeOption = MergeOption.NoTracking;
        pr = compiledProjectRegisterByUserQuery(ctx,
                  UserLogin).FirstOrDefault<ProjectRegister>();
    }
    return pr;
}
 

En las pruebas realizadas en [2] se observa que para la primera ejecución de una consulta LINQ frente a una consulta LINQ Compilada se obtiene paradójicamente que la consulta LINQ es un 23% más rápida. Si bien en sucesivas ejecuciones (suponemos siempre que en nuestro escenario empresarial las consultas son invocadas frecuentemente) se obtiene que las consultas LINQ compiladas, que por ejemplo tienen un filtro, son del orden de un 80% más rápidas. Este dato es importante porque es una mejora significativa en los tiempos de ejecución. Siendo muy recomendable utilizar LINQ to Entities compiladas en aquellas consultas que sabemos a priori que van a tener una alta tasa de invocaciones.

Resumen

Los principales patrones arquitectónicos para aplicaciones empresariales implican el uso de contextos de tiempo de vida corto, especialmente en aquellas aplicaciones ASP.NET, servicios web o WCF. Se han presentado tres buenas prácticas para mejorar la eficiencia en este tipo de escenarios.

La primera reduce el tiempo de creación del primer ObjectContext en un dominio de aplicación.

La segunda desactiva la comprobación de seguimiento para el ObjectStateManager en aquellas operaciones de mera recuperación de datos.

La tercera y última buena práctica define un patrón para utilizar consultas LINQ To Entities compiladas.

Por último, este post está totalmente abierto y con vuestras aportaciones y lo que vaya surgiendo de los blogs de ADO.NET y System.Data.Object Dev Guy lo iré ampliando hasta tener un catálogo de buenas prácticas que mejoren la eficiencia de EF en escenarios comunes como los de contextos con tiempo de vida cortos.

Referencias

 

[1]

Exploring the Performance of the ADO.NET Entity Framework - Part 1
http://blogs.msdn.com/adonet/archive/2008/02/04/exploring-the-performance-of-the-ado-net-entity-framework-part-1.aspx
(04/02/2008) 

[2]

Exploring the Performance of the ADO.NET Entity Framework – Part 2 http://blogs.msdn.com/adonet/archive/2008/02/11/exploring-the-performance-of-the-ado-net-entity-framework-part-2.aspx
(11/02/2008)
 

[3]

EF FAQ updated - v0.3
http://blogs.msdn.com/dsimmons/archive/2008/01/04/ef-faq-updated-v0-3.aspx(04/02/2008) 
 


[1] para aquellos que no les suene, una función anónima es una expresión típica del lambda cálculo introducida en C# 3.0 y que ha mejorado la forma de definir delegados. Más información.

Os anuncio la proximidad de dos interesantísimos ;-) eventos .NUGG (.Net User Group Galicia) en A Coruña.
Es necesario el registro previo.
 
Viernes 22 de Febrero de 17:00 a 19:30
.Net 3.5: ADO.NET Entity Framework
Eduardo Quintás
 
Viernes 29 de Febrero de 17:00 a 19:30
More Magic - SQL Server 2008
Pablo Álvarez Doval
 
Se celebran en los aularios del CUFIE en el edificio Xoana Capdevielle del campus de Elviña.
Aqui teneis su localización
 
Espero veros a alguno!

El mes pasado, el ADO.NET Team, tal como nos comentaba elbruno, publlicó una nueva release beta del ADO.NET Entity Framework (EF en adelante) y una nueva CPT de las herramientas visuales para la generación del modelo de datos de entidades (EDM).

Como sabeis el EF es el primer ORM de Microsoft para la tecnología ADO.NET y conociendo previamente NHIbernate, la verdad es que ofrece una característica que hara las delicias de los programadores: LINQ to Entities. Es posible aprovechar toda la potencia de LINQ para consultar las entidades de nuestro modelo de forma efectiva, rápida y homogenea, siguiendo el ideal de Microsoft de aprender un único lenguaje de consulta de datos (LINQ) para múltiples tipos de datos (colecciones en memoria, XML, registro de windows, etc.) que se compila (y por tanto se comprueban los tipos) y no se resuelve en tiempo de ejecución como los largos strings con consultas SQL o HQL en NHibernate que eran culpables de bastantes errores en tiempo de ejecución y que requerían un depurado exhaustivo y algo tedioso.

Si quereis probarlo necesitareis por un lado VisualStudio 2008, podeis descargaros un Trial desde aquí o también el VisualStudio 2008 Express que es gratuito y luego instalar el Entity Framework Beta 3. Después es necesario instalar el XML Editor QFE para finalmente proceder a la instalación del Entity Designer CTP2

También os va a hacer falta tener instalado SQL Server 2005 (vale la edición express) para poder ejecutar los ejemplos disponibles en codeplex.

Como comentaba Bruno mucha gente se pregunta cuándo utilizar EF y cuando LINQ to SQL. La principal diferencia, a mi modo de ver, es que LINQ to SQL solo trabaja contra SQL Server, algo a tener en cuenta pues EF ofrece más proveedores de datos. Actualmente y según un post reciente del ADO.NET Team Blog ya existen proveedores para Oracle, MySQL, PostgreSQL, SQLite, Informix, Ingres, Sybase, DB2, Progress, VistaDB, SQL Anywhere así como un bridge para drivers ODBC y JDBC. También se ha anunciado que pronto estará disponible un proveedor para Firebird. Ya en épocas tempranas al desarrollo de EF se ofreció un ADO.NET Sample Provider que ayudaba de desarrolladores de proveedores de acceso a datos implementar sus propios proveedores de EF. Tanto que en el TechED celebrado en Junio de 2007 en Orlando, Brent Goss del equipo de DB2 de IBM mostró un ejemplo con 101 consultas de LINQ to Entities sobre un SGBD DB2. 

A todos aquellos que esteis interesados en ORMs o trabajeis con ellos os animo a que echeis un ojo al EF y saqueis vuestras propias conclusiones... para mi es uno de los productos de desarrollo en acceso a datos más interesantes actualmente.

En artículo publicado en DotNetManía de este mes, comentaba en un punto que " ...  también se planteó la posibilidad de utilizar procedimientos almacenados, aprovechando que se compilan y su plan de ejecución nunca se descarta de un caché ...". Que aunque no afecta a la esencia del artículo, para nada es correcto. Los procedimientos almacenados en SQL Server no se compilan. Se compila su plan de ejecución y es tratado en la caché como un elemento más con las mismas politicas que por ejemplo una entrada marcada como prepared_query. Esta compilación se lleva acabo en la primera invocación del procedimiento almacenado y puede ser recompilado a lo largo del tiempo por diversos motivos. Si os interesa podeis consultar el post "Caché de planes de ejecución en SQL Server 2005 y comportamiento de los procedimientos almacenados" dónde explico con detalle preguntas lógicas sobre el rendimiento de los procedimientos almacenados con consultas y consultas marcadas como prepared_query.

Por otra parte a todos aquellos que esteis interesados en obtener el código fuente del artículo podeis descargarlo haciendo clic aquí

Esta mañana Iván me preguntaba sobre una afirmación que ponía en las conclusiones de artículo "Visualización de grandes conjuntos de datos en ASP.NET" publicado en la DotNetMania de este mes. Decía " ...  también se planteó la posibilidad de utilizar procedimientos almacenados, aprovechando que se compilan y su plan de ejecución nunca se descarta de un caché ...". Esto es totalmente incorrecto y quizas me deje llevar por mis viejas experiencias con Informix y 4GL. Stick out tongue

Además nos empezaron a surgir bastantes dudas sobre el comportamiento de los planes de ejecución de los procedimientos almacenados.

¿Los Procedimientos almacenados se compilan? ¿cuándo lo hacen?
¿Hay alguna diferencia en la forma de tratar la caché de los planes de ejecución marcados como prepared_query o stored proc?
¿Cuál es el tamaño del caché de planes de ejecución?¿Cuál es la política para descartar elementos?
¿Cuándo se invalida el plan de caché, datos etc y se precisa de recompilación?

Documentación consultada

Primero miré algunos blogs sobre SQL Server según los devolvía google y acabé leyendo bastantes opiniones que se contradecían unas a otras relacionadas con las preguntas anteriores. En algún sitio ponían que los procedimientos almacenados eran compilados en el momento de ser creados... en fin...

Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005. Explica como funciona el caché de planes de ejecución con detalle para batchs (lotes) con sentencias SQL, incluidos procedimientos almacenados. También explica las diferencias principales de SQL Server 2000 y 2005 al respecto.

En el post 9.0 Memory Pressure Limits publicado en el blog SQL Programmability & API Development Team Blog, que siempre recomiendo para entender bien cómo funciona SQL Server por debajo encontré información adicional sobre el número de cachés de planes de ejecución y cómo se comportan en condiciones de poca memoria disponible. En el post What are the different cached objects in the plan cache? Ketan Duvedi nos explica los diversos tipos de objetos que se almacenan en las cachés de planes de ejecución

Por último en SQL Server Best Practices encontré una colección de enlaces a whitepapers y recursos con buenas prácticas para SQL Server (especialmente 2005)

Antes de responder a las preguntas conviene entender lo que es un batch o un lote de sentencias en SQL Server: Entenderemos por Batch o lote por un conjunto de sentencias SELECT, INSERT, DELETE, UPDATE, llamadas a prodecimientos almacenados. También se incluye todas las sentencias intermedias de Transact-sql, sentencias GRANT, DENY, etc... es la unidad de compilación e inserción en el caché pero cada sentencia individual. El batch es la unidad de compilación de plan de ejecución y cómo tal se guarda en la caché de planes de ejecución. Para cada sentencia del batch se guarda también su plan de ejecución en la caché, pero jerárquicamente dependen del batch.

Los Procedimientos almacenados se compilan? ¿cuándo lo hacen?

Pues primera discrepancia con lo que dije: los procedimientos almacenados estríctamente hablando no se compilan. Lo que se compila es el plan de ejecución y se almacena en la caché de planes de ejecución. El plan de ejecución se crea la primera vez que se invoca el procedimiento almacenado.  Y es tratado como una entrada más en la caché de los planes de ejecución, por tanto puede llegar a ser decartado. Por cierto los batch no debería superarse los 8KB de tamaño (ojo con los literales grandes, BLOBs, etc.) si queremos que entre la caché de planes de ejecución. Una mejora importante de SQL Server 2005 respecto a 2000 es que cuando es necesario recompilar el plan de ejecución de una sentencia en un batch (por diversos motivos, porque ha cambiado el esquema, etc.) se recompila solo esa sentencia y no el batch completo, como ocurría en SQL Server 2000.

¿Hay alguna diferencia en la forma de tratar la caché de los planes de ejecución marcados como prepared_query o stored proc?

A parte de ser objetos distintos (consulta parametrizada vs. proc. almacenado), en principio parece que no, según la documentación consultada ambos favorecen la reutilización de su plan de ejecución. De hecho las consultas marcadas como prepared query se han lanzado con SQLPrepare(), al igual que sp_executesql favorecen la reutilización del Plan. Los proveedores de OleDB y Odbc lo utilizan y como comprobé el proveedor nativo de SqlServer de ADO.Net con este hecho no hay diferencia entre tener una consulta prepared (parametrizada) y un procedimiento almacenado que realiza dicha consulta pasando los parámetros. Aunque parezca poco importante es muy revelador porque aun hay mucha gente que introduce consultas en procedimientos almacenados porque cree que es la única manera de aumentar el rendimiento al asegurar la reutilización del plan de ejecución.
Y puede que las consultas preparadas tengan mejor rendimiento que las incluidas en un procedimiento almacenado por la siguiente razón: Si un procedimiento almacenado se ejecuta en una base de datos D1, su plan de ejecución no es reutilizado si se ejecuta en una base de datos distinta D2. En cambio las consultas ad-hoc, las prepared y las dinámicas SI.
Los planes de ejecución se reutilizan entre usuarios, los contextos de ejecución (p.e. unos valores de parámetros determinados) no porque son afines a la sesión del usuario (lógicamente). Así que, aunque se cachean, el impacto es más bien escaso. Por eso es muy importante envíar consultas con parámetros explícitos o crear procedimientos almacenados, si bien SQL Server 2005 tiene una característica de autoparametrización de consultas que se repiten.
Es MUY IMPORTANTE utilizar nombres calificados a la hora de referenciar objetos de la base de datos. En otro caso no podrán SER REUTILIZADOS entre usuarios.
Por ejemplo: Supongamos que hay dos tablas foo: marta.foo y jaime.foo en una misma base de datos. El plan de ejecución "select * from users" para el usuario marta no sería compartido con el de jaime. Son tablas distintas. También ocurre cuando solo tenemos una tabla, por eso es importante hacer siempre "select * from dbo.users". En ese caso el UID coincide (-2) y puede ser compartida entre distintos usuarios.
 
¿Cual es el tamaño del caché de planes de ejecución?¿cúal es la política para descartar elementos?
Para empezar no hay un solo caché de planes de ejecución... realmente hay cuatro, pero dos son los importantes: hay uno para las consultas marcadas como ad-hoc y para las marcadas como prepared query y otro para los procedimientos almacenados. Podríamos pensar que estaría bueno balancear y crear prepared queries y proc. alamacenados para llenar equitativamente las dos cachés. Es una tontería, porque en el caso de que uno se llene y queden libres en el otro se le asigna el espacio sobrante al que le hace falta.
 
Con la siguiente consulta obtenemos los siguientes tamaños de entradas para cada plan de caché:
 
select name, type, buckets_count from sys.dm_os_memory_cache_hash_tables
where type in ('CACHESTORE_OBJCP' , 'CACHESTORE_SQLCP', 'CACHESTORE_PHDR', 'CACHESTORE_XPROC')
 
Por ejemplo en mi sistema (SQl Server Express 2005)

Object Plans               (CACHESTORE_OBJCP) 10007
SQL Plans                  (CACHESTORE_SQLCP) 10007
Extended Stored Procedures (CACHESTORE_XPROC) 127

Respecto a la política para descartar elementos se basa en el espacio de memoria reservado y la reutilización del plan de ejecución. En ningún momento se desprende un trato especial a los procedimientos almacenados. Concretamente en SQL Server 2005 hay ciertas diferencias con la política de caché de SQL Server 2000. Los cachés de planes de ejecución y de datos son distintos.  El lazy-writter que decrementa periodicamente el coste de un plan de ejecución (y si es 0 en caso de poca memoria se descarta) en SQL Server 2000 no existe como tal en SQL Server 2005. En vez de eso, cuando las entradas en el caché superan el 50% del tamaño destinado, el siguiente plan que llega al caché decrementa el contador de coste en 1 de todos los anteriores. Realmente este decremento es marcado a lo cerdito (piggybacked) y no es inmediato por el comportamiento, en la práctica, es parecido al del lazy-writter de SQL Server 2000. Cuando el tamaño del caché supera el 75% un hilo con un monitor dedicado se activa y decrementa el coste de los objetos en todas (ojo todas) las cachés. Si el plan se vuelve a utilizar se resetea este contador.

¿Cuándo se invalida el plan de caché, datos etc y se precisa de recompilación?

En bastantes circustancias, solo comento las principales (mirar mejor la documentación mencionada) y se agrupan en dos:
A. Razones relativas a la corrección del plan
A.1. Modificación en el esquema que afecta a la sentencia (p.e. se borra una tabla)

A.2. Llamadas a sp_recompile en un proc. almacenado, logicamente ;-) o que fuese creado con la opción WITH RECOMPILE

A.3. Operaciones que causan la invalidación total del plan de caché, a saber: Hacer un detach. Actualizar una bd (de 2000 a 2005). Ejecutar DBCC FREEPROCCAHE. Comando Reconfigure. Comando ALTER DATABASE ... MODIFY FILE GROUP o COLLATE. Reiniciar el servidor o el servicio de SQLServer también causa la invalidación total de los planes  (lo he comprobado)

A.4 Cambios en alguna opción SET antes de la consulta: ANSI_NULL_DFLT_OFF, ANSI_NULL_DFLT_ON, ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, DATEFIRST, DATEFORMAT, FORCEPLAN, LANGUAGE, NO_BROWSETABLE, NUMERIC_ROUNDABORT, QUOTED_IDENTIFIER. Es mejor establecerlos al principio de la conexión o de la sesión o dejárlos fijos en la BD.

B. Razones relativas a nivel de optimismo del plan.
B.1 Cuando todas las tablas de la consulta están marcadas como de solo lectura el plan no se recompila. Si la consulta tiene la opción KEEPFIXED tampoco se recompila 
B.2. Cuando ciertos indicadores dicen que la consulta ya no es óptima y necesita ser recompilada... depende de bastantes factores como por ejemplo las estádisticas referentes a las tablas implicadas y su tipo (si es variable, temporal, etc.). Para más info verlo en el documento en la sección "Plan optimality.related recompilations: The Big Picture".

Evidentemente, aunque no lo incluye el documento queda la opción de que el plan de ejecución sea descartado de la caché por problemas de espacio.

Conclusión
Los planes de ejecución de los Procedimientos Almacenados son creados en la primera invocación de la instancia del servidor SQL Server y son tratados como una entrada más en la caché de planes de ejecución. No parece que haya ninguna ventaja en utilizar procedimientos almacenados en SQL Server 2005 para mejorar el rendimiento de consultas preparadas, especialmente en lo que se refiere al caché de planes de ejecución. Por lo que no compensa su creación.

Os comento un truquejo, bastante simpático, que os puede ayudar a salir del paso alguna vez.

Problema: Me solicitan por separado las imágenes y en su mejor resolución de un documento word donde están incrustadas. No dispongo de los originales (eran capturas de pantalla) pruebo a copiarlas y pegarlas en Photoshop y salen con una calidad lamentable, mucho peor que la del documento.

Solución: Si el archivo es .docx (Office 2007) podemos hacer lo siguiente: le hacemos una copia (ctrl+c, ctrl+v sobre el archivo) y renombramos la extensión del archivo a .zip. Ojo, quizáis necesitéis ir a una ventana del explorador y en opciones de carpeta desactivar la opción de "Ocultar extensiones de archivos conocidos". Si examinamos el archivo zip veremos que dentro de la carpeta word\media encontramos las imagenes en formato png, emf, etc.. y a su resolución original.

Jeje a mi "me salvó la vida".

con 2 comment(s)
Archivado en: ,

Leyendo el hace unos días unos posts muy recomendables del Sql Server Programmability & API Development Team Blog acerca de cómo funciona caché de planes de consultas y procedimientos de SqlServer 2005, me enteré de una buena práctica para nuestro código .Net que accede a datos de SqlServer. Aunque encuentro lo descubierto más interesante para evitar una mala práctica que, por despiste, seguro que hacemos más de muchos. Y como veremos… me parece que algún chico de Redmond también.

Entremos en vereda...
Cuando ejecutamos una consulta contra SqlServer se emplea un tiempo en crear un plan de ejecución. Ese plan, relativamente costoso de crear, lo almacena en un caché de planes de ejecución. La reutilización de dicho plan mejora considerablemente el rendimiento de SqlServer al ahorrarnos un tiempo precioso, y por ende, también mejora el tiempo de respuesta de nuestra aplicación.Todos estamos más o menos aleccionados a utilizar parámetros en nuestras consultas SQL, sobre todo por razones de seguridad ya que filtra lo pasado a la consulta, evitando ataques por inyección de código SQL. Las consultas parametrizadas son planificadas con sus parámetros, así pueden ser reutilizadas aunque cambie el valor de los parámetros.Una cosa que me sorprendió es que SqlServer  autoparametriza las consultas que planifica en previsión de que sean reutilizadas:

 

SqlCommand command = new SqlCommand(conn);
command.CommandText = "select * from t1 where col1 = 1 and col2 = 'abcd'";
command.ExecuteNonQuery();
command.CommandText = "select * from t1 where col1 = 1 and col2 = 'abc'";
command.ExecuteNonQuery();

Genera un único plan de ejecución para la consulta:

(@1 tinyint,@2 varchar(8000))SELECT * FROM [t1] WHERE [col1]=@1 AND [col2]=@2

Es bastante común ejecutar un SqlCommand definiendo los valores de los parámetros y no especificar su tipo, especialmente si utilizamos AddWithValue que ya no nos da opción a definir el tipo del parámetro, muchos desarrolladores lo utilizan porque ahorras una llamada a un método.
Es típico ver esta porción de código:

 

SqlCommand command = new SqlCommand("select * from t1 where col1 = @id and col2 = @str", conn);
command.Parameters.AddWithValue("@id", 1);
command.Parameters.AddWithValue("@str", "abc");
command.ExecuteNonQuery();
command.Parameters[0].Value = 2;
command.Parameters[1].Value = "abcd";
command.ExecuteNonQuery();


Examinando el cache de planes de ejecución obtenemos:

(@1 tinyint,@2 nvarchar(3))SELECT * FROM [t1] WHERE [col1]=@1 AND [col2]=@2
(@1 tinyint,@2 nvarchar(4))SELECT * FROM [t1] WHERE [col1]=@1 AND [col2]=@2

Uppps, son dos planes de ejecución distintos. No ha reutilizado la consulta. Se ha perdido un tiempo valioso.  Se da la paradoja que la consulta sin parámetros sería más eficaz que la parametrizada en este segundo ejemplo. ¿Por qué ocurre esto? Pues al parecer porque no se ha especificado la longitud del parámetro alfanúmerico y SqlServer lo crea como un nvarchar de la longitud del valor pasado.  Tranquilos, esto no ocurre con tipos fijos como int o datetime, pero si en nuestra consulta tenemos un parámetro que es un string, evitad utilizar AddWithValue.

Este otro fragmento de código si sería más correcto:

command.CommandText = "select * from t1 where col1 = @id and col2 = @str";
command.Parameters.Add("@id", SqlDbType.Int);
command.Parameters.Add("@str", SqlDbType.NVarChar, 50);
command.Parameters[0].Value = 1;
command.Parameters[1].Value = "abc";
command.ExecuteNonQuery();
command.Parameters[0].Value = 2;
command.Parameters[1].Value = "abcd";
command.ExecuteNonQuery();


Produciendo un único plan de ejecución:
(@id int,@str nvarchar(50))select * from t1 where col1 = @id and col2 = @str


¿Os imagináis que pasaría si está dentro de un bucle que va insertar masivamente valores en nuestra base de datos? Pues que el caché de planes de ejecución se llenaría de consultas preparadas para cada combinación de parámetros alfanuméricos, sacando otras consultas preparadas (planificadas) del caché de planes de ejecución, además de incurrir en un tiempo extra en planificarla.

Lo curioso es que ayer mismo, utilizando un plan de generación de datos en VisualStudio 2005 for Database Professionals, descubrí un pequeño detalle al respecto mientras investigaba otra cosa… estaba insertando 524288 tuplas en una tabla de pruebas en la que tenía dos campos con NVarChar (psche… lo hago todos los días antes de desayunar) cuando paralelamente ejecutó una consulta Transact-sql en Sql Server Studio Management Express sobre la tabla del sistema con los planes de ejecución cacheados y me encuentro con esto:

Pantallazo de una consulta ... 

Cómo podéis observar en la imagen, ha creado un plan de ejecución por consulta, debido al problema que antes os mencionaba. Probé a hacer un count(*) en la tabla del caché de planes de ejecución y contenía 8192 tuplas (oh, curioso límite) con planes de ejecución de esta operación de generación de datos. El impacto de este problema no es excesivamente grave pues los planes de generación de datos se suelen ejecutar en instalaciones de SQLServer en entornos de desarrollo y no en producción, por lo que no parece importante que afecte al rendimiento de otras consultas.Desde mi punto de vista el problema se encuentra en una mala implementación de AddWithValue, el desarrollador no puede estar pendiente de si un método aparentemente cómodo y eficiente no lo sea, esa es la confianza que depositamos en el .Net Framework. Quizás sería recomendable que en futuros frameworks se reescribiera este método sobre todo para que intercepte los tipos de datos alfanuméricos y le ponga un tamaño por defecto al igual que hace SqlServer en las consultas auto parametrizadas. También sería recomendable una sobrecarga de AddWithValue que permita especificar el tipo de dato explícitamente.

Desde ayer teneis listo para descargar el Service Pack 2 de 2003 server por fin en castellano:

http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=1b9fe9e4-1d57-4698-a5cf-db271ed6d90a

Oh, si alguno tiene un ISA montado que eche un ojo, por si acaso a:

WARNING! Windows Server 2003 SP2 May Destroy Your ISA Firewall without Warning:

http://blogs.isaserver.org/shinder/2007/03/23/warning-windows-server-2003-sp2-may-destroy-your-isa-firewall-without-warning/

You cannot host TCP connections when Receive Side Scaling is enabled in Windows Server 2003 with Service Pack 2:

http://support.microsoft.com/default.aspx?scid=kb;EN-US;927695

 

Normalmente la tarea que más aumenta el tiempo de respuesta en la devolución de una página ASP.NET que consulta contra una BD es la propia consulta a la BD. Un escenario frecuente es la recuperación y presentación de datos con poca tasa de actualización. Para cada cliente que ejecuta la página se ejecuta de nuevo la consulta. Pese a que internamente SQL Server u otro gestor SGBD suele tener planificada las últimas consultas SQL realizadas y dispone de sus propios cachés, no tenemos la certeza de que así sea, sobre todo en momentos de alta carga de consultas, justo cuando más se necesita.

La primera aproximación para mejorar el throughput contra la BD es hacer las consultas solo en la primera petición de la página, en post backs sucesivos no se realiza la consulta.

    protected void Page_Load(object sender, EventArgs e)

    {

        if (!Page.IsPostBack)

        {

            BindData();

        }

    }

El método BindData() obtendría los datos de la BD y, por ejemplo, se los asociaría a un GridView , en peticiones posteriores  de la página por parte del mismo cliente no se ejecutaría este método, pero se seguirían mostrando los datos en el viewstate.  Este codelet significó una gran mejora de ASP.NET 1.0 frente ASP. En ASP.NET 1.0 se introdujo ya un conjunto de clases para gestionar un caché de datos y su política de invalidación. ASP.NET 2.0 mejora y extiende estas características.

Caché de datos global

Supongamos un escenario en el que tenemos una serie de datos en la BD relativamente inmutables y que utilizan en común todos los clientes de nuestra aplicación ASP.NET 2.0. Por ejemplo, una tabla de provincias o de empleados. Con la técnica del apartado anterior, cada vez que un nuevo cliente solicita o recarga la página que contiene un combo con la lista de provincias se ejecuta al menos una consulta contra la BD.

Puesto que los datos son comunes a todos los clientes de la aplicación podemos usar el caché de datos global. Este caché, a menos que se especifique lo contrario, tiene el mismo tiempo de vida y validez que el proceso de nuestra aplicación ASP.NET en el servidor. La afinidad del caché es a nivel de aplicación en un pool de IIS. Por lo que en un entorno de webfarming se tendría un caché global para cada aplicación en un pool dentro de un nodo del clúster.

El caché de ASP.NET en una arquitectura de tres capas

En los ejemplos que se pueden encontrar en la documentación de ASP.Net o en los tutoriales de www.asp.net se muestran ejemplos del uso del caché con datos en aplicaciones de una sola capa; dentro de una misma página ASP.NET se realizan las consultas a datos, su procesamiento y visualización.

Si bien es práctico en desarrollos de poco alcance, cuando se va a desarrollar una aplicación en un entorno empresarial es muy recomendable una arquitectura en al menos tres capas; Interfaz de usuario (UI), Lógica de Negocio (Business Logic, BL) y acceso a datos (Data Access, DA).

Si queremos emplear el caché global de datos en nuestra lógica de negocio observaremos que depende realmente de clases del interfaz de usuario pues el objeto Cache se obtiene o bien del objeto Page actual o bien del objeto HttpContext. No podemos usar estas clases en nuestra Lógica de Negocio porque se haría dependiente del interfaz empleado.  La capa BL debería servirnos para cualquier tipo de interface, WindowsForms, aplicación de consola, etc.

Para desacoplar las clases de caché dependientes de ASP.NET de la lógica de negocio, os propongo el siguiente diseño, basado en el patrón de diseño Estrategia (Strategy Pattern) [GAM97] y un patrón Adaptador  (wrapper) [GAM97] . Permite elegir varias estrategias de cache y el segundo abstrae el interfaz de System.Web.Caching.Cache .  Para el acceso a datos del modelo conceptual se utilizan DataSets en vez de objetos DTO junto a una clase DAO (Direct Access Object) que realiza las consultas al gestor  SqlServer.

El interfaz de usuario (UI) accede a la lógica de negocio a través de una fachada (Facade Pattern)[GAM97] que al carecer de estado es una clase de instancia única (Singleton Pattern)[GAM97]

 Diagrama 1. Diagrama estático de clases del ejemplo Sample.TestCache

Diagrama 1. Diagrama estático de clases del ejemplo Sample.TestCache

La página por defecto invoca con su método BindData() el enlace inicial de los datos a los controles visuales.

        private void BindData()

        {

            FacadeData facade = FacadeData.getWithCacheStrategy();           

            GridView1.DataSource = facade.Employees;

            GridView1.DataBind();

        }

En el fragmento de código superior vemos cómo invocando el método getWithCacheSrtategy() se obtiene de forma implícita una instancia de la fachada de acceso a la lógica de negocio y se solicita que utilice una estrategia de caché. La implementación concreta de ICacheStrategy se establece en el fichero web.config.

  <appSettings>

    <add key="CacheStrategyClass" value="Samples.TestCache.CacheStrategyAspNet, Samples.TestCache"/>   

  </appSettings>

La fachada tiene otros dos métodos para obtener una instancia de si misma;  getInstance(), que carece de estrategias de cache y getWithCacheStrategy(ICacheStrategy) que acepta desde código una estrategia de cache explícita.

 

        public static FacadeData getWithCacheStrategy(ICacheStrategy ipd)

        {

            if (instance == null)

                instance = new FacadeData();

            CacheStrategy = ipd;

            return instance;

        }

 

        public static FacadeData getWithCacheStrategy()

        {

            return getWithCacheStrategy(getInstanceCacheStrategy());

        }

        private static ICacheStrategy getInstanceCacheStrategy()

        {

            string strCacheStrategyClass =

                   WebConfigurationManager.AppSettings["CacheStrategyClass"];

           

            if (strCacheStrategyClass != null)

                return (ICacheStrategy) Activator.CreateInstance(

                                            Type.GetType(strCacheStrategyClass));                     

            else

                return null;

        }

La fachada también actúa en como proxy a la hora de recuperar los empleados. Se hace bajo demanda y devuelve el dataset del caché en caso de que así se haya configurado o directamente del DAO.

        public DataSet Employees

        {

            get

            {

                if (CacheStrategy != null)

                {

                    if (CacheStrategy.getData("cachedEmployees") == null)

                        CacheStrategy.setData("cachedEmployees", EmployeeDAO.getEmployees())

                    

   return CacheStrategy.getData("cachedEmployees");

                }

                else

                    return EmployeeDAO.getEmployees();

            }

        }

El interface de ICacheStrategy para este ejemplo, es muy sencillo:

    public interface ICacheStrategy

    {

       DataSet getData(string dataKey);

       void setData(string dataKey, DataSet data);

    }

La implementación para el caso de utilizar el caché de ASP.NET:

        public DataSet getData(string dataKey)

        {

            HttpContext context = System.Web.HttpContext.Current;

            return (DataSet) context.Cache[dataKey];

        }

 

        public void setData(string dataKey, DataSet data)

        {

            HttpContext context = System.Web.HttpContext.Current;

            context.Cache.Remove(dataKey);

            context.Cache.Add(dataKey, data, null, DateTime.Now.AddMinutes(MIN_TO_EXPIRE),

                   Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);

        }

El método setData podría haberse simplificado, sin embargo he preferido esta opción porque, entre otras cosas, se puede controlar el tiempo de invalidación del caché y su prioridad. La clase precisa del contexto HTTP para poder acceder al caché.

Si distribuímos en ensamblados las clases, en el del IU tendríamos las clases Default (página ASPX). En el ensamblado de la lógica de negocio estarían el resto de clases. Nótese que CacheStrategyAspNet tiene una dependencia indirecta a HttpContext.

Este ejemplo podría haberse realizado igualmente con objetos del modelo en vez de con DataSets.

Si ejecutamos la aplicación veremos que múltiples peticiones devuelven los mismos datos obtenidos en el mismo instante (columna  RetrievedAt). Incluso cuando el cliente recarga la página.  Solo cuando hace clic en “Refresh Data” se actualiza el caché.  Recordad que este caché es privado a la aplicación en ejecución, se comparte entre todas las sesiones que mantenga la aplicación dentro del pool del servidor IIS, si por ejemplo queréis hacer una consulta con parámetros dependientes de la sesión este esquema no os valdría.

Con este ejemplo hemos visto cómo desacoplar la tecnología de caché de asp.net de una fachada de nuestra lógica de negocio.

Queda abierta la discusión para el caso de querer crear un cliente ligero y llevar la lógica de negocio a un servidor distinto al de IIS, usando por ejemplo, Windows Communication Foundation.

Referencias

 [GAM97]            Design Patterns, Elements of Reusable Object-Oriented Software, Gamma et Al.  Addison-Wesley Eds. 1997. ISBN 0-201-63361-2

Pues resulta que algunos ipods han salido al mercado (desde apple) con un virus. El virus RavMonE está diseñado para S.O. Windows. Apple, textualmente ha dicho:

"As you might imagine, we are upset at Windows for not being more hardy against such viruses, and even more upset with ourselves for not catching it."

Como puedes imaginarte, estamos enfadados con Windows por no luchar más contra los virus con dureza  e incluso estamos más enfadados con nosotros mismos por no haberlo pillado.

Flipante, echando M. sobre Microsoft por no tener un antivirus... si es que le dan a la razón a Ms cuando dice que en Vista va a tener un papel más activo en la tecnología de antivirus.

Por otra parte... ¿qué hace apple con un windows para configurar sus ipods? al parecer  tiene un contratista que lo usa... eso ha dicho.

Dios me voy corriendo a pasarle AppleAntiVirus a mi ipod, no sea que por usar windows haya pillado el virus. Claro que si me entra no le echaré a la culpa al hijo *** que hizo el virus, ni a los despistados de PcCillin al que estoy subscrito, ni siquiera a apple por dejar alguna puerta abierta... noooo... la culpa es de microsoft... y de mi santísima madre desde el día que me parió. Flipante.

Finalmente, en bastantes blogs la gente está responsabilizando a apple y no les parece tampoco muy ético echar pestes sobre microsoft, podeis consultarlos en http://news.com.com/2061-11199_3-6127343.html?part=rss&tag=6127343&subj=news

 

 

¡A lo mejor me escuchan!

Bueno, después de comentar con Bruno las últimas novedades de Live con su OneCare. Se me ocurre una característica que desconozco si ya la tienen en mente pero que a mi y a unos cuantos nos resolvería bastantes problemas; os cuento:

Ahora, prácticamente todo el mundo, tiene una conexión a internet permanente en cualquier entorno de trabajo. Por otro lado yo tengo cuatro equipos donde desarrollo mi trabajo / ocio. El del trabajo de la mañana, el del trabajo de la tarde, el de casa y un portatil que llevo cuando hay fiestas fuera de casa.

Toda mi información y preferencias no ocupan más de 1GB-2GB. Evidentemente no incluyo películas o música, simplemente words, código de proyecto, excel, email, etc. Es bastante común que cuando entro en un ordenador tenga que ir por escritorio remoto a otro para coger los archivos que me hacen falta. No hablemos del correo, que me lo tengo que bajar por pop en 3 outlooks... con idénticas reglas y filtrando el correo no deseado por triplicado.

A mi me gustaría que mi perfil, con mis datos estuviese en un repositiorio global, que al iniciar sesión en live, se abriera como una carpeta y que automáticamente se cargasen mis preferencias en VisualStudio, Office, y Outlook.  Podrían tener una tecnología tipo shadow folders para prevenir caídas en la red y mejorar el rendimiento. Evidentemente esto se podría hacer para una intranet con tecnología actual, pero la realidad es que muchos de nosotros trabajamos en 3 sitios distintos dónde no tiene sentido tener la misma intranet. Por otra parte Sharepoint Team Services me arregla algo este problema pero no tiene la soltura y comodidad de lo que acabo de describir.

Es cierto que la mayoría de usuarios tendrían recelos de que sus archivos estén en un disco duro remoto que no controlan, pero es un argumento vacío desde que todos envíamos correo con archivos por hotmail o gmail. Creo que esa desconfianza está superada.

Lo dicho a ver si me escuchan... <<suspiro>> ... tendría que despreocuparme de llevar un pen drive o un hd portatil sincronizado.

 

Las empresas de antivirus para windows como McAfee, Symantec, etc. están un poco nerviosas con las nuevas características de seguridad de Windows Vista como el PatchGuard. En el artículo, un tanto capcioso, "Why Microsoft is wrong on Vista security" de George Heron (investigador jefe de mcafee) dice que la confianza en los antivirus se basa en que ha sido gestionada por terceras empresas y Microsoft siempre les dejó hacer... (les dejó hacer negocio, vamos) y que eso era una gran ventaja porque así protegían tanto a microsoft de sus propios bugs como a los usuarios ;-) pero con el nuevo enfoque de vista con protecciones como PatchGuard (impide hacer hooks a ciertas funciones del API/Kernel) hace que sea muy dificil de implementar antivirus a los "chicos buenos" y que, con lo "ineptos" que son en microsoft con sus "gravísimos" bugs de seguridad ellos ya no podrían hacer nada para que un montón de chicos malos se metieran en nuestros ordenadores por culpa de Ms, claro.

Menuda retórica. Todo para decir que se les acaba el pastel y que a ver si se movilizan los usuarios en contra de Ms para que las bondadosas empresas de antivirus puedan seguir facturando. Sinceramente, que alguien como el Sr. Heron se ponga las sábanas y haga del fantasma de la inseguridad de los operativos de microsoft solo demuestra lo preocupante de la situación.

De la misma forma que algún restrasado perteneciente a brigadas de extinción de incendios quemó el monte para crear la necesidad de contratarlo... deberían investigar la relación de las empresas de antivirus con la aparición de nuevos virus.

Que el colega Heron no nos toque la moral con los fantasmas de siempre. Que se pongan las pilas. Si las multinacionales que fabrican virus y antivirus están nerviosas es que hay algo bueno para los usuarios. Y de quien empiezo a desconfiar seriamente es de McAFee con artículos como estos. Habrá que tenerlos vigilados.

En este post os describo como montar un cluster o web farming de IIS con aplicaciones ASP.NET utilizando como balanceador de carga entre nodos del clúster un Linux Virtual Server (LVS).

Como sabeis, cuando crece el número de usuarios web el escalado interno de vuestro servidor suele resolver a corto plazo el problema de atender gran numero de peticiones. Pero si teneis éxito en vuestra web ;-) y empieza a subir exponencialmente el número de peticiones, vais a tener que buscar otra solución: el escalado externo.

Las técnicas de Clustering o Webfarming permiten añadir servidores web o nodos formando un conjunto de máquinas que se reparten las peticiones http/https y si una está muy cargada o no disponible, otro nodo operativo resuelve la petición. Al final se logran entornos con alta disponibilidad y tolerante a fallos. Para realizar el balanceo de carga puede optarse por soluciones software como LVS o NLBs o por soluciones hardware como conmutadores de capa 7, también llamados conmutadores de capa de aplicación.

La solución más óptima es la última pero es muchísimo más cara (entre $30.000 y $60.000). Mediante software se puede montar un balanceo muy económico (tanto por hardware requerido como por licencias software) utilizando LVS.

Ya puestos en materia los aspectos a considerar son:

1.         La instalación de un Linux con LVS.

2.         La configuración de red y LVS del clúster.

3.         La persistencia de la sesión de las aplicaciones web.

Como la mayoría de nosotros estamos un poco más acostumbrados a los servidores Windows, seré algo más extenso en la configuración de LINUX y LVS.

Para simular el entorno utilicé vmware sobre un Debian ya que solo disponía un equipo con 1GB de RAM y necesitaba ejecutar 3 Windows 2003 Server y un LVS.

Instalación de LVS

Existen bastantes recursos en línea de cómo instalar LVS, concretamente yo lo instalé sobre un Debian con kernel 2.4.7 y la distribución UltraMonkey de LVS. También hay productos comerciales como Piranha de RedHat. En el sitio web de UltraMonkey está bastante detallado el proceso de instalación en debian para diversos escenarios.

En esencia, no es muy complicada, pero en mi kernel tuve que compilar e instalar los módulos de IPVS (el core del LVS) manualmente con insmod y activar el reenvío de paquetes ip (packet forwarding). Por cierto, tiene tres mecanismos para hacerlo y dependen un poco de la infraestructura de red que tengamos: Rutado directo, encapsulación IP a IP y NAT. Por comodidad yo elegí NAT en mi entorno de vmware.

IPVS no es simplemente un IP forwarding con un algoritmo de balanceo de carga. IPVS reconstruye los datagramas IP (layer 3) en el mensaje de capa de aplicación (Layer 4), como por ejemplo mensajes HTTP, y se lo manda completo (en uno o más paquetes IP) al nodo que lo va a resolver. Esto, que parece una tontería, es muy importante porque si no podrían llegar peticiones HTTP incompletas y el clúster funcionaría intermitentemente.

Aquí podéis ver el esquema de la red del clúster que monté.

El servidor con LVS se suele llamar Linux Director (LD). El esquema presentado está pensado para balanceo de carga con alta disponibilidad. En el caso de precisar tolerancia a fallos en el Linux Director se podría montar un nuevo servidor gemelo y configurarlo según la documentación  para que actúe como respaldo del Linux Director principal.

Fijaos que el LD tiene dos interfaces de red, uno público de cara al exterior (10.200.11.128) y uno interno para los servidores del cluster (10.200.10.128). Los nodos del cluster (wincluster01 y wincluster02) tienen como puerta de enlace principal la 10.200.10.128, de esta forma las respuestas de los wincluster llegan al cliente remoto enmascaradas con la ip 10.200.11.128 y no con la IP del nodo del clúster que ha respondido. Esto es imprescindible si estamos manteniendo el seguimiento de la sesión con cookies (así lo hace por defecto ASP.NET) y no queremos que se cree una nueva sesión cuando cambia el nodo que responde.

El fichero de configuración del ldirectord es ldirector.cf, ahí es dónde reflejaremos la estructura de nuestro clúster.

# Global Directives

checktimeout=10

checkinterval=2

autoreload=no

quiescent=yes

# A sample virtual with a fallback that will override the gobal setting

virtual=10.200.11.128:80

            real=10.200.10.129:80 masq

            real=10.200.10.130:80 masq

            #fallback=127.0.0.1:80 gate

            service=http

            request="/TestCluster"

            receive=".NET"

            scheduler=rr

            #persistent=600

            protocol=tcp

            checktype=negotiate

 

En ese fichero definimos el servidor virtual, 10.200.11.128:80 y los nodos reales (en este caso 2).  Con fallback podemos definir un servidor de respuesta en caso de que todos los nodos estén caídos.

Lo que me resultó curioso fue la forma de comprobar cada dos segundos (CheckInterval=2) qué nodos del cluster están activos. No envía un simple ping, en el caso de un servicio http o https hace una petición a una página de pruebas (request="/TestCluster") y busca una expresión regular en ella (receive=".NET"). En caso de no obtener respuesta (checktimeout=10) o no sea la esperada marca el servidor como caído y manda la petición HTTP a otro lado del cluster. El algoritmo de balanceo para este clúster es RoundRobin (rr), pero hay muchos más. Por ejemplo, si tenemos servidores con distinta capacidad podemos usar Weighted Round Robin (wrr) y darle pesos adecuados (entre 0 y 1) a los nodos. A menor peso, menos peticiones a resolver.

¿Y cómo sabemos que todo marcha?, fácil, tenemos unas cuantas herramientas para ello. La primera es mirar el syslog y ver que el deamon ldirectord ha arrancado perfectamente. Luego podemos utilizar ipvsadm, nos informará de los nodos definidos, su peso actual (0 - inactivo, 1- activo) y el número de conexiones activas.

Además pueden consultarse estadísticas que serían representables con software de monitorización.

#ipvsadm

IP Virtual Server version 1.0.11 (size=4096)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  10.200.11.128:www rr

  -> 10.200.10.129:www            Masq    1      1          0        

  -> 10.200.10.130:www            Masq    1      0          1         

#ipvsadm -Ln --stats

IP Virtual Server version 1.0.11 (size=4096)

Prot LocalAddress:Port               Conns   InPkts  OutPkts  InBytes OutBytes

  -> RemoteAddress:Port

TCP  10.200.11.128:80                   16     1105      536   316196   732713

  -> 10.200.10.129:80                    8      690      337   194315   468421

  -> 10.200.10.130:80                    8      415      199   121881   264292

 

En caso de problemas os recomiendo que utilicéis tcpdump, aunque no os conozcáis el protocolo IP al detalle os puede ayudar a resolver problemas en la configuración de red o de ldirectord.

Una vez que el LD está funcionando vamos a configurar los nodos para que puedan ejecutar aplicaciones ASP.NET en el clúster.

Una vez que el LD está funcionando vamos a configurar los nodos para que puedan ejecutar aplicaciones ASP.NET en el clúster.

 

Persistencia de sesión

La sesión de las aplicaciones web contienen el contexto de ejecución del cliente remoto. Supongo que es un concepto que conocereis todos ;-). La sesión se almacena en la memoria del servidor, el cliente remoto utiliza cookies o parámetros en el POST para identificarse en el servidor en cada petición y que éste sepa asociar los contenidos de una sesión determinada con el cliente correcto.

Lo interesante es que en un clúster hay varios nodos y una petición http puede ser resuelta por cualquier nodo activo del clúster. Si el nodo del clúster no tiene en memoria los datos de la sesión del cliente, porque fue creada en otro nodo, tenemos un problema realmente grave ya que no se puede recuperar el contexto de la aplicación.

Existen dos enfoques para resolver peticiones http a una aplicación web dentro de un clúster y no tener el problema mencionado:

El primer enfoque utiliza afinidad de nodo (sticky sessions, persistent connections...) en esencia: si un nodo crea una sesión para un cliente, ese nodo resuelve todas las peticiones del cliente remoto para esa sesión.

Es una aproximación sencilla pero no hay tolerancia a fallos real. Si un nodo se cae, lo hace con las sesiones de sus clientes. Además si un nodo esta cargado los que hayan iniciado sesión allí no pueden moverse a otro nodo menos saturado.

En el segundo enfoque cualquier nodo del clúster puede responder la petición. Para eso es necesario que todos los nodos tengan acceso a los datos de las sesiones de los clientes.

Con ASP.NET es posible compartir la sesión de dos formas: en la memoria o en un SqlServer de un servidor al que preguntan los nodos para recuperar una sesión activa.

Esta aproximación sí es tolerante a fallos (respecto a los nodos del clúster) y mejora el rendimiento. Aunque hay un punto único de fallo. Para evitarlo sería necesario montar dos SqlServers para mantener la sesión con replicación.

En mi caso monté el gestor de sesiones en memoria dentro de un Server 2003. Basta con iniciar el servicio "Servicio de estado de ASP.NET". Si bien hay que tocar el registro de windows para indicarle que acepte conexiones remotas y reiniciar el servicio.

Concretamente hay que darle un valor positivo a la clave:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnection

Ahí también podéis cambiarle el puerto por defecto, que es el 42424.

Ahora hay que decirle a las aplicaciones ASP.NET dónde deben gestionar la sesión.

Para .Net 1.1 hay que tocar el web.config de la aplicación y modificar la forma de mantener la sesión:

    <sessionState

            mode="StateServer"

            stateConnectionString="tcpip=10.200.10.131:42424"

            cookieless="false"

            timeout="20"

    />

Para probarlo cree una pequeña página .aspx que incrementa una variable de sesión en cada invocación e informa del nombre de la máquina que resuelve la petición junto con el código de sesión.

Esta aplicación debe montarse en el mismo directorio virtual de IIS en todos los nodos. En una ejecución típica y para el entorno descrito podemos obtener resultados como los que siguen.

Petición a http://10.200.11.128/TestCluster

Postback 1:

Fecha:  13/09/2006 10:19:06

Servidor: WINCLUSTER01 (10.200.10.129)

Sesión: epeh12552w2blr45u2nhur2e (nueva)

Timeout: 20 min.

Counter (sesión): 1

Postback 2:

Fecha: 13/09/2006 10:19:07

Servidor: WINCLUSTER02 (10.200.10.130)

Sesión: epeh12552w2blr45u2nhur2e (vieja)

Timeout: 20 min.

Counter (sesión): 2

En el primer post se crea una nueva sesión y la crea el nodo WINCLUSTER01. El contador, que se almacena en la sesión, comienza en 1. Se hace una recarga de la página y el segundo post es contestado por el nodo WINCLUSTER02. Se observa que la sesión es la misma y se ha incrementado el counter almacenado en la sesión.

Hice varias pruebas tumbando algún nodo del clúster y no hubo ningún problema. En cuanto el servidor Linux Director detecta que un nodo no le devuelve la página de pruebas le asigna un peso 0, lo descarta y busca otro nodo según el algoritmo de balanceo. El cliente sufre un retraso de 1 segundo pero recupera el flujo normal de la aplicación.

Conclusiones

El aumento de tráfico de un sitio web solo puede ser atendido con una solución basada en clústers. Lo ideal es garantizar alta disponibilidad y tolerancia a fallos. En este artículo se describe una solución de clustering para ASP.NET basada en tecnología heterogénea (Linux/OpenSource, Windows/Microsoft). Utilizando Linux/LVS puedes dedicar hardware relativamente antiguo como equipo LinuxDirector y evitarte alguna licencia de 2003 para hacer NLB.

Las ventajas de clustering son la alta disponibilidad y una tolerancia a fallos total o parcial (dependiendo del esquema elegido y los puntos de fallo únicos). Incluso con un número pequeño de usuarios, si la aplicación es crítica, merece la pena optar por un esquema como el presentado.

Queda para un futuro una prueba de rendimiento de NLB y LVS

Espero que os haya gustado! y si alguna parte os queda poco clara u oscura comentádmelo y amplío el post.

No parece una novedad, pero es que os estaba preparando un post sobre clustering con LinuxVirtualServer e IIS 6.0 y se me ocurrió hacerlo con el editor de textos AbiWord.. cuando tenía medio artículo escrito pulsé no sé que teclas y me salió un error irrecuperable y que si me apetecía informar a los programadores. No lo consideré necesario (no sea que ese proceso se cargase algo más)... perdí bastante texto... y eso que guarda una copia de seguridad automática (que ni te informa de que lo ha hecho o te la recupera al abrir de nuevo el programa).

Pues eso, que sigo con el Post... pero que me fío más del Word 2007 Beta2.

Chao chiquilines

 

 

Yo tengo imán para que las instalaciones de CTPs y BETAs casquen. La última es la de Atlas en su CTP de Julio 2006. Al ejecutar ASPNETAtlas.vsi en la instalación se recibe el mensaje de error "La longitud de la cadena no puede ser cero". El resultado final es que no instala / registra las plantillas de proyecto para VS.2005. Para solucionarlo debeis meter en vuestro registro las dos claves siguientes:

En:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSEnvCommunityContent\ContentTypes\VSTemplate

Meteis:
"ClassName"  con "VSContentInstaller.VSTemplateInstallerPage" y "DisplayName" con "#VSTemplateContentDisplayName"

Volveis a ejecutar ASPNETAtlas.vsi que en mi caso anda por la carpeta C:\Archivos de programa\Microsoft ASP.NET\Atlas\v2.0.50727

Feliz Agosto! 

Más artículos Página siguiente >