MongoDB: qué es, cómo funciona y cuándo podemos usarlo (o no)

Descripción y licencia

MongoDB es un sistema de base de datos multiplataforma orientado a documentos, de esquema libre. Como ya os expliqué, esto significa que cada entrada o registro puede tener un esquema de datos diferente, con atributos o “columnas” que no tienen por qué repetirse de un registro a otro. Está escrito en C++, lo que le confiere cierta cercanía al bare metal, o recursos de hardware de la máquina, de modo que es bastante rápido a la hora de ejecutar sus tareas. Además, está licenciado como GNU AGPL 3.0, de modo que se trata de un software de licencia libre. Funciona en sistemas operativos Windows, Linux, OS X y Solaris.
Las características que más destacaría de MongoDB son su velocidad y su rico pero sencillosistema de consulta de los contenidos de la base de datos. Se podría decir que alcanza un balance perfecto entre rendimiento y funcionalidad, incorporando muchos de los tipos de consulta que utilizaríamos en nuestro sistema relacional preferido, pero sin sacrificar en rendimiento.

Terminología básica en MongoDB

En MongoDB, cada registro o conjunto de datos se denomina documento. Los documentos se pueden agrupar en colecciones, las cuales se podría decir que son el equivalente a las tablas en una base de datos relacional (sólo que las colecciones pueden almacenar documentos con muy diferentes formatos, en lugar de estar sometidos a un esquema fijo). Se pueden crear índices para algunos atributos de los documentos, de modo que MongoDB mantendrá una estructura interna eficiente para el acceso a la información por los contenidos de estos atributos.

Formato de los documentos e ideas para la organización de datos

Los distintos documentos se almacenan en formato BSON, o Binary JSON, que es una versión modificada de JSON que permite búsquedas rápidas de datos. Para hacernos una idea, BSONguarda de forma explícita las longitudes de los campos, los índices de los arrays, y demás información útil para el escaneo de datos. Es por esto que, en algunos casos, el mismo documento en BSONocupa un poco más de espacio de lo que ocuparía de estar almacenado directamente en formatoJSON. Pero una de las ideas claves en los sistemas NoSQL es que el almacenamiento es barato, y es mejor aprovecharlo si así se introduce un considerable incremento en la velocidad de localización de información dentro de un documento.
Sin embargo, en la práctica, nunca veremos el formato en que verdaderamente se almacenan los datos, y trabajaremos siempre sobre un documento en JSON tanto al almacenar como al consultar información. Un ejemplo de un documento en MongoDB podría ser perfectamente éste:
  {
    “_id”          : “4da2c0e2e999fb56bf000002”
    “title”        : “Una introducción a MongoDB”,
    “body”         : “Lorem ipsum dolor sit amet…”,
    “published_at” : “2011-05-09T18:17:07-07:00”,
    “author_info”  : {
                       “_id”  : “4dc8919331c0c00001000002”
                       “name” : “Carlos Paramio”
                     },
    “tags”         : [“MongoDB”, “NoSQL”, “Bases de datos”]
    “comments”     : [
                       {
                         “author_info” : { “name” : “Jorge Rubira”, “email” : “email1@example.com” },
                         “body”        : “Test”,
                         “created_at”  : “2011-05-10T10:14:01-07:00”
                       },
                       {
                         “author_info” : { “name” : “Txema Rodríguez”, “email” : “email2@example.com” },
                         “body”        : “Otro test”,
                         “created_at”  : “2011-05-10T10:14:09-07:00”
                       }
                     ]
    “liked_by”     : [“4d7cf768e999fb67c0000001”, “4da34c62ba875a19d4000001”]
  }
Como podemos adivinar, este documento pretende representar la manera en que podrían almacenarse los datos correspondientes a un post de un blog. Los atributos “_id” (o clave principal) pueden tener el formato que se desee, aunque MongoDB utiliza un valor parecido a un UUID en hexadecimal por defecto si no se ha especificado ninguno. A pesar de parecer un valor completamente aleatorio (aunque ya sabemos que la aleatoriedad real no existe en informática), utilizan como base una semilla basada en la MAC de la interfaz de red de la máquina (y otros detalles de la misma) para evitar que dos máquinas diferentes puedan generar el mismo valor para la clave de un documento. Y los primeros bytes corresponden a una marca de tiempo, de modo que las claves se ordenan de forma natural por orden de creación (o casi, pues está claro que las distintas máquinas corriendo MongoDB deben tener la fecha y hora sincronizadas) sin tener que mirar cuál fue el último valor usado. Una solución inteligente, a mi modo de ver bastante más eficiente que un campo autonumérico, en especial para evitar que una máquina bloquee la inserción de registros en una colección sólo para asegurarse que no se dan condiciones de carrera al intentar dos máquinas diferentes escribir un documento con el mismo valor para “_id”. Por cierto, este atributo “_id” es el único obligatorio para un documento.
Las etiquetas y los comentarios están en el propio documento que representa al post, en lugar de guardarlos en colecciones separadas y utilizar claves foráneas para referenciar a los mismos. Sin embargo, en el atributo “liked_by” sí que guardamos una relación de claves, que corresponden a los usuarios que han marcado el post como que les ha gustado. Utilizar una forma u otra dependerá de las necesidades de acceso a estos datos En este caso, por ejemplo, sabemos que no vamos a pintar información sobre los usuarios que han marcado un post con un “me gusta”, pero sí queremos ver cuántos lo han marcado así, o si el usuario actual ya lo ha marcado o no, con lo que almacenar únicamente las claves de esos usuarios y guardar su información personal detallada en otra colección es lo más conveniente.
Por supuesto, no es necesario pedir a MongoDB que nos devuelva todo el documento cada vez que lo consultamos. Si por ejemplo vamos a pintar únicamente un listado de posts recientes, seguramente sea suficiente obtener el atributo “title”, con los documentos ordenados por “published_at”. Así,ahorramos ancho de banda entre el motor de base de datos y la aplicación, al mismo tiempo quememoria dado que no hay que instanciar todo el documento. Además, si tenemos muchos miles de visitantes, el atributo “liked_by” podría llegar a crecer bastante. Aunque el tamaño de un documentode MongoDB puede llegar hasta los 16 Megabytes, con lo que podemos almacenar bastante información dentro de un único documento sin necesidad de utilizar referencias, si así lo necesitamos. En caso de que tuviéramos que almacenar mucho más, habría que optar por utilizar otro esquema.
A veces, toca desnormalizar para poder tener a mano la información necesaria a la hora de pintar un post. Es por eso que en el atributo “author_info” he utilizado una versión intermedia: Si bien tenemos la clave principal del usuario que ha escrito este post, como es habitual que pintemos el nombre del autor, he almacenado también dicho nombre en el documento que representa al post, para que no sea necesario realizar una segunda consulta a la colección “usuarios”. Estas desnormalizaciones dependen nuevamente del uso que se den a los datos. En este caso, tengo claro que el nombre de un usuario no va a cambiar demasiado, así que recorrer todos los posts para cambiar este valor en caso de que el usuario realice esta operación, si bien es una modificación que podría llevar un tiempo considerable para ejecutarse, no es una operación habitual frente a la consulta del nombre del autor, y por tanto compensa. Incluso podríamos llegar a tratar el post como algo más permanente, de modo que aunque un usuario cambiara su nombre a posteriori, el nombre utilizado para firmar los posts anteriores no varíe, o sencillamente los posts puedan firmarse con diferentes pseudónimos.
Como creo que habrá quedado patente a estas alturas, el modelado del esquema de datos con MongoDB depende más de la forma en que consultaremos o actualizaremos los datos que de las limitaciones del propio sistema.

Cómo consultar los datos

Sin entrar demasiado en detalles acerca de todas las posibilidades que MongoDB nos ofrece para consultar los datos almacenados, sí quisiera nombrar algunas de ellas, para hacer notar que no estamos frente a un sistema simple de almacenamiento de pares clave-valor.
En primer lugar, MongoDB nos permite utilizar funciones Map y Reduce escritas en Javascript para seleccionar los atributos que nos interesan de los datos, y agregarlos (unificarlos, simplificarlos) en la manera deseada, respectivamente. Esto es algo habitual en muchos sistemas NoSQL, y en algunos casos es incluso la única forma posible de consultar datos. Claro está que muchas veces necesitamos algo bastante más sencillo que ésto.
En MongoDB se pueden utilizar consultas al valor de un atributo específico. Por ejemplo, podemos capturar el post que tiene un determinado título:
  db.posts.find({‘title’ : ‘Una introducción a MongoDB’})
El valor a consultar puede estar anidado en un tipo de datos más completo en el atributo del documento (por ejemplo, como valor de un hash asociado al atributo, o como el valor de uno de los ítems de un array). Se utiliza un punto como separador de los nombres de las claves de los diferentes hashes que hay que recorrer hasta llegar al valor deseado. Por ejemplo, la siguiente consulta devolvería todos los posts escritos por un determinado autor:
  db.posts.find({‘author_info._id’ : ‘4da2c0e2e999fb56bf000002’})
Y esta otra los posts etiquetados con MongoDB:
  db.posts.find({‘tags’ : ‘MongoDB’})
El hash utilizado como conjunto de condiciones que deben cumplir los documentos a devolver puede incluir operadores de muy diversos tipos, no sólo comparadores del valor absoluto buscado. Algunos de ellos son:
  • $all : Para indicar que el array almacenado como valor del atributo debe tener los mismos elementos que el proporcionado en la condición.
  • $exists : Para comprobar que el atributo existe en el documento.
  • $mod : Para comprobar el resto de una división del valor del atributo por un número.
  • $ne : Para indicar que el valor no puede ser el proporcionado.
  • $in : Para indicar que el valor debe estar entre alguno de los proporcionados.
  • $nin : Contrario de $in.
  • $or : Para indicar que se debe cumplir al menos una condición de entre un grupo de condiciones.
  • $nor : Contrario de $or.
  • $size : Para indicar el número de elementos que debe haber en el array almacenado como valor.
  • $type : Para comprobar el tipo del valor almacenado (número, cadena…)
  • Expresiones regulares : El valor debe concordar con la expresión regular indicada.
Y muchos, muchos más. Los resultados se pueden agrupar, ordenar, contar, paginar, y otras tantas operaciones comunes sin necesidad de recurrir al farragoso Map / Reduce. Y siempre que los atributos consultados tengan definidos un índice, la velocidad de las consultas es espectacular.
Bueno, por ahora creo que es suficiente como para que sirva de introducción a este fabuloso motor de base de datos. En futuros artículos procuraré hablaros de sus otras características a la hora de escalarlo para su uso en múltiples máquinas, así como otros mecanismos interesantes de MongoDB como los índices geoespaciales. Si queréis experimentar con él, y trabajáis habitualmente con Ruby, tal vez os interese echar un vistazo a un Object Document Mapper para este lenguaje que escribí para un proyecto, y que facilita la interacción con el motor desde tus modelos: MongoODM.
Una de las diferencias más importantes con respecto a las bases de datos relacionales, es que no es necesario seguir un esquema. Los documentos de una misma colección – concepto similar a una tabla de una base de datos relacional -, pueden tener esquemas diferentes.
Imaginemos que tenemos una colección a la que llamamos Personas. Un documento podría almacenarse de la siguiente manera:
{
  Nombre: "Pedro",
  Apellidos: "Martínez Campo",
  Edad: 22,
  Aficiones: ["fútbol","tenis","ciclismo"],
  Amigos: [
   {
     Nombre:"María",
     Edad:22
   },
   {
     Nombre:"Luis",
     Edad:28
   }
  ]
}
El documento anterior es un clásico documento JSON. Tiene strings, arrays, subdocumentos y números. En la misma colección podríamos guardar un documento como este:
{
  Nombre: "Luis",
  Estudios: "Administración y Dirección de Empresas",
  Amigos:12
} 
Este documento no sigue el mismo esquema que el primero. Tiene menos campos, algún campo nuevo que no existe en el documento anterior e incluso un campo de distinto tipo.
Esto que es algo impensable en una base de datos relacional, es algo totalmente válido enMongoDB.

¿Cómo funciona MongoDB?

MongoDB está escrito en C++, aunque las consultas se hacen pasando objetos JSON como parámetro. Es algo bastante lógico, dado que los propios documentos se almacenan en BSON. Por ejemplo:
db.Clientes.find({Nombre:"Pedro"});
La consulta anterior buscará todos los clientes cuyo nombre sea Pedro.
MongoDB viene de serie con una consola desde la que podemos ejecutar los distintos comandos. Esta consola está construida sobre JavaScript, por lo que las consultas se realizan utilizando ese lenguaje. Además de las funciones de MongoDB, podemos utilizar muchas de las funciones propias de JavaSciprt. En la consola también podemos definir variables, funciones o utilizar bucles.
Si queremos usar nuestro lenguaje de programación favorito, existen drivers para un gran número de ellos. Hay drivers oficiales para C#, Java, Node.js, PHP, Python, Ruby, C, C++, Perl o Scala. Aunque estos drivers están soportados por MongoDB, no todos están en el mismo estado de madurez. Por ejemplo el de C es una versión alpha. Si queremos utilizar un lenguaje concreto, es mejor revisar los drivers disponibles para comprobar si son adecuados para un entorno de producción.

¿Dónde se puede utilizar MongoDB?

Aunque se suele decir que las bases de datos NoSQL tienen un ámbito de aplicación reducido,MongoDB se puede utilizar en muchos de los proyectos que desarrollamos en la actualidad.
Cualquier aplicación que necesite almacenar datos semi estructurados puede usar MongoDB. Es el caso de las típicas aplicaciones CRUD o de muchos de los desarrollos web actuales.
Eso sí, aunque las colecciones de MongoDB no necesitan definir une esquema, es importante que diseñemos nuestra aplicación para seguir uno. Tendremos que pensar si necesitamos normalizar los datos, denormalizarlos o utilizar una aproximación híbrida. Estas decisiones pueden afectar al rendimiento de nuestra aplicación. En definitiva el esquema lo definen las consultas que vayamos a realizar con más frecuencia.
MongoDB es especialmente útil en entornos que requieran escalabilidad. Con sus opciones de replicación y sharding, que son muy sencillas de configurar, podemos conseguir un sistema que escale horizontalmente sin demasiados problemas.

¿Dónde no se debe usar MongoDB?

En esta base de datos no existen las transacciones. Aunque nuestra aplicación puede utilizar alguna técnica para simular las transacciones, MongoDB no tiene esta capacidad. Solo garantiza operaciones atómicas a nivel de documento. Si las transacciones son algo indispensable en nuestro desarrollo, deberemos pensar en otro sistema.
Tampoco existen los JOINS. Para consultar datos relacionados en dos o más colecciones, tenemos que hacer más de una consulta. En general, si nuestros datos pueden ser estructurados en tablas, y necesitamos las relaciones, es mejor que optemos por un RDBMS clásico.
Y para finalizar, están las consultas de agregación. MongoDB tiene un framework para realizar consultas de este tipo llamado Aggregation Framework. También puede usar Map Reduce. Aún así, estos métodos no llegan a la potencia de un sistema relacional. Si vamos a necesitar explotar informes complejos, deberemos pensar en utilizar otro sistema. Eso sí, esta es una brecha que MongoDB va recortando con cada versión. En poco tiempo esto podría dejar de ser un problema.

¿Cómo se instala MongoDB?

La instalación de una instancia del servidor es un juego de niños. Simplemente tenemos que bajar los binarios para nuestro sistema operativo. Hay versiones par Windows, Linux y MacOs. Una vez bajados podremos arrancar el servicio de MongoDB con un solo comando.
mongod --dbpath data
Con este comando arrancamos el servicio mongod, que empezará a escuchar peticiones por el puerto 27017. Es importante indicar el parámetro —dbpath, con la ruta dónde se almacenarán los ficheros de nuestra base de datos.

¿Cómo puedo usar la consola?

Si ya tenemos el servidor lanzado en nuestra máquina, bastará con lanzar desde la consola el siguiente comando
mongo localhost
Desde ese momento entraremos en la consola y podremos realizar consultas. Si escribimos helptendremos un listado con los comandos más comunes y su descripción.

Y de momento lo dejamos aquí. En próximos artículos veremos como se pueden hacer inserciones, consultas y actualizaciones de datos.

No hay comentarios:

Publicar un comentario

Todos los comentarios son bien recibidos...