Riak: Revisando Y Practicando

- - publicado en Base de Datos etiquetado como basho, erlang, mapreduce, nosql, riak | Comentarios

Después de la introducción que hice ya hace unos meses sobre la teoría en la que se fundamenta esta base de datos, me he dispuesto a someterla a unas pruebas de funcionamiento, para así aprender a manejar de forma apropiada esta herramienta. En principio, como revisé solo los conceptos en los que se basa y no el funcionamiento a nivel de usuario, de programador, no sabía si era una interfaz amigable para realizar consultas o búsquedas, y la forma de realizar las cargas, inserciones y demás. Tengo que decir que, al menos el acceso y la comunicación con el servidor resulta muy cómoda y sencilla, ya que se basa en REST.

A través de peticiones HTTP muy sencillas y que se pueden generar incluso desde la línea de comandos con herramientas como curl, podemos realizar peticiones a la base de datos.

Mensajes hacia Riak

En principio, Riak se basa en REST, esto quiere decir que cada URI especifica un recurso dentro de la base de datos, y sobre ese recurso podemos realizar las acciones HTTP que se relacionan a cualquiera de las acciones básicas de una petición:

  • GET equivale a tomar datos, sería la R del CRUD (retrieve) o el SELECT del SQL.
  • PUT equivale a insertar datos nuevos, sería la C del CRUD (create) o el INSERT del SQL.
  • POST equivale a una modificación o actualización de datos, sería la U del CRUD (update) o el UPDATE de SQL.
  • DELETE equivale a una eliminación del dato, sería la D (delete) o el DELETE de SQL.

Esto permite muchas ventajas, como el uso, en el GET, de la cabecera If-Modified-Since, que nos dará la posibilidad de comprobar si un elemento pesado (imagen, vídeo o similar) es nuevo o no, para volver a descargarlo. Riak nos retornará el objeto con el mensaje 200 para indicar que hay uno nuevo, o solo la cabecera 304 (Not Modified) para indicarnos de que es el mismo que ya tenemos, por lo que no hace falta volver a transmitirlo.

Ante una petición de POST, o una de PUT en el que no exista el registro, se retornará el mensaje 201 (Created), y ante una operación de DELETE, si ha salido bien recibiremos un 204 (No Content) o 404 (Not Found).

Composición de la URI

La URI para localizar recursos dentro de Riak se puede establecer como sigue:

/riak/BUCKET/RESOURCE

NOTA: la palabra clave riak ha sido modificada en nuevas versiones por buckets, pero se mantiene riak por compatibilidad hacia atrás. Por lo que, si estás instalando una versión nueva tomada de la parte de descargas de Riak, puedes usar en lugar de riak la palabra buckets.

Riak configura su URI de modo que la primera palabra clave es precisamente riak, seguida por el bucket donde se almacenarán los recursos que se indique a continuación en la URI. El bucket es un bolsillo en el que se puede almacenar lo que se desee. Además, el bucket se puede configurar a través del paso de algunos parámetros de configuración específicos. Esto se sale un poco del objetivo del artículo, por lo que si tienes curiosidad de lo que se puede configurar puedes echarle un vistazo a este enlace.

Volviendo al tema, tenemos recursos. Estos recursos son un espacio en el que podemos guardar la información que necesitemos. Puede ser un texto normal, codificado como YAML, como JSON, como XML, o incluso una imagen o un vídeo… o incluso una imagen ISO (comprobado con una ISO de Sabayon de 700 MB aproximadamente).

Por lo que, podemos probar a hacer algunas prácticas. Lanzamos Riak. Si lo tenemos en ejecución en el puerto 8098 (es el de por defecto cuando se instala el paquete deb), podemos hacer lo siguiente:

curl -v -X PUT -H "Content-type: image/jpeg" --data-binary @imagen.jpg http://127.0.0.1:8098/riak/pruebas/imagen.jpg

Nota: asegúrate de que la localización de la imagen es correcta, a una imagen de tu disco válida, y no olvidas poner la arroba (@) justo antes de esa ruta (sin espacios).

La comprobación más simple es a través del cualquier navegador que tengas instalado en esa misma máquina:

http://127.0.0.1:8098/riak/pruebas/imagen.jpg

Con esto ya tendríamos la primera pureba satisfactoria de nuestro sistema de clave-valor para el almacenamiento de datos. Ahora, si lo que queremos es eliminarla, podemos hacerlo así:

curl -v -X DELETE http://127.0.0.1:8098/riak/pruebas/imagen.jpg

Si recargamos el navegador con la URL anterior, veremos que nos responde not found.

Búsquedas en Riak

En caso de que tengamos información formateada en JSON dentro de cada clave y queramos hacer una búsqueda de esta información para extraer únicamente las claves que nos sean útiles, tendremos que emplear el sistema conocido como map-reduce, para realizar una búsqueda a través de todas las claves de un bucket.

Voy a basarme en lo explicado en la documentación oficial de Riak, para no tener que inventar datos. En principio, si descargamos los dos siguientes ficheros y los ejecutamos:

  • goog.csv, datos a insertar en Riak (en formato CSV).
  • load_data, script en Erlang que insertará los datos, convirtiéndolos en un formato JSON. Hay que editar el scirpt y cambiar la URL para que tenga el puerto correcto, en mi caso puse 8098.

Esto nos da un bucket bastante grande en el que poder practicar.

Como decíamos antes, vamos a dar un repaso al sistema de MapReduce, el cual fue popularizado por Google. Este se basa en el algoritmo de divide y vencerás, realmente, ya que realiza un lanzamiento sobre cada elemento individual (map) tomando un resultado y, después, la parte de reduce, se encarga de analizar y unificarlo todo en un solo resultado.

Por lo tanto, tenemos que enviar mediante POST, a una URI concreta, un formato JSON de dato que será el siguiente:

{"inputs": "goog", "query": [
    {"map": {
        "language":"javascript",
        "source": "function(value, keyData, arg) { var data = Riak.mapValuesJson(value)[0]; if(data.High && parseFloat(data.High) > 600.00) return [value.key]; else return []; }",
        "keep": true
    }}
]}

Si guardamos esto en un fichero, podemos lanzarlo con el siguiente comando:

curl -X POST http://127.0.0.1:8098/mapred -H "Content-type: application/json" -d @mapred.json

En este caso, la función de map es la siguiente:

function(value, keyData, arg) {
  var data = Riak.mapValuesJson(value)[];
  if(data.High && data.High > 600.00)
    return [value.key];
  else
    return [];
}

Esto quiere decir que se toma el contenido de cada clave, se parsea con una función especial de Riak para convertir de JSON y retornar la primera línea para que sea almacenada en data. Se comprueba que tenga campo High y que High sea mayor de 600.

Es un filtro como se puede hacer con cualquier función map en cualquier lenguaje (que disponga de ella, claro).

Ahora, para emplear tanto map, como reduce, imagina que lo que queremos, de esta información, es econtrar la máxima varianza diaria del precio por mes. Para eso, en el map sacaríamos la varianza diaria, y en el reduce tomaríamos solo la máxima varianza por mes, las funciones serían así:

/* Map calcula la varianza diaria y asigna la clave por mes */
function(value, keyData, arg){
  var data = Riak.mapValuesJson(value)[];
  var month = value.key.split('-').slice(,2).join('-');
  var obj = {};
  obj[month] = data.High - data.Low;
  return [ obj ];
}
 
/* Reduce encuentra la varianza máxima por mes */
function(values, arg){
  return [ values.reduce(function(acc, item){
             for(var month in item){
                 if(acc[month]) { acc[month] = (acc[month] < item[month]) ? item[month] : acc[month]; }
                 else { acc[month] = item[month]; }
             }
             return acc;
            })
         ];
}

Codificado para enviarlo en el fichero mapred.json, lo anterior quedaría así:

{"inputs":"goog",
 "query":[{"map":{"language":"javascript",
                  "source":"function(value, keyData, arg){ var data = Riak.mapValuesJson(value)[0]; var month = value.key.split('-').slice(0,2).join('-'); var obj = {}; obj[month] = data.High - data.Low; return [ obj ];}"}},
         {"reduce":{"language":"javascript",
                    "source":"function(values, arg){ return [ values.reduce(function(acc, item){ for(var month in item){ if(acc[month]) { acc[month] = (acc[month] < item[month]) ? item[month] : acc[month]; } else { acc[month] = item[month]; } } return acc;  }) ];}",
                    "keep":true}}
         ]
}

NOTA: el parámetro keep es el que nos dice qué es lo que se retorna. Si agregamos este mismo parámetro en las dos secciones (map y reduce), nos encontramos que el retorno de la búsqueda es tanto de uno como de otro.

Otras cosas

Otra de las cosas que han agregado es la posibilidad de enlazar recursos, de modo que, asignando un enlace y una etiqueta (con el nombre del tipo de enlace), podamos tener relacionado un recurso con otro. Esto da un abanico de posibilidades tan grande como el que se consigue con las bases de datos relacionales, ya que es posible tener en un bucket contactos, en otro datos específicos de un servicio y relacionar el contacto con esos datos agregando una etiqueta al mismo, e incluso que el enlace sea bidireccional agregando las etiquetas en los dos sentidos, cambiando la etiqueta para que sea semántica.

Una imagen de lo que se puede conseguir:

Las partes de administración (para ajustar las variables del teorema CAP) y configuración de otros nodos es algo que dejamos a revisar de la documentación oficial.

Conclusiones

Como puede verse, Riak solventa con su paradigma de NoSQL, basado en el almacenamiento por clave-valor y búsquedas por MapReduce, la mayoría de necesidades que se pueden encontrar a la hora de emplear este tipo de sistemas. Lo único que queda un poco en el aire, o hay que realizar con llamadas sucesivas, son las actualizaciones y eliminaciones masivas, pero realmente, puede ser un mal menor, si estas no son empleadas en el esquema que se vaya a implementar Riak.

Hay que tener presente que Riak es una muy buena base de datos, que fundamenta sus potencias en la posibilidad de contar con un cluster, lecturas rápidas con caché, y búsquedas artificiosas con MapReduce, pero puede haber otros usos en los que las consultas de modificación y eliminación masivas sean lo más importante y esto ya sería mejor abarcarlo con otra base de datos de otro tipo.

Manuel Rubio

Manuel Rubio

Fundador de AltenWald

Comments