HOT:Inserciones Masivas en MongoDB vs Sql Server (II)
DESCARGO DE RESPONSABILIDAD
Antes de empezar la entrada me gustaría dejar claro que esto es un pasatiempos, una forma de dilucidar quien pagaba las cenas durante el tiempo que nos toco a los dos estar perdidos en una ciudad que no es la nuestra con nuestros respectivos trabajos. La idea de esta serie de blogs no es ver si queremos más a Papa o a Mama, a Sql Server o a una NoSQL. Los resultados que se obtengan no tienen porque hacer pensar que una es mejor que la otra ( sobre todo cuando son compatibles) y por supuesto el performance no es una(la única) razón de selección de una NoSQL como Mongo, escalabilidad, schema-free, base, last-update ni de un sistema transacciónl como Sql Serve. Si esto te queda claro y quieres jugar con nosotros, y por supuesto, aprender como lo hemos hecho nosotros, a forzar situaciones como la presente tanto de Sql Server como de Mongo entonces sigue leyendo…
Bueno, seguro que muchos habéis leido la entrarada de mi compañero de batallas y amigo Pablo Doval sobre un pequeño pique “paga cenas” a ver quien insertaba de una forma más rápida 500 K de registros. En la primera entrada de mi contrincante hemos podido ver como su primer esfuerzo ha resultado en 4 segundos escasos, quiero imaginarme que con escasos no se refiere a 0,9 por lo que entenderé que mi primer reto es superar simplemente 4 segundos. Bien, 4 segundos, no está nada mal, sobre todo porque teniendo en cuenta lo que ha utilizado me temo que tiene un gran arco de mejora ( aunque no seré yo quien se lo diga, más que nada porque de Sql Server el ya sabe mejor que yo …). Vamos a ver que podemos hacer con nuestro jueguete, por supuesto, no empezaremos haciendo ninguna optimización, lo más simple, así tendremos una ligera idea de por dónde andamos.
El primer intento
Tal y como acabo de comentar el primer intento será lo mas simple posible, insertaremos en MongoDB 500K documentos. Si, documentos, para los que no lo sepais, MongoDb es una base de datos documental, entendiendo como esto a algo similar a lo que podéis ver en la imagen de la derecha. Las normas de nuestra pequeña apuestas decían que teníamos que in
sertar 500K documentos ( el filas ) con un tipo de datos Guid y una cadena con 20 caracteres. Por lo tanto, abriremos un proyecto de Visual Studio 2010 y vamos a empezar a jugar. Lo primero será agregar las librerías necesarias para trabajar con Mongo DB, para ello podemis ir hacia la sección de drivers de Mongo DB y descargar los de C# o mejor aún utilizar el paquete de NuGet que esta gente tiene disponible, concretamente el paquete se llama mongocsharpdriver.
Levantando el servidor
Lógicamente, antes de conectarnos tenemos que levantar un servidor de MongoDB, para ello, por ahora, no utilizaremos ninguna setting especial, de hecho, lo levantaremos como una consola y no como un servicio de Windows ( si, ya se que eso puede tener un impacto, veremos si es cierto… ). Es decir, levantaremos el servidor con las simples instrucciones siguientes:
mongod.exe –dbpath c:\mongo\data [c:\mongo\data es el path para alojar la base de datos]
Ahora, es hora del código, con las siguientes lineas voy a acceder a una colección llamada tests en una base de datos llamada apuesta dónde posteriormente intentaré introducir los 500 K registros.
static MongoCollection GetCollection()
{
var server = MongoServer.Create("mongodb://localhost");
var database = server.GetDatabase("apuesta");
return database.GetCollection("tests");
}
Ahora, solamente me queda darle a mi bucle y esperar los resultados para ver desde dónde vamos a empezar a jugar, espero sinceramente que no sea muy alto porque 4 segundos es un muy buen número. Mientras me preparaba para hacerlo me puse a pensar si lo haríamos de forma tipada o no. Es decir, el API de MongoDB me permite definir los distintos documentos mediante clases que posteriormente el serializará al formato de trabajo. Aunque no quería hacer ninguna optimización temprana me parecía que esto sería un poco estúpido y por lo tanto trabajaría directamente con documentos no tipados, es decir, con BsonDocuments.
static void Main(string[] args)
{
var collection = GetCollection();
var watch = Stopwatch.StartNew();
for (int i = 0; i < 500000; i++)
{
collection.Insert(new BsonDocument()
{
{"Guid",Guid.NewGuid()},
{"Data","XXXXXXXXXXXXXXXXXXX"}
});
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
Console.ReadLine();
}
……………………. ( redoble de tambores)… 18,74 segundazos…… bufff, más de 4 veces que el mejor número de Pablo. Lo primero que hago es comprobar que no he comentido un error, que todos los datos estan en la base de datos, por lo que me conecto a mongo y hago una revisión del numero de documentos y del formato que contienen, en la siguiente imagen podéis ver los pasos que he seguido desde la shell de MongoDb para hacerlo.

….. unos ínfimos cambios y otro disparo….
Bien, estamos lejos, pero la verdad, teniendo en cuenta como lo hicimos, tampoco es que me asustará demasiado, hay dos cosas que podemos arreglar rápidamente, la primera es que cuando levantamos el servidor de Mongo como consola no desabilitamos el stdout ni el nivel de verbosidad, por lo tanto, segurmanente eso esté influyendo bastante. La segunda es que mongo dispone de un sistema de batching por el cual podemos insertar un conjunto de documentos de forma simultanea.
Empezaremos los cambios por levantar una instancia del servidor de mongo como un servicio de windows con la siguiente parametrización:
mongod –-install [lo instalamos como un servicio]
--diaglog 0 [deshabilitamos los diagnostics log]
--quiet [reducimos la verbosidad de standard out
--nojournal [ eliminamos la feature introducida en 1.8 para asegurar recuperación de datos no insertados en caso de crash]
Ahora, le toca la parte al código, vamos a hacer el batching, para ello, modificamos nuestro código de inserción por el siguiente:
static void Batching()
{
var collection = GetCollection();
int max = 500000;
int batch = 1000;
int iterations = max / batch;
int count = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
var data = new BsonDocument[batch];
while (count < batch)
{
data[count] = new BsonDocument()
{
{"g",Guid.NewGuid()},
{"d","XXXXXXXXXXXXXXXXXXX"}
};
++count;
}
collection.InsertBatch(data);
count = 0;
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
Console.ReadLine();
}
Ahora tomamos el tiempo y es ….5.005 segundos…. nada mal, si señor, estamos cerca ya de nuestro amigo…. pero aún falta ganar un segundo por algun lado. La primera parece pensar en jugar con los tamaños del batch pero después de algunas pruebas la mejora no es substancial como para ponerla, en mi máquina mi mejor respuesta es con un tamaño de 600 y a partir de 1500 los tiempos se hacen muy grandes. Por lo que parece que se mueve mejor con batch pequeños de estos documentos que muy grandes…
…tercer disparo y Pablo paga la cena (hoy)
Hay que ganar un segundo, ese es el reto ahora. Como lo hacemos?? bueno, a ver, vamos a revisar las opciones de inserción para ver si podemos tunear algun parámetro. Si, nos fijamos en las sobrecargas de InsertBatch, veremos que podemos incluir unas opciones de insercion por medio de la clase MongoInsertOptions. Si revisais esta clase, veréis que hay tres parámetros importantes, CheckElementNames,SafeMode y Flags. El primero, nos permite decidir si queremos comprobar los nombres de los documentos, claves, collecciones etc antes de procesar un insert, el segundo permite especificarnos el modo de seguridad por defecto y el tercero el comportamiento en caso de fallo. Vamos a tunear estas opciones con nuestros batch y a ver que resultado tenemos….
var insertOptions = new MongoInsertOptions(collection);
insertOptions.CheckElementNames = false;
insertOptions.SafeMode = SafeMode.False;
insertOptions.Flags = InsertFlags.ContinueOnError;
…youhuuuuu 3.50 segundos… Pabloooooooooooooooooooooooooooooooo pagas la cena!!!!!!!!!!!!!!!!!
…aún así, sigo preocupado
Si, sigo preocupado porque Pablo tiene buenas posibilidades de mejora y yo, viendo las estadisticas de mi Mongo, siguiente imagen, veo que si quiero bajar de esos tiempos tengo que cambiar algún concepto y usar algo más de artillería… pero no se si me llegará…. en fin, ya veremos si supera los 3.50 y por cuanto lo supera…

Nos veremos en la siguiente…
Unai