MongoDb y c#. Dos titanes en lucha constante

Durante la semana pasada tuve la suerte de compartir unos momentos hablando de MongoDb con @erincon y para resumiros  a grandes rasgos os digo lo que aconseje equivocadamente a Eladio.

1. Utiliza el Driver nativo de Mongo

http://www.nuget.org/packages/mongocsharpdriver/1.8.3

2. Utiliza MongoRepositorio

http://www.nuget.org/packages/MongoRepository/

3. Utiliza Linq

En el primer y segundo punto no me equivoque, fue realmente en el tercero donde metí la pata hasta dentro y ese es el motivo del post, evitar en la medida de lo posible que ningún otro se equivoque.

Uno de los link que pase a Eladio no fue otro que la documentación oficial de mongo que habla de linq, que yo no había leído, exceso de confianza.

CSharp Driver LINQ Tutorial

Yo creo que no pasaron más de 10 minutos cuando me respondió con  esto.

image

Para mi mismo dije yo. ¡Olé,olé,olé!. Acabo de triunfar, menos mal que la parte que utilizo de MongoDb en la app no se ve fuertemente afectada por esto, pero piensa  que en vez de manejar 5 documentos en mi caso, sean muchos mas y entonces es cuando de verdad te tienes que preocupar, así que si estás utilizando Linq to MongoDb ponte a llorar.

Y llorar por qué? Pues sencillo pongas lo que pongas en tu proyección(select) vas a leer del servidor absolutamente todo, es decir que si tu quieres solo dos campos del documento y este tiene 50 te vas a traer los bytes de esos 50 campos. Vamos para entendernos si piensas en relacional un “SELECT * FROM” por cada sentencia Linq que escribas contra MongoDb.

Vamos a ver si lo que dice la documentación es verdad, porque honestamente no podía pensar que algo se hiciese tan mal, para ello lo primero que hice es descargar el código fuente del Driver de MongoDb y pasar de utilizar MongoRepository.

Con lo cual fue fácil escribir el siguiente código.

image

Y crear un Dto con todos los campos de la entidad Cards menos uno.

image

Pues al dar ejecutar a esas líneas y lógicamente iterar por la query me empieza a saltar las sospecha que  la documentación era correcta y mi visión acerca de utilizar Linq con MongoDb era incorrecta.

Como prueba nada mejor que encontrarme con esto.

image

Y realmente quien me estaba engañando. El Driver de Mongo o MongoRepository, pues es sencillo quien me estaba engañando era MongoRepository y lo vamos a ver con un ejemplo.

 

image

La forma de consumir el método GetAll de CardsRepository sería de la siguiente forma.

image

Que a la postre es lo mismo que si hubiese utilizado eso con Linq to Mongo sin utilizar MongoRepository y también como se puede observar hubiese sido engañado.

image

Bueno pues evidentemente me queda otra comprobación ver realmente los bytes que me llegan del servidor, utilizando Linq y el Driver de Mongo.

Realmente lo tenía fácil, simplemente era aprovechar la exceptión para saber exactamente donde tenía que poner un breakpoint, para ver el buffer directo que me llegaba del servidor y de esta forma obtener varias conluciones, uno es que no utilices Linq en Mongo y el otro era entender BSON y llegar a la deducción que realmente no es más que el modelo que utilizan otras bb.dd como Sql Server para transferir datos desde el servidor al cliente y creerte que MongoDb no utiliza Json sino binario del bueno.

Si quieres perder el tiempo como yo lo he hecho, no tienes más que ir al método ReceiveMessage<TDcoument> de la clase MongoConection o ir a la clase MongoCursor<TDocument> al metodo GetEnumerator y hagas todo el seguimiento hasta llegar al primer método, eso quizá te dará una visión más clara de que el Driver de Mongo para c# tiene como poco muchas cosas que mejorar.

Es decir no se te ocurra iterar dos veces por la misma query sin hacer ToList(), puesto que vas a hacer dos peticiones inútiles al servidor,para mi una mala implementación.

Pues una vez que estamos ya con el control en nuestras manos lo mejor es leer bytes y de esa forma poder explicar que es lo que esta pasando entre bastidores.

1. Cualquier consulta que tu envías a MongoDb ejecuta los siguientes comandos de Mongo Db y todo en binario, los request los dejo para que los investigues tu.

db.runCommand({“ping”:1})

db.runCommand({“ismaster”:1})

db.runCommand({“buildinfo”:1})

Por ultimo tu Query.

2. En el response desde el servidor nos llega la siguiente información.

1. Un header con 36 bytes

Longitud del Mensaje(int) 4 bytes.

RequestId(int) 4 bytes.

ResponseTo(int) 4 bytes.

OpCode(int) 4 bytes.

ResponseFlags(int) 4 bytes.

CursorId(long) 8 bytes: Si tu query responde con un gran tamaño de información Mongo devuelve un cursor y la siguientes petición las hace con este cursor, en caso contrario devuelve 0. Esto se puede parametrizar si se establece BatchSize en el objeto Cursor.

StartingFrom(int) 4 bytes: Numero de registro inicial leído en en este lote.

NumberReturned(int) 4 bytes: Numero de registros devueltos en este lote.

Como podéis observar tenemos fácil comprobar que nos llega con Linq y que nos llega utilizando el Driver de Mongo, leemos los primeros 4 bytes y sabemos que número  de bytes nos devuelve mongo para ese lote.

Para el primer lote que no lo he especificado con BatchSize me llegan para Linq con 101 registros 10944 bytes y utilizando la siguiente consulta en Mongo.

var result = collection.Find(null).SetFields(Fields.Include(«ApplicationId»));

Para los mismos registros me llegan 6702 bytes es decir un 40% más de peso utilizando Linq. Pues imagina ahora un documento con más de 50 campos y el problema no es otro que la documentación es correcta y yo no la leí y LINQ TO MONGO deja mucho que desear .

Y que nos falta por analizar a estás alturas pues el cuerpo/body del mensaje, es decir donde nos llegan los datos.

-4 bytes que indican la longitud del documento que voy a leer

-1 byte de inicio de documento que siempre es 2 y se corresponde con la enumeración

-1 byte del tipo de dato

-En el nombre del campo no llegan comillas es decir solo el nombre del campo terminado con el byte ‘0’, cuidado con hacer dinámico el nombre de los campos que te puede caer una gorda y alguien hacer una inyección con Null Byte. Tranquilos que he visto que lo controla el Driver Sonrisa.

-Si es un string el numero de bytes a leer más el valor en utf8 más final de cadena ‘0’ , si el tipo es diferente el valor.

Y ahora no te da por pensar en una cosa, menudo desperdicio de bytes, porque no nos llega el esquema en la cabecera, pues sencillo ese el coste que tienes que pagar por tener registros de longitud variable, no hacías palmas cuando leíste en la documentación de Mongo que el esquema no es fijo. La verdad es que se podrían esforzar un poquito más y cuando se especifique SetFields el esquema de una proyección se conoce y por tanto cambiar la forma de devolver bytes del servidor y mi experiencia me dice que si esto se tiene en cuenta la reducción puede alcanzar un 25% más.

Por último el Orden de los campos y no lo se a nivel de servidor si tiene repercusiones a nivel de rendimiento, da lo mismo como lo marques en el cliente, los campos para cada documento llegan en el orden en el que se grabaron en el servidor.

Conclusiones.

Solo una que por una vez que me he abstraído del funcionamiento de una bb.dd la he cagado, con lo cual apúntate esto como una cosa que tienes que hacer antes de trabajar con cualquier bb.dd, que parece que es lo menos importante y realmente es donde vas a tener los problemas. Y las abstracciones sobre esta, pueden llegar a ser malas como en este caso.

Por último y es por si no te ha quedado claro no utilices LINQ TO MONGO. La verdad que estoy pensando en no utilizar LINQ para nada relacionado con Acceso a datos, pero eso lo dejo para otra entrada o mejor si te parece lo puedes ver en el #gusenet en la charla que se titula “Dos tontos muy tontos”. Es la última así que o te quedas o no te vas a enterar Sonrisa.

Mis agradecimientos a:

1. Eladio Rincon por volver a hacerme pensar.

2. Marc Rubiño por responderme por twitter.

3. Ruben Ferandez por presentarme a Charlas Cylon que nos dejo claro alguna que otra cosa interesante por twitter.

4. Modesto San Juan que me dejo muy claro una cosa y es que no utilices LINQ EN MONGO

Se me olvidaba lo mejor y es que lo mismo en mi WebApi devuelvo un MongoCursor<BsonDocument> y me olvido de por vida con Mongo de crear Dtos, investiga esto que yo ya lo he hecho y posible es Sonrisa.