1. Introducción
En este tutorial, vamos a examinar JanusGraph y Gremlin. JanusGraph es una base de datos de grafos de código abierto, masivamente escalable, diseñada para soportar enormes grafos que requieren la colaboración de múltiples nodos de base de datos mientras permite trabajar con ellos de manera eficiente. Este avance se logra mediante la construcción sobre tecnologías bien soportadas como Cassandra, HBase, y Elasticsearch. Además, JanusGraph ofrece integración nativa con el stack de Apache TinkerPop, que incluye la consola Gremlin y el lenguaje de consulta Gremlin.
2. Ejecutando JanusGraph y Gremlin
Para ejecutar JanusGraph localmente, primero necesitamos descargar la última versión que, en el momento de escribir esto, es la versión 1.1.0. Una vez descargado, podemos descomprimirlo y estaremos listos para correrlo. Esto requiere que una JVM de Java 8 o superior esté ya instalada.
Una vez que está descomprimido, podemos iniciar una sesión de Gremlin ejecutando ./bin/gremlin.sh
o ./bin/gremlin.bat
desde nuestro directorio descomprimido:
-> % ./bin/gremlin.sh
\,,,/
(o o)
-----oOOo-(3)-oOOo-----
plugin activated: tinkerpop.server
plugin activated: tinkerpop.tinkergraph
08:45:56 INFO org.apache.tinkerpop.gremlin.hadoop.jsr223.HadoopGremlinPlugin.getCustomizers - HADOOP_GREMLIN_LIBS is set to: /Users/baeldung/janusgraph-1.1.0/lib
08:45:56 WARN org.apache.hadoop.util.NativeCodeLoader. - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
plugin activated: tinkerpop.hadoop
plugin activated: tinkerpop.spark
plugin activated: tinkerpop.utilities
plugin activated: janusgraph.imports
Esta instancia de Gremlin puede alojar una base de datos de JanusGraph en el proceso, lo cual es muy útil para probar cosas. Accedemos a esto usando el comando JanusGraphFactory.open
, apuntando a un archivo de configuración apropiado:
gremlin> graph = JanusGraphFactory.open('conf/janusgraph-inmemory.properties')
Esto creará una nueva variable graph
en nuestro cliente, que representará nuestra base de datos de grafos y funcionará como el punto de entrada para todas las futuras llamadas. Hay varios archivos de configuración estándar que podemos usar para diferentes backends de almacenamiento e indexado, pero el archivo janusgraph-inmemory.properties
es el más simple y configura una base de datos en memoria sin persistencia.
2.1. Servidor Autónomo
Alternativamente, podemos iniciar un servidor independiente de JanusGraph usando el comando ./bin/janusgraph-server.sh start
:
-> % ./bin/janusgraph-server.sh start
/Users/baeldung/janusgraph-1.1.0/conf/gremlin-server/gremlin-server.yaml will be used to start JanusGraph Server in background
Server started 8163
Por defecto, esto iniciará un servidor en el puerto 8182, corriendo con el mismo archivo janusgraph-inmemory.properties
. Sin embargo, dado que ahora es un servidor en funcionamiento, podemos conectar nuestros clientes a él. Podemos apuntar nuestro cliente Gremlin a un servidor remoto usando el comando :remote connect
:
gremlin> :remote connect tinkerpop.server conf/remote.yaml session
08:54:19 INFO org.apache.tinkerpop.gremlin.driver.Connection. - Created new connection for ws://localhost:8182/gremlin
Una vez hecho esto, también necesitamos indicarle a Gremlin que envíe todos los comandos al servidor remoto:
gremlin> :remote console
En este punto, cualquier cosa que hagamos funcionará con este servidor remoto.
3. Estructura del Grafo
Las bases de datos de grafos como JanusGraph representan los datos en un grafo. Esto significa que tenemos una combinación de vértices y aristas que los conectan. En JanusGraph, las aristas se conectan a exactamente dos vértices y tienen una dirección; siempre apuntan de un vértice a otro. Así, éstas siempre son grafos dirigidos. Sin embargo, no es necesario que sean acíclicas, ya que es válido tener ciclos de cualquier longitud entre nuestros vértices.
Representamos los datos en nuestra base de datos como etiquetas y propiedades en los vértices y aristas. Las aristas deben tener una etiqueta, y los vértices pueden tener una opcionalmente, lo que define el tipo de datos que estamos representando, ej., “artículo” o “escrito_por”. Adicionalmente, podemos tener un número arbitrario de pares clave/valor en cada vértice o arista, eg. “título: Introducción a JanusGraph”.
4. Cargando Datos de Ejemplo
La CLI de Gremlin viene con una fábrica especial para cargar algunos datos de ejemplo en una base de datos:
gremlin> GraphOfTheGodsFactory.loadWithoutMixedIndex(graph, true)
Esto carga un conjunto de datos de muestra llamado “El Grafo de los Dioses”:
Esto representa una pequeña porción del panteón romano y datos relacionados sobre ellos, pero es suficiente para demostrar cómo usar JanusGraph.
5. Consultando Datos
Ahora que tenemos una base de datos con datos de ejemplo, estamos listos para consultarlo. Antes de esto, necesitamos crear una fuente de recorrido:
gremlin> g = graph.traversal()
Esto crea una nueva variable g
como el punto de entrada para recorrer nuestros datos en el grafo.
5.1. Consultando Vértices
Lo primero que podemos hacer es consultar vértices individuales. La forma más sencilla de esto es listar cada vértice sin ningún filtro. La función V()
en nuestra variable g
nos da acceso a esto:
gremlin> g.V()
Sin embargo, esto no es muy útil. También podemos filtrar para obtener solo aquellos vértices que cumplen ciertos criterios usando la función has()
:
gremlin> g.V().has('name', 'hercules')
Sería útil también ver más sobre el vértice devuelto. Hasta ahora solo estamos obteniendo el ID interno. Si queremos, podemos ver el mapa completo de valores para el vértice retornado:
gremlin> g.V().has('name', 'hercules').valueMap()
O podemos obtener detalles individuales en su lugar:
gremlin> g.V().has('name', 'hercules').label()
gremlin> g.V().has('name', 'hercules').values('name')
También podemos asignar los resultados de nuestras consultas a una variable para su uso posterior:
gremlin> hercules = g.V().has('name', 'hercules').next()
Estamos utilizando la llamada next()
aquí para devolver la referencia del vértice en lugar del recorrido del grafo.
5.2. Navegando a Través de Aristas
Sin la capacidad de navegar a través de las aristas, no hay beneficio en representar nuestros datos como un grafo. Navegamos a través de las aristas en nuestra consulta usando las funciones in()
y out()
, dependiendo de la dirección de la arista que queremos navegar. Al usar out()
, esta sigue las aristas que apuntan hacia fuera desde el nodo en cuestión:
gremlin> g.V().has('name', 'hercules').out('father').valueMap()
Igualmente, al usar in()
, esto sigue aristas que apuntan hacia dentro del nodo:
gremlin> g.V().has('name', 'jupiter').in('father').valueMap()
Esto también permitirá que sigamos casos donde hay muchas aristas coincidentes para seguir:
gremlin> g.V().has('name', 'hercules').out('battled').valueMap()
Por lo tanto, es posible que necesitemos filtrar estos aún más. Como antes, esto se hace usando la función has()
:
gremlin> g.V().has('name', 'hercules').out('battled').has('name', 'hydra').valueMap()
Sin sorpresas, podemos seguir estas aristas tan lejos como necesitemos, permitiendo descubrimientos más complejos:
gremlin> g.V().has('name', 'hercules').out('battled').out('lives').in('lives').valueMap()
6. Agregando y Editando Datos
Ser capaz de consultar nuestros datos es importante, pero si no podemos manipularlos, entonces no es muy útil. Podemos agregar nuevos vértices a nuestro grafo utilizando la llamada graph.addVertex()
:
gremlin> theseus = graph.addVertex('human')
Note que, al ser llamada desde el graph
mismo y no desde el recorrido del grafo, obtenemos el nuevo vértice directamente. También podemos especificar algunas propiedades en la llamada addVertex()
:
gremlin> theseus = graph.addVertex(T.label, 'human', 'name', 'theseus')
Desde que las etiquetas de los vértices son opcionales, se representan en esta llamada como si fueran propiedades con un nombre especial de T.label
. Podemos más tarde actualizar las propiedades de este vértice usando la llamada property()
:
gremlin> theseus.property('name', 'theseus')
También podemos crear aristas usando el método addEdge()
:
gremlin> cerberus = g.V().has('name', 'cerberus').next()
gremlin> theseus.addEdge('met', cerberus)
Ahora podemos hacer uso de estos en nuestras consultas de datos:
gremlin> g.V().has('name', 'theseus').out('met').valueMap()
7. Conclusión
En este artículo, hemos dado un breve vistazo a JanusGraph y a lo que podemos hacer con él. Hay mucho más que se puede lograr utilizando esta base de datos, así que la próxima vez que necesite usar una base de datos de grafos, ¿por qué no echar un vistazo a JanusGraph?
Consejos Prácticos para Programadores Especializados en JAVA
- Conocimiento de Base de Datos: Familiarízate con los conceptos de bases de datos de grafos antes de profundizar en JanusGraph y Gremlin. Comprender los principios de los grafos te ayudará a utilizar las funcionalidades avanzadas de JanusGraph de manera efectiva.
- Abstracción y Uso de Funciones: Aprovecha las funciones de Gremlin para abstraer consultas complejas. Aprende a encadenar funciones y a utilizar variables de forma efectiva para manejar respuestas de consulta.
- Optimización de Consultas: Utiliza los índices para optimizar las consultas. Por defecto, JanusGraph permite las consultas iterativas, lo que puede ser ineficiente en bases de datos grandes.
- Configuraciones Avanzadas: Explora diferentes archivos de configuración para comprender cómo las configuraciones afectan el rendimiento y la escalabilidad en entornos de producción.
- Prueba y Error: No tengas miedo de experimentar con diferentes estructuras de datos y relaciones hasta que consigas la arquitectura de grafo que mejor se adapte a tus necesidades.
Con estos puntos en mente, estarás listo para empezar a trabajar con JanusGraph y Gremlin de manera efectiva.