lunes, 29 de noviembre de 2010

Primeros pasos en MongoDB y Linux

MongoDB es una base de datos multiplataforma, con lo que no existen problemas ni apenas diferencias funcionales ni técnicas entre éstas. Donde sí puede notarse alguna diferencia es en el rendimiento y la velocidad que la propia plataforma provee. En este punto, Linux tiene algunos puntos de ventaja con respecto a plataformas Windows.

En este artículo intentaré ser lo más claro y sencillo posible para que usuarios noveles puedan entender los conceptos. Se asume que el lector ya tiene una nociones mínimas sobre el sistema operativo Linux (al menos el concepto de directorio).

Este artículo no pretende abarcar muchos conceptos, si no los primeros pasos o los conceptos más básicos para poder funcionar la base de datos y realizar las operaciones más básicas.

Los conceptos aquí explicados se pueden aplicar también a MongoDB Solaris, OS X y Windows (a excepción de la instalación).


Instalación de MongoDB en Linux

Los pasos a seguir para instalar MongoDB en Linux son muy sencillos:

1. Acceder a la página de descargas de MongoDB en http://www.mongodb.org/display/DOCS/Downloads
2. Seleccionar la versión requerida (32bits o 64 bits)
3. Descargar la versión a nuestro disco duro
4. Abrir una consola o terminal
5. Iniciar una sesión como superusuario o root (mediante el comando “su” e introducir la contraseña del mismo)
6. Crear el directorio de datos: mkdir -p /data/db
7. Acceder al directorio donde se descargó el archivo .tz de MongoDB
8. Descomprimir este archivo en el lugar que deseemos: tar xzf mongodb-linux-i686-1.4.0.tgz

Notas:
1. En sistemas Linux antiguos podría no funcionar MongoDB. En este caso, la versión a descargar sería la legacy-static
2. La versión de 32 bits tiene una limitación de hasta 2GB por fichero de datos (más información en http://blog.mongodb.org/post/137788967/32-bit-limitations).
3. MongoDB es muy ligero. El fichero descargado ocupa apenas 23MB


Arrancar el servidor MongoDB

Una vez instalado MongoDB, acceder al directorio bin (dentro del directorio donde lo instalamos):

$ cd xxx/bin

Para ejecutar el servidor:

$ ./mongodb &

Con “&” el servidor se ejecutará en background. Esto es opcional, y al usarlo damos por hecho que el servidor estará ejecutándose de forma contínua. Si queremos parar el servidor hemos de localizar el proceso con el comando:

$ ps

PID TTY TIME CMD
12047 pts/1 00:00:00 mongod
12343 pts/1 00:00:00 ps
14633 pts/1 00:00:00 su
14636 pts/1 00:00:00 bash


Una vez localizado, anotar el PID y especificarlo en el comando kill:

$ kill -9 12047

En el caso de no especificar “&”, el servidor de MongoDB se ejecuta de forma exclusiva en primer plano, acaparando la atención de la consola. Para para el servidor, simplemente hay que pulsar las teclas Ctrl y C.


La consola mongo

Una vez arrancado el servidor ya es posible trabajar con la base de datos. MongoDB provee de una consola (o DB Shell), donde podremos interactuar directamente con la misma. Existen también drivers para cualquier lenguaje de programación (Java, .NET, Perl, Phyton, PHP, C, C++, etc.) para su uso en nuestras aplicaciones.

Para arrancar la consola:

$ mongo

MongoDB shell version: 1.4.0
url: test
connecting to: test
Sat Mar 27 08:55:36 connection accepted from 127.0.0.1:39720 #1
type "help" for help
>


El comando “help” nos permitirá conocer un resumen de los comandos posibles:

> help
HELP
show dbs show database names
show collections show collections in current database
show users show users in current database
show profile show most recent system.profile entries with time >= 1ms
use set curent database to
db.help() help on DB methods
db.foo.help() help on collection methods
db.foo.find() list objects in collection foo
db.foo.find( { a : 1 } ) list objects in foo where a == 1
it result of the last line evaluated; use to further iterate
>


Para mostrar las bases de datos disponibles:

> show dbs
admin
local


Para obtener una ayuda sobre los comandos a utilizar con una base de datos:

> db.help()
DB methods:
db.addUser(username, password[, readOnly=false])
db.auth(username, password)
db.cloneDatabase(fromhost)
db.commandHelp(name) returns the help for the command
db.copyDatabase(fromdb, todb, fromhost)
db.createCollection(name, { size : ..., capped : ..., max : ... } )
db.currentOp() displays the current operation in the db
db.dropDatabase()
db.eval(func, args) run code server-side
db.getCollection(cname) same as db['cname'] or db.cname
db.getCollectionNames()
db.getLastError() - just returns the err msg string
db.getLastErrorObj() - return full status object
db.getMongo() get the server connection object
db.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair
db.getName()
db.getPrevError()
db.getProfilingLevel()
db.getReplicationInfo()
db.getSisterDB(name) get the db at the same server as this onew
db.killOp(opid) kills the current operation in the db
db.printCollectionStats()
db.printReplicationInfo()
db.printSlaveReplicationInfo()
db.printShardingStatus()
db.removeUser(username)
db.repairDatabase()
db.resetError()
db.runCommand(cmdObj) run a database command.  if cmdObj is a string, turns it into { cmdObj : 1 }
db.serverStatus()
db.setProfilingLevel(level,) 0=off 1=slow 2=all
db.shutdownServer()
db.stats()
db.version() current version of the server


Para probar algo, utilizaremos la base de datos “prueba”:

> use prueba
switched to db prueba


Esta base de datos aún no existe, pero al especificar este comando, MongDB se prepara para su creación.

Para obtener el nombre de la base de datos en uso:

> db.getName()
prueba


Para conocer la versión de la base de datos:

> db.version()
1.4.0


Para obtener la información del servidor MongoDB al que estamos conectados:

> db.getMongo()
{
"host" : "127.0.0.1",
"setSlaveOk" : function () {
this.slaveOk = true;
},
"getDB" : function (name) {
return new DB(this, name);
},
"getDBs" : function () {
var res = this.getDB("admin").runCommand({listDatabases:1});
assert(res.ok == 1, "listDatabases failed:" + tojson(res));
return res;
}
...


Esta información está en formato JSON (al igual que toda la información que se trata y utiliza en MongoDB), e incluye código JavaScript. Al principio obtenemos que estamos conectados a un servidor en la dirección 127.0.0.1 (localhost o máquina local). El resto son funciones disponibles para utilizar en el mismo comando. Por ejemplo, para obtener la lista de bases de datos (y su detalle) que gestiona el servidor Mongo al que estamos conectados:

> db.getMongo().getDBs()
{
"databases" : [
{
"name" : "admin",
"sizeOnDisk" : 1,
"empty" : true
},
{
"name" : "local",
"sizeOnDisk" : 1,
"empty" : true
},
{
"name" : "test",
"sizeOnDisk" : 1,
"empty" : true
}
],
"totalSize" : 0,
"ok" : 1
}


Comparativa entre modelo relacional y modelo documental

Para que nuestra base de datos “pruebas” pueda crearse, ésta debe alojar algún dato, el cual se ubica en una colección. Podemos concebir una colección como un almacén de datos relacionados sobre una temática (agenda, noticia, pedidos, etc.). Puede tener un símil con las tablas de las bases de datos relacionales en cuanto a su concepto, pero no en cuanto a su forma, ya que las tablas tradicionales organizan la información de una manera estructura y rígida. Podemos imaginar una hoja de cálculo con x columnas (campos) y n filas (registros o tuplas). La información se almacena aquí con unas reglas estrictas. Las columnas son siempre las mismas y el tipo de datos es el mismo. Estas reglas aseguran la consistencia de la información, pero la gestión de esos datos resulta limitada a esas mismas reglas.

MongoDB es una base de datos orientada a documentos (u a objetos), y no está sometida a las reglas de las bases de datos relacionales. Por de pronto, nos olvidamos de las limitaciones de la hoja de cálculo, pues cada fila es un documento (u objeto), el cual puede ser igual, similar o distinto a los documentos precedentes o posteriores de la misma colección.

Un documento, en sí mismo, una colección de valores clave/valor, con un formato libre. De esta manera, el dato de una clave en un documento puede ser de un tipo, y en otro documento puede ser de otro tipo. Asimismo, una clave puede no estar limitada a un único valor, si no contener un array (o lista) de valores, o incluso un documento o un array de documentos (como si fuera otra colección interna (“inner”)).
Para visualizar gráficamente ésto, imaginemos una base de datos para almacenar noticias. Para no extendernos mucho, almacenaremos la noticia junto con las etiquetas de categoría y los comentarios de los usuarios.

En una base de datos relacional, necesitaríamos las siguientes tablas:

Tabla noticia
Campo id_noticia (entero, no nulo, único, clave primaria)
Campo titulo (texto(50), no nulo)
Campo fecha (fecha, no nulo)
Campo autor (texto(20), no nulo)
Campo noticia (texto grande, no nulo)

Tabla comentario
Campo id_comentario (entero, no nulo, único, clave primaria)
Campo id_noticia (entero, no nulo, clave foránea)
Campo fecha (fecha, no nulo)
Campo autor (texto(20), no nulo)
Campo comentario (texto grande, no nulo)

Tabla etiqueta
Campo id_etiqueta (entero, no nulo, único, clave primaria)
Campo etiqueta (texto(20), no nulo)

Tabla relacion_etiqueta_noticia
Campo id_noticia (entero, no nulo, clave foránea)
Campo id_etiqueta (entero, no nulo, clave foránea)

El modelo entidad relación sería el siguiente:

La normalización de los datos permite evitar la redundancia (repetición) de los mismos, y para ello se divide y se clasifica la información en diferentes tablas. Se crea un campo clave (normalmente con nombre id) que representa ese dato de forma única. En un modelo relacional, los datos están atomizados.

Este modelo ha funcionado muy bien hasta nuestros días, pero implica una complejidad asociada. Por un lado, hemos de conocer el modelo de los datos para tracear y poder reconstruir y unir de nuevo la información. A veces, con una join es suficiente, pero muchas veces hemos de realizar varios procesos dependientes unos de otros, lo que complica la programación.

En el modelo documental esto es mucho más sencillo, pues la entidad de documento es en sí un objeto, lo que podemos utilizar clases que directamente carguen los datos sin operaciones de consulta complejas ni intermedias.

Con el ejemplo propuesto, un documento en MongoDB podría ser el siguiente:

{ autor: ‘Rafael Hernamperez’,
titulo: ‘Primeros pasos en MongoDB Linux’,
fecha: Date(’03-27-2010’),
noticia: 'bla bla bla',
etiqueta: [‘opensource’,’base de datos’,’MongoDB’],
comentarios: [{autor: ‘adan3000’, fecha: Date('03/27/2010'), comentario: ‘Muy sencillo, gracias’},
{autor: ‘majopero’, fecha: Date('03/27/2010'), comentario: 'Me parece muy interesante'}
]
}


Como se puede apreciar, todo está ahí, de manera directa. No es necesario reconstruir el documento consultando diferentes fuentes, como en el modelo relacional. Si en otro documento no quiero etiquetas o comentarios, simplemente, no los pongo. Todo ésto, además de versátil y sencillo, es óptimo y legible. El modelo relacional nos obliga a estar ligando los datos a través de códigos id que para nosotros no tienen significado..

Las ventajas o inconvenientes entre estos dos modelos de datos no acaba ahí. Por ejemplo, si el día de mañana deseamos agregar galerías de imágenes o de vídeos a la noticia (o cualquier otro tipo de adición que pueda acontecer y que no tenemos previsto), se complica en el modelo relacional, pues hay que añadir nuevas tablas, nuevas claves, nuevas relaciones y nuevas reglas. El desarrollo debe añadir un extra para ligar los nuevos datos, reconstruirlos y representarlos.

Espero que en un artículo futuro pueda recopilar todas las diferencias y analizar los pros y los contras de ambos modelos.


Andando, que es gerundio

Tras la teoría del capítulo anterior, vamos a crear definitivamente la base de datos “pruebas”, creando un documento que se añadirá a una colección llamada “noticias”. Para ello, desde la consola mongo, se creará una variable que contenga el documento y, a continuación se guarda. Se puede realizar directamente, pero para dejarlo más claro, debido a lo extenso de los datos del documento, se ha preferido hacer en dos pasos:

> var noticia = {
... autor: 'Rafael Hernamperez',
... titulo: 'Primeros pasos en MongoDB Linux',
... fecha: Date('03/27/2010'),
... noticia: 'bla bla bla',
... etiqueta: ['opensource', 'base de datos', 'MongoDB'],
... comentarios: [
... {autor: 'adan3000', fecha: Date('03/27/2010'), comentario: 'Muy sencillo, gracias'},
... {autor: 'majopero', fecha: Date('03/27/2010'), comentario: 'Me parece muy interesante'}
... ]
... }
> db.noticias.save(noticia)
Sat Mar 27 10:32:53 allocating new datafile /data/db/pruebas.ns, filling with zeroes...
Sat Mar 27 10:32:53 done allocating datafile /data/db/pruebas.ns, size: 16MB, took 0.066 secs
Sat Mar 27 10:32:53 allocating new datafile /data/db/pruebas.0, filling with zeroes...
Sat Mar 27 10:32:54 done allocating datafile /data/db/pruebas.0, size: 64MB, took 0.185 secs
Sat Mar 27 10:32:54 building new index on { _id: 1 } for pruebas.noticias
Sat Mar 27 10:32:54 Buildindex pruebas.noticias idxNo:0 { name: "_id_", ns: "pruebas.noticias", key: { _id: 1 } }
Sat Mar 27 10:32:54 done for 0 records 0secs
Sat Mar 27 10:32:54 insert pruebas.noticias 261ms


De forma implícita, MongoDB ha creado físicamente la colección “noticias” dentro de la base de datos “pruebas” (en uso). Para verificarlo, listamos las bases de datos:

> show dbs
admin
local
pruebas
test


Ahora listamos las colecciones:

> show collections
noticias
system.indexes


Para listar todos los documentos de la colección:

> use pruebas
switched to db pruebas
> db.noticias.find()
{ "_id" : ObjectId("4badd0c542691c3d5d4ba694"), "autor" : "Rafael Hernamperez", "titulo" : "Primeros pasos en MongoDB Linux", "fecha" : "Sat Mar 27 2010 10:32:39 GMT+0100 (CET)", "noticia" : "bla bla bla", "etiqueta" : [ "opensource", "base de datos", "MongoDB" ], "comentarios" : [
{
"autor" : "adan3000",
"fecha" : "Sat Mar 27 2010 10:32:39 GMT+0100 (CET)",
"comentario" : "Muy sencillo, gracias"
},
{
"autor" : "majopero",
"fecha" : "Sat Mar 27 2010 10:32:39 GMT+0100 (CET)",
"comentario" : "Me parece muy interesante"
}
] }


Internamente, MondoDB crea claves con el prefijo “_”. La clave “_id” es gestionada de forma interna y exclusiva por MongoDB para evitar la redundancia de documentos.

Podemos añadir nuevos documentos con claves similares pero no iguales:

> db.noticias.save({autor:'Rafael Hernamperez', titulo:'Noticia2', fecha: Date('03/27/2010'), noticia: 'bla bla bla 2'})
> db.noticias.save({autor:'Claudio Mendetto', titulo:'Noticia3', fecha: Date('03/27/2010'), noticia: 'bla bla bla 3'})


Ahora tenemos tres documentos. Para hacer una búsqueda, por ejemplo, las noticias publicadas por Claudio:

> db.noticias.find({autor: 'Claudio Mendetto'})

O las noticias en las cuales 'adan3000' ha dejado comentario:

> db.noticias.find({'comentarios.autor' : 'adan3000'})

Safe Creative #1003275848376