5.3 Fase de construcción
5.3.4 Acceso al Data Lake
En este punto del desarrollo ya se cuenta con una parte autónoma del sistema capaz de cargar datos de forma continua a través de dos programas. Estos programas se dejan funcionando durante varias
horas cuándo el alumno deja las oficinas de avanttic durante un periódo de dos semanas para construir un Data Lake extenso y consistente. No obstante, el propio paradigma de almacenamiento clave-valor complica el acceso a los datos. Es común que en una base de datos se trabaje con operaciones de
extracción, carga y transformación de los datos [ref ETL], y que todas estas operaciones se realicen por medio de programación en SQL. No obstante, esto solo es aplicable a las bases de datos relacionales por su paradigma de tablas. En el caso del formato clave valor, uno puede acceder al valor pero este
está almacenado como una cadena, por lo que no se puede acceder a los datos que están contenidos dentro de esta cadena como argumentos.
La solución a esta situación es sencilla desde el punto de vista lógico. Si solo se pueden reali-
zar ETLs en un esquema relacional, habrá que introducir una base de datos relacional en nuestra arquitectura. Además de introducirla, se debe hacer que el Data Lake sea accesible desde esta nueva pieza del sistema. Es en este momento cuando el tutor asignado por parte de avanttic propone el
uso de tablas externas. Esta integración de la base de datos relacional y la construcción de las tablas externas y todos los pasos intermedios son el contenido de esta sección.
CAPÍTULO 5. RESULTADOS 73 5.3.4.1 Contenedor con Oracle 12c
La base de datos relacional escogida para este trabajo ha sido la Oracle 12c. Como con los
contenedores anteriores, es necesario encontrar una imagen con esta base de datos para construir un container Docker con la misma. No obstante, dicha imagen no está disponible en hub.docker.com ya que esta base de datos suele ser de pago. Como avanttic Consultoría Tecnológica es una empresa
100 % Oracle, el alumno ha tenido acceso a la tienda de imágenes Docker de Oracle, y pudo descargar de forma gratuita la imagen store/oracle/database-enterprise en su última versión, la 12.2.0.1
Una vez descargada la imagen, para levantar el contenedor con la misma basta con lanzar el comando:
$ docker run -d -it --name <nombre \ _contenedor > store / oracle / database - enterprise :12.2.0.1
Cuándo el contenedor está levantado se puede acceder tanto al propio servidor donde se aloja la base
de datos como a SQLPlus, la herramienta para realizar ETLs de las bases de datos relacionales de Oracle. Será desde este servidor junto con el servidor con la base de datos NoSQL desde donde se realizarán las operaciones necesarias para crear una tabla externa y poder acceder a los datos para su
posterior análisis.
5.3.4.2 Tabla externa a Oracle NoSQL
El acceso a los datos es una condición primordial para un proyecto de analítica, y es por eso que
esta parte del desarrollo ha sido una de las más extensas y cuidadas de todo el proyecto. La principal dificultad que se ha encontrado durante este proceso se debe a la propia arquitectura del sistema. El hecho de no disponer de estas tecnologíason premise dificulta en gran medida que las intalaciones estándar funcionen, ya que no se comparte sistema operativo entre ambas bases de datos, ni versión, ni funcionalidades internas del sistema. A todo esto hay que sumarle la dificultad añadida de conectar ambos contenedores entre sí. Esto se debe a que para una instalación estándar las bases de datos se
encuentran en la misma máquina y comparten host entre ellas por lo que no existe la dificultad de encontrar la la localización de los datos; sin embargo en Docker cada contenedor actúa de manera individual.
Teniendo en cuenta este último factor, se comienza por hacer cada contenedor visible y accesible para el resto de contenedores. En Docker, todos los contenedores operan sobre una red interna y reciben una dirección IP acorde a esta red. Para hacer que los contenedores de ambas bases de datos
puedan verse, es necesario crear otra red interna en Docker y levantar los contenedores dentro de esta misma red. De esta manera, ambos serán asignados una IP nueva pero dentro de la misma subred, siendo accesibles en ambas direcciones. Como es una operación interna al dominio de Docker, se
hará uso de uno de los comandos Docker al igual que en otras ocasiones a lo largo de este trabajo.
$ docker network create -d bridge <nombre \_red >
Una vez disponemos de una red interna, es necesario mover todos los contenedores a dicha red. Para hacerlo podemos hacer uso de comandos Docker; aunque gracias a Portainer esta operación
puede realizarse de forma rápida y sencilla desde su interfaz. En la figura 5.4 se puede apreciar como portainer es el único contenedor con una IP fuera de la nueva red que hemos creado
74 5.3. FASE DE CONSTRUCCIÓN
Figura 5.5: Red Interna e IPs
Una vez disponemos de todos los contenedores en la misma red podemos comenzar el proceso de creación y confuración de la tabla externa. Este proceso se puede resumir en 6 pasos:
1. Crear la estructura de directorios necesaria para el funcionamiento de la tabla externa en el servidor de la base de datos relacional.
2. Crear un usuario para la base de datos NoSQL y darle los permisos necesarios para operar en
ambos extremos de las bases de datos.
3. Crear una tabla en la base de datos 12c con los campos que deseamos recuperar del valor almacenado en la base de datos NoSQL.
4. Crear un programa Formatter que actúe como Parser del contenido del valor almacenado en la base de datos NoSQL
5. Realizar un “Publish” desde la base de datos NoSQL hacia la base de datos relacional
6. Probar el funcionamiento
Para realizar el primer paso debemos crear dos directorios llamados “processor” y “tweet-dataloc”. Estos directorios han de crearse tanto a nivel de sistema operativo como a nivel virtual en la base de
datos. Es muy importante realizar las operaciones de sistema operativo como usuario Oracle ya que hacerlo como root impediría el posterior acceso a los datos por parte del nuevo usuario que se creará en el siguiente paso. También es vital colocar los directorios en la carpeta /home del usuario Oracle.
Así pues, en primer lugar creamos ambos directorios en el sistema operativo.
$ mkdir / home / oracle / processor $ mkdir / home / oracle /tweet - dataloc
En estos directorios se situarán los archivosnosql_stream y nosql.dat, respectivamente. nosql_stream es un script que invoca el preprocesador de la base de datos Oracle para tablas externas y utiliza como argumento la clase Formatter que se realiza en el penúltimo paso. Esta invocación se hace de forma invisible al programador cada vez que se consultan los datos en el esquema relacional. Por
su parte,nosql.dat es un fichero con un formato de etiquetado muy similar a XML que se genera de forma automática al realizar el quinto paso, la función Publish.
Una vez los directorios han sido creados a nivel de sistema operativo, creamos estos directorios a
nivel de base de datos. En este caso, debemos hacerlo bajo el rol de administrador ya que es el único que tiene permisos para realizar esta operación. El directorio “ext_tab” se corresponde con processor mientras que el directorio “nosql_bin_dir” se corresponde con tweet-dataloc. Esta parte del proceso
se realiza accediendo a SQLPlus directamente desde el contenedor con el servidor de la base de datos relacional.
$ sqlplus / as sysdba
SQL > CREATE DIRECTORY ext \ _tab AS '< exttab \ _pathname >'; SQL > CREATE DIRECTORY nosql \ _bin \ _dir AS '<bin \ _pathname >';
CAPÍTULO 5. RESULTADOS 75
Cuándo acabamos este paso no debemos salir de SQLPlus pues el siguiente paso también implica
usar SQL. Para ello, creamos el usuario “nosqluser” y le brindamos los permisos necesarios para crear y alterar tablas, ejecutar cambios sobre los directorios y crear sesiones de trabajo. Recordemos que este paso también debe ser realizado bajo el rol de administrador.
SQL > CREATE USER nosqluser IDENTIFIED BY password ; SQL > GRANT CREATE SESSION TO nosqluser ;
SQL > GRANT EXECUTE ON SYS . UTL_FILE TO nosqluser ;
SQL > GRANT READ , WRITE ON DIRECTORY ext_tab TO nosqluser ;
SQL > GRANT READ , EXECUTE ON DIRECTORY nosql_bin_dir TO nosqluser ; SQL > GRANT CREATE TABLE TO nosqluser ;
A partir de este punto todos los pasos a nivel de base de datos serán realizados con el usuario nosqluser recién creado, y todos los pasos a nivel de sistema operativo con el usuario Oracle. El siguente paso
es la creación de la tabla donde podremos visualizar los datos en un esquema relacional. En este momento se ha de tomar una decisión sobre que campos de todos los que tiene un tweet se desean almacenar. Siempre existe la opción de incluir todos los campos, aunque esa sería una decisión pobre.
La primera razón es que dentro de los campos que tiene el tweet algunos de ellos tienen el mismo valor en distinto formato. Por ejemplo, el identificador del tweet está disponible en forma numérica y en forma de cadena. La segunda razón es que algunos de estos campos no nos ofrecen ningún valor
de cara al análisis de los mismos. Por ejemplo, carece de sentido incluir atributos como el booleano que indica si el usuario tiene imagen de perfil o no, si tiene activadas las notificaciones, o el valor hexadecimal del color de fondo que tiene en su perfil entre muchos otros. Por ello, nuestra tabla
externa contará con 27 campos donde cada uno de ellos sirve o bien como identificativo, o bien como posible entrada para los algoritmos de análisis. La sentencia SQL de creación de la tabla es la siguiente:
Listado 5.3: Creación de la tabla en la base de datos 12c
1 CREATE TABLE " NOSQLUSER "." TWEETS_DEF " 2 ( " CREATED_AT " VARCHAR2(200 CHAR), 3 " ID_STR " VARCHAR2(200 CHAR),
4 " NAME " VARCHAR2(200 CHAR), 5 " LOCATION " VARCHAR2(200 CHAR), 6 " VERIFIED " VARCHAR2(200 CHAR), 7 " FOLLOWERS_COUNT " NUMBER, 8 " FRIENDS_COUNT " NUMBER, 9 " LISTED_COUNT " NUMBER, 10 " FAVOURITES_COUNT " NUMBER, 11 " STATUSES_COUNT " NUMBER,
12 " CREATED_AT_USER " VARCHAR2(200 CHAR), 13 " COUNTRY " VARCHAR2(200 CHAR),
14 " COUNTRY_CODE " VARCHAR2(200 CHAR), 15 " FULL_NAME_PLACE " VARCHAR2(200 CHAR), 16 " NAME_PLACE " VARCHAR2(200 CHAR), 17 " PLACE_TYPE " VARCHAR2(200 CHAR), 18 " IS_QUOTE_STATUS " VARCHAR2(200 CHAR), 19 " FULL_TEXT " VARCHAR2(400 CHAR), 20 " QUOTE_COUNT " NUMBER,
21 " REPLY_COUNT " NUMBER, 22 " FAVORITE_COUNT " NUMBER, 23 " RETWEET_COUNT " NUMBER,
24 " FAVORITED " VARCHAR2(200 CHAR), 25 " RETWEETED " VARCHAR2(200 CHAR), 26 " FILTER_LEVEL " VARCHAR2(200 CHAR), 27 " LANG " VARCHAR2(200 CHAR),
28 " TIMESTAMP_MS " VARCHAR2(200 CHAR)
29 )
30 ORGANIZATION EXTERNAL 31 ( TYPE ORACLE_LOADER
32 DEFAULT DIRECTORY " EXT_TAB " 33 ACCESS PARAMETERS
76 5.3. FASE DE CONSTRUCCIÓN
34 ( records delimited by newline
35 preprocessor NOSQL_BIN_DIR :' nosql_stream '
36 fields terminated by '|' missing field values are null)
37 LOCATION
38 ( 'nosql . dat '
39 )
40 )
41 REJECT LIMIT UNLIMITED ;
En la última parte de esta sentencia se indican los directorios virtuales que defininimos previamente, donde deberá acceder la tabla para encontrar la información. Esto es así por la propia definición de una tabla externa, ya que esta no va a albergar datos, que seguirán manteniendose en el Data
Lake alojado en la base de datos NoSQL. Lo único que hace la tabla externa es permitirnos visualizar los datos en un esquema relacional y poder aplicar sentencias ETL sobre los mismos. Hasta aquí ya hemos realizado los tres primeros pasos y disponemos de directorios, usuario y tabla externa. El siguiente paso es conseguir llevar los datos a dicha tabla.
Durante la creación de la tabla se ha argumentado como no es necesario visualizar todos los datos almacenados, y de hecho sería contraproducente. Por lo tanto, será necesario un programa que sea capaz de acceder a la base de datos NoSQL, ver el contenido de cada valor de toda la colección con
la claveTweeterStream, tomar únicamente los valores necesarios y llevarlos a la tabla externa. Este programa se llama “TweetFormatter” porque hace la función que su propio nombre indica, dar formato a los datos para poder representarlos en el esquema relacional. Para ello, la clase debe implementar
la interfaz Formatter procedente de la librería de Oracle Key-Value previamente importada. El único método que debe implementar la clase TweetFormatter es toOracleLoaderFormat(). Este método recupera los registros almacenados en una colección de una KV Store que recibe como argumento del método. Una vez dispone de los valores de estos registros, debe construir una cadena en formato: valor1 | valor2 | ... | valorN que retornará como resultado a la base de datos relacional para que esta se encargue de forma invisible al programador de asignar dichos valores a una columna de la tabla que creamos en pasos anteriores.
La clase TweetFormatter obtiene los valores de la clave, la KV Store y el objeto KeyValueVersion del archivo nosql.dat, que generaremos en un paso posterior. Obtenidos estos valores, filtra en primer lugar la clave mayor, en este caso buscando que tenga el valor “TweeterStream”, que es el que le
asignamos a los datos al almacenarlos en nuestro Data Lake. Una vez encuentra un registro con esta clave, pasa a parsear su valor utilizando la librería GSON, de Google. El concepto de parsear consiste en dividir la enorme cadena en formato JSON que representa a un Tweet en una sucesión de cadenas más pequeñas que representan clases. Cade una de estas sub-cadenas recibe el nombre de
token, y están representadas por una clase Java con el mismo nombre que el token que se pretende encontrar. Además, dentro de cada clase existen varios atributos y no todos son deseables para su almacenamiento, por lo que cada clase debe llevar como atributos únicamente aquellos campos de la
cadena JSON que deseemos extraer. Las clases deseadas son:
• Tweet: Esa clase es la que contiene todos los atributos que deseamos introducir en la tabla, y será la clase de la que creamos un objeto que servirá como argumento del parser en el siguiente segmento de código. Esta clase tiene un total de 14 atributos, de los cuales tres de ellos son objetos de clases auxiliares. Estas clases auxiliares surgen de la necesidad de almacenar datos que se encuentran dentro de diccionarios que están a su vez contenidos dentro de la propia
cadena JSON. Para acceder a ellos, es necesario crear clases similares a esta y que presenten como atributos aquellos argumentos que deseemos recuperar para el modelo relacional.
• User: Contiene 8 atributos que se corresponden con los datos que se desean recuperar del campo “User” del tweet. Entre ellos se encuentran distintas métricas sobre la cuenta del usuario o su alcance.
CAPÍTULO 5. RESULTADOS 77
• Place: Contiene 5 atributos que se corresponden con los datos que se desean recuperar del campo “Place” del tweet. Entre ellos se encuentran datos de localización como el país o la ciudad desde donde se envió el tweet.
• Extended_Tweet: Contiene un solo campo, el texto del tweet. Esta clase es necesaria dado que la propia API de Twitter limita el campo del texto del tweet a 140 caractéres, y el tweet
completo (que puede llegar hasta los 280 caractéres) solo es accesible mediante este campo, que forma parte de la entidad superior “Extended_Tweet”.
Cada una de estas clases cuenta con métodosgetter y setter para cada uno de sus atributos, así como una implementación del métodotoString() para que se amolde al formato que la base de datos espera recibir. Por motivos de extensión no se incluyen estas clases en este capítulo, pero puede ver sus
características en el Anexo I de este trabajo.
Listado 5.4: TweetFormatter.java
1 package formatter ; 2
3 import java .io .*;
4 import java . lang . String ;
5 import java . nio . charset . StandardCharsets ; 6 import java . util . List ;
7 import oracle .kv .*;
8 import com . google . gson . Gson ; 9 import oracle .kv. KeyValueVersion ; 10 import oracle .kv. exttab . Formatter ; 11
12 public class TweetFormatter implements Formatter { 13
14 public TweetFormatter () {
15 super();
16 }
17
18 public String toOracleLoaderFormat (final KeyValueVersion kvv ,
final KVStore kvStore ){
19
20 String valor_retorno = null;
21 Tweet tw;
22
23 try {
24 final Key key = kvv . getKey ();
25 final Value value = kvv . getValue (); 26 Value . Format format = value . getFormat (); 27
28 / / Filtrar Clave
29 List < String > major = key . getMajorPath (); 30 Boolean contains = false;
31 for( String x : major ) {
32 if(x. equals (" TweeterStream ")|| 33 x. equals ("/ TweeterStream ")|| 34 x. equals ("/ TweeterStream /")) { 35 contains = true; 36 break; 37 } 38 } 39 / / Parsear 40 if( contains ){
41 String data = new
String ( value . toByteArray () , StandardCharsets . UTF_8 );
42 data = data . trim ();
43 tw = new Gson (). fromJson (data , Tweet .class);
78 5.3. FASE DE CONSTRUCCIÓN
45 if( contarCampos (tw. toString ()) ==26) {
46 valor_retorno = tw. toString (). replace ("\n", "");
47 } 48 } 49 } catch ( Exception e) { 50 e. printStackTrace (); 51 } 52 return valor_retorno ; 53 } 54
55 private static int contarCampos ( String a) { 56 int count = 0;
57 for(int i =0;i<a. length ();i ++) { 58 if(a. charAt (i)=='|') {
59 count ++; 60 } 61 } 62 return count ; 63 } 64 }
Con esto llegamos al último paso para crear la tabla externa, realizar unPublish desde la base de datos NoSQL hacia la base de datos relacional para que esta última pueda tener acceso a los datos. La función Publish es una utilidad de la base de datos NoSQL que nos permite publicar un archivo de configuración XML donde indicamos al software de la base de datos distintos atributos como el
nombre de la tabla en la que debe buscar los datos, el host de la base de datos, el usuario que será propietario de la tabla, etc. Como resultado, se genera automáticamente el fichero “nosql.dat” en el directorio de la base de datos relacional que marcamos con anterioridad.
Listado 5.5: config.xml
1 <config version="1">
2 <component name =" publish " type =" params ">
3 <property name =" oracle .kv. exttab . connection . url "
4 value =" jdbc:oracle:thin: /@// relacional:1521 / ORCLPDB1 . localdomain " 5 type =" STRING "/>
6 <property name =" oracle .kv. exttab . connection . user " 7 value =" nosqluser "
8 type =" STRING "/>
9 <property name =" oracle .kv. exttab . tableName " 10 value =" nosqluser . tweets "
11 type =" STRING "/> 12 </ component >
13 <component name =" nosql_stream " type =" params "> 14 <property name =" oracle .kv. kvstore "
15 value =" kvstore " 16 type =" STRING "/>
17 <property name =" oracle .kv. hosts " 18 value =" bequi_kvlite_1:5000 " 19 type =" STRING "/>
20 <property name =" oracle .kv. formatterClass " 21 value =" formatter . TweetFormatter " 22 type =" STRING "/>
23 </ component > 24 </ config >
Una vez se ha terminado de desarrollar el archivo de configuración lanzamos el comando y generamos
el fichero nosql.dat en el directorio tweet-dataloc de la base de datos 12c
$ java -cp
./ lib / kvstore . jar :./ lib / kvstore -ee. jar :./ lib / ojdbc6 -11.2.0.3. jar oracle .kv. exttab . Publish -config config . xml -publish -verbose
CAPÍTULO 5. RESULTADOS 79
Y finalmente, podremos visualizar nuestros datos en una tabla de esquema relacional a través de SQL
Developer, además por supuesto de poder realizar ETLs sobre nuestro Data Lake.
Figura 5.6: Tabla tweets_def
5.3.5 Análisis de datos
En esta sección se detalla el análisis que se ha realizado sobre los datos almacenados en el Data
Lake. En primer lugar, se realiza un análisis de sentimiento que produce como resultado un conjunto de datos etiquetado con el sentimiento de cada tweet (positivo, negativo o neutro). De forma paralela, se aplica sobre los datos el algoritmo LDA así como otros algoritmos de análisis de textos, que
produce como resultado un conjunto de temas a los que cada tweet puede pertenecer. Una vez ambos resultados han sido obtenidos, se cruzan los resultados para conocer el sentimiento de la población estudiada sobre cada tema y así poder sacar conclusiones sobre los diversos ámbitos que se tratan en
las redes dentro de la educación.