Batch size and StatelessSession in NHibernate
Hoy vamos a comprobar dos cosas:
Nuestro dominio será el siguiente
public class CrudTest { public virtual Guid Id { get; set; } public virtual string Description { get; set; } }
El fichero de mapeo
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateInfo.Console" namespace="NHibernateInfo.Console.Domain"> <class name="CrudTest" table="CrudTest"> <id name="Id" column="Id" type="guid" > <generator class="assigned" /> </id> <property name="Description" length="200" not-null="true" /> </class> </hibernate-mapping>Test 1 : Insertamos 20mil objetos sin usar Batch size ni StatelessSession
Configuration configuration = new Configuration().Configure("NHibernate.config"); ISessionFactory factory = configuration.BuildSessionFactory(); Stopwatch time = Stopwatch.StartNew(); using (ISession s = factory.OpenSession()) using (s.BeginTransaction()) { foreach (var testObject in CreateTestObjects(20000)) s.Save(testObject); s.Transaction.Commit(); } System.Console.WriteLine("Elapsed {0} ms", time.ElapsedMilliseconds);
Para crear los objetos hacemos uso del siguiente metodo
private static IEnumerable CreateTestObjects(int count) { for (var i = 0; i < count; i++) yield return new CrudTest { Id = Guid.NewGuid(), Description = Guid.NewGuid().ToString() }; }Elapsed 2699 ms
Test 2: Activamos el batch size
<property name="adonet.batch_size">100</property>Elapsed 1600 ms
Test 3: Activamos el StatelessSession
Configuration configuration = new Configuration().Configure("NHibernate.config"); ISessionFactory factory = configuration.BuildSessionFactory(); Stopwatch time = Stopwatch.StartNew(); using (IStatelessSession s = factory.OpenStatelessSession()) using (s.BeginTransaction()) { foreach (var testObject in CreateTestObjects(20000)) s.Insert(testObject); s.Transaction.Commit(); } System.Console.WriteLine("Elapsed {0} ms", time.ElapsedMilliseconds);Elapsed 1191 ms
Conclusiones (resumidas de los posts de Davy Brion y de la documentación de NH)
Hay que poner especial atención al usar la Session para inserciones masivas. Podemos tener problemas de 'OutOfMemoryException' ya que NHibernate cachea todas las instancias en la cache de nivel de session. Una solución propuesta por la documentación de NHibernate es la de ir volcando y liberando memoria cada ciertos objetos creados, en función del parámetro que le hayamos puesto al parámetro ADO batch size.
Asignar un valor adecuado al parámetro de configuración adonet.batch_size. El razonable deberá estar entre 10 y 50 (el 100 del ejemplo está puesto a modo didáctico).
Con la activación del batch_size a 100 conseguimos agrupar las sentencias de 100 en 100. Esto permite reducir los accesos a base de datos de 20.000 a 200, lo que se traduce en una mejora en cuanto al rendimiento.
Esto solo aplica a sentencias "insert", "update" y "delete", siempre y cuando los generadores de identidad no sean de tipo post-insercion. Si usamos generadores de ese tipo, NH necesitará acceder a la base de datos para recuperar el identificador, por lo que esta agrupación no aplicaría para los "insert"; aunque sí sería válido para los "update" y "delete". En la documentación dicen lo siguiente "Note that NHibernate disables insert batching at the ADO level transparently if you use an identiy identifier generator"
Si deseamos agrupar varias querys en una sola llamada a base de datos, podemos usar el soporte MultiQuery de NHibernate, que sirvió de base para las características de Future<T> y FutureValue<T>
Por último, si nos piden que hagamos operaciones de insercción masiva partiendo de un modelo que ya tenemos implementado (infrastructura, mapping file, etc) habría que planterse la opción de hacerlo con IStatelessSession. El hecho de no usar la session de NHibernate provoca que el proceso gestione menos memoria (ya que no almacena valores en la cache de primer nivel ni lanza los eventos asociados) y es una opción bastante considerable a tener en cuenta.
No hay comentarios:
Publicar un comentario