Introducción a las bases de datos NoSQL con MongoDB

image

Oficina de Software Libre de la Universidad de Granada
(Twitter:@OSLUGR/ Facebook:@SoftwareLibreUGR)

Granada Geek
(http://www.meetup.com/es-ES/Granada-Geek/)

image

Primero lo primero... ¿Qué es una base de datos?

Una base de datos es una colección de información organizada de forma que se puedan seleccionar "rápidamente" los fragmentos de datos que se necesiten.
image

¿Tipos de bases de datos? Muchas..

  • Jerárquicas
  • De red
  • Transaccionales
  • Relacionales
  • Multidimensionales
  • Por clave-valor
  • Orientadas a documentos
  • En grafo

¿Lo más usado tradicionalmente?

  • Bases de datos relacionales: la información se organiza en una o varias tablas estructuradas en registros (filas) y campos (columnas) para dar forma un esquema entidad-relación.
  • SQL(Structured Query Language - Lenguaje de Consulta Estructurada): lenguaje que nos permite realizar diferentes tipos de operaciones con la información.

Bases de datos relacionales. Ventajas (ACID):

  • Atomicity (Atomicidad): una operación no se realiza si todos sus pasos no pueden ser ejecutados.
  • Consistency (Consistencia): una operación solo puede llevar la base de datos de un estado válido a otro estado válido.
  • Isolation (Aislamiento): una operación no puede afectar a otra.
  • Durability (Durabilidad): una operación realizada no se puede deshacer.

Bases de datos relacionales. Desventajas:

  • Esquemas muy rígidos.
  • No son prácticas para algo que no sea texto.
  • "Bajo" rendimiento y difícil escalabilidad.

¿Qué son las bases de datos NoSQL?

Bases de datos que no requieren estructuras fijas en los registros de datos, usando para ello varios tipos de modelos distintos:clave-valor,orientada a documentos,en grafo...

No soportan operaciones JOIN (reuniones), ni garantizan ACID. Pero escalan horizontalmente con facilidad y sus operaciones se realizan "rápidamente".

¿Por qué aparecen las bases de datos NoSQL?

Por el crecimiento exponencial de la información que circula en Internet. (Recordad: atomicidad y "bajo" rendimiento, además de consultas cada vez más complejas).

  • Añadir más nodos es simple
  • Consultas tipo Map-Reduce
  • Almacenamiento de datos mediante sharding.
  • Trabajo en memoria y datos eventualmente consistentes.

Tipos de bases de datos NoSQL. Por clave-valor:

Cada elemento es identificado por una clave única y la información se almacena generalmente en un objeto binario.

image

  • Ejemplos: Cassandra, HBase, Redis.

  • Usadas en Facebook, Twitter, Yahoo o StackOverflow.

Tipos de bases de datos NoSQL. Orientada a documentos:

Almacenan la información en estructuras simples estandarizadas como JSON, XML o BSON sobre las que se pueden realizar búsquedas por clave-valor o realizar consultas más avanzadas.

image
  • Ejemplos: MongoDB, CouchDB.

  • Usadas en Foursquare, eBay o BBC.

Tipos de bases de datos NoSQL. En grafo:

Mediante la teoría de grafos usa nodos y aristas para representar la información. Muy útil para representar modelos con muchas relaciones como las redes sociales.

image
  • Ejemplos: Infinity Graph o Neo4j.

  • Usadas en en HP, Infojobs o Cisco.

En resumen, cuando usar NoSQL:

  • Cuando el volumen de datos crece muy rápidamente en momentos puntuales pudiendo superar el terabyte.
  • Cuando la escalabilidad no es viable en el modelo relacional por motivos de coste o técnicos.
  • Cuando existen picos de uso del sistema en múltiples ocasiones.
  • Cuando el esquema de la información no es homogéneo.

¿Qué es MongoDB?

  • MongoDB (de la palabra en inglés “humongous” que significa enorme) es un sistema de base de datos NoSQL multiplataforma orientado a documentos de esquema libre.
  • Está escrita en C++.
  • Los registros de datos reciben el nombre de documentos y se puede agrupar en colecciones.
  • Licencia libre GNU AGPL 3.0.

Características de MongoDB

  • Reconoce sin problemas muchos de tipos de datos: string, integer, double, boolean, date, timestamp, null, array, objects...

  • El uso de colecciones y que un documento pueda contener otro documento suple la no existencia de JOIN (-complejidad +rapidez)

  • Búsquedas por campos, rangos o expresiones regulares usando el shell de MongoDB o directamente mediante programas en JavaScript

  • Escalado horizontal mediante "sharding". Se puede elegir como se distribuyen.

  • Ejecución simple en varios servidores, balanceo automático de carga y replicación automática en caso de fallo hardware.

Esquema comparativo de organización

Modelo relacional MongoDB
Base de datos Base de datos
Tabla Colecciones
Filas Documentos
Columnas Campos

Uso de cursores para contar registros o iterar sobre la información.

¿Cómo se organizan internamente los documentos?

Mediante el uso de BSON (Binary JSON), una versión modificada de JSON que guarda de forma explícita información útil para realizar búsquedas rápidas de datos.

Todos los documentos tienen un campo "_id" único.

Ejemplo de documento

                    {
  "_id": ObjectId("4efa9f2b7d343dad555e4bc7"),
  "nombre": "German",
  "apellidos": "Martinez Maldonado",
  "edad": 28,
  "genero_favorito": "Drama",
  "series": [
    {
      "nombre":"Breaking Bad",
      "genero":"Drama",
      "nota":10,
      "comentario":"La mejor serie de la historia"
    },
    {
      "nombre":"Doctor Who",
      "genero":"Ciencia Ficcion",
      "nota":9,
      "comentario":"Ojala yo teniendo una TARDIS ahora"
    },
    {
      "nombre":"LOST",
      "genero":"Ciencia Ficcion",
      "nota":4,
      "comentario":"El final más meh de la historia"
    }
  ]
}

MongoDB Shell

Es un interfaz JavaScript interactivo que nos permite conectarnos a MongoDB.

  • db.[COLLECTION].help()
  • show [dbs|collections|users]
  • use DATABASE
  • db.createCollection("usuarios")

Operaciones CRUD

CREATE (insert):

SQL: INSERT INTO usuarios VALUES ("Tipo", "De Incognito"...)

                      > db.usuarios.insert(
  {
    "nombre": "Tipo",
    "apellidos": "De Incognito",
    "edad": 43,
    "genero_favorito": "Humor",
    "series": [
      {
        "nombre":"Los Simpsons",
        "genero":"Humor",
        "nota":7,
        "comentario":"A partir de la temporada 11 dejaron de tener gracia."
      }
    ]
  }
)
  
  

Operaciones CRUD

READ (find / findOne):

SQL:
SELECT nombre, edad, favoritos FROM usuarios WHERE edad > 20 LIMIT 5 ORDER BY edad ASC

                      > db.usuarios.find(                           ⬅️️  coleccion
  { edad: { $gt: 20 } },                      ⬅️️  filtro
  [{nombre: 1, edad: 1, genero_favorito: 0 }] ⬅️️  proyeccion
  ).limit(5).sort( {edad: 1 } )               ⬅️️  modificadores

Operaciones CRUD / READ / Filtros

  • $eq (igual que):
    db.usuarios.find({edad:{$eq:20}})
  • $gt (mayor que):
    db.usuarios.find({edad:{$gt:20}})
  • $gte (mayor o igual que):
    db.usuarios.find({edad:{$gte:20}})
  • $lt (menor que):
    db.usuarios.find({edad:{$lt:20}})
  • $lte (menor o igual que):
    db.usuarios.find({edad:{$lte:20}})
  • $ne (distinto a):
    db.usuarios.find({edad:{$ne:20}})

Operaciones CRUD / READ / Filtros

  • $in (igual a alguno):
    db.usuarios.find({genero_favorito:{$in:["Drama","Ciencia Ficcion"]}})
  • $exists (existencia):
    db.usuarios.find({genero_favorito:{$exists:true}})
  • $or (OR):
    db.usuarios.find({"$or":[{edad:{$gte:20}},{edad:{$lte:30}}]})
  • $and (AND):
    db.usuarios.find({"$and":[{edad:{$gte:20}},{genero_favorito:"Drama"}]})
  • $not (NOT):
    db.usuarios.find("$not:{"{"$or":[{edad:{$gte:20}},{edad:{$lte:30}}]}})
  • $regex (expresión regular):
    db.usuarios.find({nombre: /\w*/i})

Operaciones CRUD / READ / Modificadores

  • limit()
  • count()
  • sort()
  • skip()

Operaciones CRUD

UPDATE (update):

SQL:
UPDATE usuarios SET genero_favorito = "Drama" WHERE edad > 30

                      > db.usuarios.update(                        ⬅️️  coleccion
  { edad: { $gt: 30 } },                     ⬅️️  filtro
  { $set: { genero_favorito: "Drama"} },     ⬅️️  accion [$set|$unset|$inc]
  { multi: true } )                          ⬅️️  opcion [$multi|$upsert]

Operaciones CRUD

REMOVE (delete):

SQL:
DELETE FROM usuarios WHERE edad > 30

                      > db.usuarios.remove(                        ⬅️️  coleccion
  { edad: { $gt: 30 } )                     ⬅️️  filtro

Agregación

Se aplican varias acciones a los documentos para obtener los resultados necesarios

SQL MongoDB
SELECT $project
WHERE $match
GROUP BY $group
HAVING $match
ORDER BY $sort
LIMIT $limit
SUM $sum

Agregación

                      
SELECT Localidad, AVG (Facturacion) "FACTURACION MEDIA"
FROM pedidos
WHERE Localidad <> "Jaen"
GROUP BY Localidad
HAVING AVG (Facturacion) > 5000
ORDER BY Localidad ASC;

Agregación

  db.pedidos.aggregate({
   $sort: {
    "Localidad": -1
   }
}, {
   $match: {
     "Localidad": {
       "$ne": "Jaen"
   }
   }
}, {
   $group: {
      _id: {
        $toUpper: "$Localidad"
      },
      "FACTURACION MEDIA": {
        $avg: "$Facturacion"
      }
    }
 }, {
    $match: {
      "FACTURACION MEDIA": {
        $gt: 5000
      }
    }
  })
  

MapReduce

  • Map: realiza el mapeo paralelo de los datos.
  • Reduce: se aplica una función en paralelo a cada porción de datos.

MapReduce

Ejemplo usando base de datos libre GeoWorldMap para calcular el par de ciudades que se encuentran más cercanas en cada país, excluyendo a Estados Unidos.


db.runCommand({
  mapReduce: "cities",
  map: function Map() {
    var key = this.CountryID;
    emit(key, {
      "data": [{
        "name": this.City,
        "lat": this.Latitude,
        "lon": this.Longitude
      }]
    });
  },

  reduce: function Reduce(key, values) {
    var reduced = {
      "data": []
    };
    for (var i in values) {
      var inter = values[i];
      for (var j in inter.data) {
        reduced.data.push(inter.data[j]);
      }
    }
    return reduced;
  },
  finalize: function Finalize(key, reduced) {
      if (reduced.data.length == 1) {
        return {
          "message": "Este país sólo tiene una ciudad"
        };
      }
      var min_dist = 999999999999;
      var city1 = {
        "name": ""
      };
      var city2 = {
        "name": ""
      };
      var c1;
      var c2;
      var d;
      for (var i in reduced.data) {
        for (var j in reduced.data) {
          if (i >= j) continue;
          c1 = reduced.data[i];
          c2 = reduced.data[j];
          d = (c1.lat - c2.lat) * (c1.lat - c2.lat) + (c1.lon - c2.lon) * (c1.lon - c2.lon);
          if (d < min_dist && d > 0) {
            min_dist = d;
            city1 = c1;
            city2 = c2;
          }
        }
      }
      return {
        "city1": city1.name,
        "city2": city2.name,
        "dist": Math.sqrt(min_dist)
      };
    },
    query: {
      "CountryID": {
        "$ne": 254
      }
    },
    out: {
      merge: "ciudades_proximas"
    }
  });

  db.ciudades_proximas.find({}, {
    "_id": 0
  })