[Windows Phone 7.5] Inserciones masivas en SQL Server CE vs SQL Server vs MongoDb (y II)

Vuelvo a la carga! Después de una retirada para curar nuestras heridas, vamos a continuar la batalla, tengo sorpresas buenas y no tan buenas que iremos viendo a lo largo del artículo. Si no has visto la primera parte y no sabes de que estoy hablando… no me he enrolado ni he perdido definitivamente la cabeza, echa un vistazo aquí.

Missile Three

Vamos a recordar cuales fueron los últimos resultados de nuestro Missile Two:

image_thumb20

Nos quedamos con unos tiempos en el emulador de 3 minutos y 48 segundos, el HTC Mazaa llegó a los 28 minutos y 33 segundos y el Nokia Lumia 800 a los 26 minutos y 21 segundos. Lo que vamos a intentar para optimizar los resultados es ejecutar nuestro código en un hilo diferente al de la interface de usuario, para de esta forma no perjudicar nuestro rendimiento y porque en una aplicación real sería lo que tendríamos que hacer. Simplemente usamos la clase ThreadPool para ejecutar nuestras inserciones en un nuevo hilo:

ThreadPool.QueueUserWorkItem((Object) =>
{
    using (DbContext context = new DbContext("Data Source='isostore:/Db.sdf'; Max Database Size = 128; Max Buffer Size = 4096;"))
    {

        for (int i = 0; i < 500000; i++)
        {
            TestTable insert = new TestTable();
            insert.Id = Guid.NewGuid();
            insert.Payload = load;
            context.Test.InsertOnSubmit(insert);
        }

        context.SubmitChanges();
        watch.Stop();
        this.Dispatcher.BeginInvoke(() => { TotalTime.Text = watch.Elapsed.TotalMinutes.ToString(); });
    }
});

Por sí mismo, este simple cambio ya nos ofrece una ganancia en el tiempo de ejecución:

image

El emulador ha descendido hasta los 3 minutos y 30 segundos, 18 segundos de mejora, mientras que los dispositivos han bajado hasta los 25 minutos y 9 segundos el HTC (más de 3 minutos más rápido) y hasta los 22 minutos y 48 segundos el Nokia Lumia 800 (también otros más de 3 minutos de ganancia). Está claro que el hilo de ejecución principal estaba consumiendo tiempo en otros menesteres y restándolo a nuestro proceso. ¿Podemos mejorarlo más?

Nuclear Strike

Vamos a probar otra cosa distinta partiendo de la base de ejecutarnos en un hilo propio:

Hasta ahora, mi clase TestTable tenía una propiedad de versionado, que mejora el rendimiento a la hora de consultar y actualizar registros:

[Table(Name = "Test")]
public class TestTable
{
    [Column(IsPrimaryKey = true)]
    public Guid Id { get; set; }

    [Column()]
    public string Payload { get; set; }

    [Column(IsVersion = true)]
    public Binary version { get; set; }
}

Pero, ¿Que efecto tiene sobre las inserciones? Pues estamos insertando más datos además de nuestra cadena y nuestro GUID. Además de mejorar el rendimiento de actualización, ¿afectará positiva o negativamente al rendimiento de las inserciones? Pues…

image

Si, si que influye y mucho! en el caso del Nokia Lumia 800, quitando la columna de versión hemos descendido hasta los 17 minutos y 42 segundos… ¡5 minutos más rápido! también hemos mejorado tanto en el emulador como en el HTC Mazaa aunque de forma más comedida.

Y ya solo nos queda otra pequeña cosa por probar… Hasta ahora estamos usando el método InsertOnSubmit para insertar cada elemento en nuestro contexto a la espera de llamar al método SubmitChanges. Existe otro método, llamado InsertAllOnSubmit que nos permite pasarle una colección de objetos a insertar, ¿Mejorará el rendimiento? Vamos a ver como usarlo:

string load = "XXXXXXXXXXXXXXXXXXXX";

Stopwatch watch = Stopwatch.StartNew();
List<TestTable> collection = new List<TestTable>();
ThreadPool.QueueUserWorkItem((Object) =>
{
    using (DbContext context = new DbContext("Data Source='isostore:/Db.sdf'; Max Database Size = 128; Max Buffer Size = 4096;"))
    {

        for (int i = 0; i < 500000; i++)
        {
            TestTable insert = new TestTable();
            insert.Id = Guid.NewGuid();
            insert.Payload = load;
            collection.Add(insert);
        }
        context.Test.InsertAllOnSubmit(collection);
        context.SubmitChanges();
        watch.Stop();
        this.Dispatcher.BeginInvoke(() => { TotalTime.Text = watch.Elapsed.TotalMinutes.ToString(); });
    }
});

Simplemente debemos ir guardando nuestras instancias en una colección y pasarlas todas juntas al InsertAllOnSubmit, ¿El resultado?

image

Pues efectivamente, al usar InsertAllOnSubmit mejoramos los tiempos, más de 1 minuto y 30 segundos en el Nokia Lumia 800 y unos 3 minutos en el caso del HTC Mazaa, el emulador solo mejora en 18 segundos.

¿Como ha mejorado la situación de forma global desde que empezamos con nuestras primeras inserciones? He preparado un grafico profesional, profesional para que no digáis que no me lo trabajo:

image

Como podemos ver, hemos mejorado notablemente el tiempo en todos los dispositivos, en el caso del Nokia Lumia 800 hemos pasado de un tiempo inicial de 29 minutos y 20 segundos a un tiempo final de 16 minutos y 4 segundos. El HTC Mazaa ha pasado de 37 minutos a 20 minutos y 1 segundo. El emulador no ha sufrido una mejoría menos aguda, pasando de 4 minutos y 36 segundos iniciales a 2 minutos 30 segundos. Es sorprendente, en todos los dispositivos hemos logrado de media un 45% de mejora!!

Lecciones aprendidas

Al igual que en la primera parte, este artículo ha servido como un simple conducto por el cual explorar un poco más a fondo SQL Server CE, es hora de ver que hemos aprendido:

  • Siempre, siempre, siempre: USA UN HILO SECUNDARIO para tus procesos, el usuario te lo agradecerá y tu aplicación también, ya no es solo una cuestión de que la interface de usuario responda, hemos dejado claro que además te liberará de los trabajos que está haciendo la UI y hará que tus procesos sean más rápidos.
  • Aunque la columna de versión nos va a mejorar el rendimiento en las recuperaciones de datos o en las actualizaciones, ten cuidado, penaliza el rendimiento de las inserciones. Puede que en pocos registros no se note, pero si realizas inserciones masivas, perderás rendimiento.
  • Además de InsertOnSubmit, disponemos de InsertAllOnSubmit que nos ofrece mayor rendimiento a la hora de insertar en la base de datos múltiples registros en una sola operación.

Conclusión

The Winter is coming… bueno, bastante por hoy, con una reducción del 45% creo que hemos conseguido optimizar muy bien nuestro código de inserción… ¿Se puede hacer más? Siempre se puede hacer más, pero eso, es otra historia.

Dejo por aquí el código de mi Nuclear Strike para que lo tengáis como referencia.

Un saludo, Happy Coding y Feliz Navidad!

Published 21/12/2011 19:14 por Josué Yeray Julián Ferreiro
Archivado en: ,,
Comparte este post:

Comentarios

# re: [Windows Phone 7.5] Inserciones masivas en SQL Server CE vs SQL Server vs MongoDb (y II)

Thursday, December 22, 2011 9:53 AM por Mario Cortés Flores

Muy bueno!!!!

# re: [Windows Phone 7.5] Inserciones masivas en SQL Server CE vs SQL Server vs MongoDb (y II)

Thursday, December 22, 2011 11:47 AM por Juanma

No sé si vas a seguir con la serie, pero por si te animas, seguramente si pasas de EF y usas SqlCommand "a pelo" el rendimiento mejore bastante, y si usas TableDirect en lugar de lanzar inserts, es posible que mejore aún más.

# re: [Windows Phone 7.5] Inserciones masivas en SQL Server CE vs SQL Server vs MongoDb (y II)

Thursday, December 22, 2011 11:53 AM por Josué Yeray Julián Ferreiro

Gracias Mario! :)

Juanma, en Windows Phone 7.5 se usa Linq 2 SQL y no tienes la opción de usar SqlCommand a "pelo". Todo lo que hagas, sea lectura o escritura va por medio del contexto de datos y Linq.