Muy buenas, vamos a retomar el anterior video y vamos a abordar el repaso de las etapas y componentes que conforman el procesamiento de grafos. Casi la totalidad de motores de "graph processing" basan su funcionamiento en el paradigma BSP, que es "Bulk Synchronous Parallel", que consiste en la repetición iterativa de un procedimiento que consta de tres fases. Una primera en la que una determinada cantidad de unidades de cómputo realizan un procesamiento de unos datos, realizan lo que se denomina una tarea. La segunda fase viene a ser una etapa en la que todas las unidades de cómputo intercambian mensajes entre ellas. Finalmente, tenemos una tercera fase en la que se realiza una sincronización global, de manera que esta sería la fase que daría paso a un nuevo procedimiento, un nuevo paso de este procedimiento. En el caso que nos ocupa, los nodos serían las unidades de procesamiento y la idea consiste en que este procedimiento se repite hasta que todos los nodos se quedan sin nada por procesar. De manera que, para llevar a cabo este algoritmo, generalmente, los nodos comparten o tienen disponible un estado que puede ser, o bien, activo, es decir, que tiene tarea por procesar, o bien, parado o inactivo, que es decir que ya no ha recibido mensajes, no tiene tarea por procesar. Vamos a poner un pequeño ejemplo, un ejemplo tradicional dentro de lo que es el procesamiento de grafos, que es el de obtener el máximo valor entre entre estos cuatro nodos. Como vemos aquí, tenemos cuatro nodos que simplemente contienen un número, tienen unas determinadas conexiones entre ellos y tenemos que extraer cuál es el que contiene el máximo valor. Vemos que tenemos un paso en el que los nodos envían la información de su propio valor a aquellos otros nodos con los que tiene conexión, de manera que tenemos que el primer nodo recibe información del segundo nodo, el segundo nodo recibe información del primer nodo y, también, del tercero, el tercer nodo recibe del cuarto y, por último, el cuarto nodo, lo recibe del segundo y del tercero. En esta etapa, empezamos a procesar y, con los mensajes que hemos recibido y el propio valor que tenemos en nuestro nodo, determinamos cuál es el nuevo valor máximo con el que nos actualizamos, de manera que tenemos estos resultados. Ahora, de nuevo, iniciamos la fase de comunicación y nos comunicamos, únicamente, se comunicarán, mejor dicho, aquellos nodos que han visto alterado su valor, el valor máximo que tienen como propiedad. De manera que, en este caso, tenemos que el segundo nodo recibe de nuevo un mensaje del nodo "uno", que ha actualizado su valor, y el tercer nodo también lo recibe del cuarto nodo, que también ha actualizado su valor. Si os fijais, los nodos que no han actualizado su valor han pasado al estado inactivo y no han emitido ningún mensaje. En este nuevo paso, todos realizan su cómputo, su comparación, y volvemos a obtener unos nuevos resultados de manera que, en este caso, el nodo que ha cambiado su estado es el tercero, por lo tanto, vuelve a pasar a ser activo y emitirá, en la siguiente fase, dos nuevos mensajes, uno por cada nodo con el que conecta, es decir, al segundo y al cuarto. En este caso, vemos que el segundo y el cuarto reciben un valor que no les hace modificar su estado, el tercer nodo ha pasado a estado inactivo y, por lo tanto, como tenemos todos los nodos inactivos, podemos considerar que este procedimiento ha finalizado. En este esquema, podemos identificar a una serie de componentes que son típicos y son compartidos entre la mayoría de motores de procesamiento de grafos. En primer lugar, tenemos lo que conocemos como "superstep", es decir, cada una de las etapas que hemos identificado dentro de lo que es el paradigma BSP, es decir, la etapa de procesamiento, la etapa de envío o emisión de mensajes y la etapa de sincronización, hemos visto que estas etapas se repiten iterativamente y cada una de estas iteraciones la conocemos como un super paso, un "superstep". Del mismo modo, normalmente, podemos identificar lo que se denominan combinadores. Los combinadores son funciones que tratan de combinar o realizar algún pequeño cálculo rápido, o ligero, con los mensajes que se reciben, de manera que reducen la cantidad de mensajes a procesar. En este caso que hemos visto, la función que ejecuta cada nodo podría, en sí mismo, ser la de un combinador, dado que el cómputo era muy sencillo. Esto, lo que permitiría es calcular el máximo de todos los valores recibidos por mensaje para, luego, realizar un cálculo más complejo dentro de lo que es la función principal del nodo. Por último, tenemos los componentes agregadores. Los agregadores vienen a ser lo que, en paradigmas de programación más tradicionales, conocemos como variables globales. Es decir, los agregadores nos permiten compartir estados, o información, de un nodo hacia el resto de nodos que están participando en el procedimiento. De manera que, podemos compartir información que nos indique cómo controlar determinadas estructuras de flujo, por ejemplo, si las funciones internas que ejecutan los nodos presentan saltos condicionales, bucles, etcétera. Cómo podemos ver, el procesamiento de grafos nos permite abordar problemas de una manera distinta a la vista hasta ahora. Por ejemplo, si lo comparamos con el esquema de MapReduce que hemos estudiado hasta ahora, a esta manera de procesar no la podríamos llevar a cabo con MapReduce porque, si nos fijamos en el esquema MapReduce, éste tiene dos limitaciones muy importantes. "Uno", que no está diseñado para computación iterativa, y "dos", que no está pensado tampoco para transmitir mensajes entre entidades. En el caso de la computación iterativa, nosotros sabemos que si quisiéramos que MapReduce funcionase iterativamente, nosotros tendríamos que intervenir en lo que es el paso de iteración a iteración, recogiendo los datos generados, volcándolos en algún sistema de ficheros y, luego, leyéndolos de manera efectiva para volverlos a repartir y que vuelven a ser computados, lo cual implica una gran cantidad de accesos a la entrada salida, con la degradación en cuanto a rendimiento que ello conlleva. En cuanto al paso de mensajes, es evidente que MapReduce tampoco está pensado para transmitir mensajes entre ellos, entre las distintas entidades, y existen muchos algoritmos, como los que hemos visto, que necesitan el paso de información entre vértices para poder continuar con la exploración de la información y el procesamiento efectivo de ésta. MapReduce, por ejemplo, no lo proporciona. Es en este tipo de casos en los que la computación basada en el procesamiento de grafos, nos es beneficiosa. Además de todo esto, el procesamiento de grafos nos ofrece otras ventajas a considerar dependiendo del problema que estemos tratando, de la necesidad que tengamos. Por un lado, tenemos que, como hemos comentado, las bases de datos orientadas a grafos o el análisis proveniente del procesamiento de grafos maneja de manera intensiva las relaciones, pero de manera mucho más eficiente de lo que lo harían, por ejemplo, las bases de datos relacionales. Sabemos que las bases de datos relacionales, para procesar relaciones entre entidades, tenemos que realizar una serie de "joins" e, incluso, crear estructuras o tablas intermedias lo cual se convierte, en muchas ocasiones, en dolores de cabeza importantes. Entonces, en este caso, para este tipo de problemas, el rendimiento que proporciona el procesamiento de grafos es mucho mejor, pero por órdenes de magnitud, respecto al de las bases de datos relacionales. Por otro lado, tenemos que los grafos en sí, como estructura, son muy flexibles, de manera que se adaptan fácilmente a medida que cambian las aplicaciones y, incluso, los modelos de negocio. En lugar de modelar un dominio o un esquema de bases de datos con anticipación, la mayoría de bases de datos orientadas a grafos nos permiten añadir entidades y cambiar los tipos de relaciones sin alterar el resto de la estructura. Aparte de esto, además de una cierta flexibilidad, nos ofrece versatilidad porque podemos adaptar nuestras líneas de investigación o podemos, incluso, adaptar la finalidad para la cual tenemos la propia base de datos. Y todo esto, además, sin interferir en el resto de procesos que ya teníamos implementados y que funcionaban de una determinada manera. Es decir, generalmente, en una base de datos orientada al grafo, el hecho de añadir nuevos nodos o modificar las relaciones, no interfiere en las anteriores operaciones que estábamos realizando. Como acostumbro a deciros, en función de las necesidades que tengamos dentro de nuestro negocio, tendremos que evaluar qué alternativa, en cuanto a herramientas, es más apropiada para nosotros. Por poner algunos ejemplos, aquí os muestro Apache Giraph, que corresponde a una herramienta de procesamiento de grafos, pero más orientada a un contexto analítico, es decir, de OLAP, lo que vimos hace algunas sesiones. Y, de esta manera, Giraph se utiliza para analizar datos de grafos de una manera orientada a algoritmos tipo "Page Rank", por ejemplo. Titán, por su parte, es una base de datos orientada a grafos, pero destinada más a un análisis OLTP, que es "OnLine Transactional Processing". En otras palabras, Titán se usa como una base de datos para consultas en tiempo real y actualizaciones, también, en tiempo real o casi real. De Titán, tenemos un sucesor, por así decirlo, que se denomina Janus Graph, que también está cobrando mucho protagonismo, dentro de distintas organizaciones. Janus Graph es un "fork" del proyecto Titan y se caracteriza, también, por estar muy, muy bien integrado con las distintas herramientas del ecosistema de Hadoop, de manera que se pueden realizar operaciones muy complementarias con Apache Spark, con Giraph mismo y con otras herramientas. Finalmente, otra herramienta muy utilizada en la industria es Neo4j que dispone de una licencia abierta, tipo GPL, y también viene ofrecido bajo una licencia de uso comercial que, evidentemente, proporciona funcionalidades más potentes. Tiene una API de Java muy apropiada, muy fácil de manejar, dispone de herramientas para procesos DTL, tiene almacenamiento de grafos nativo, procesamiento también nativo, evidentemente, está orientado a una alta disponibilidad y dispone de su propio lenguaje de consultas, que es un lenguaje mucho menos verboso, mucho menos denso, que el propio SQL. Hasta aquí, los capítulos destinados al estudio de "graph processing".