Ejecuta JanusGraph en GKE con Bigtable

Las bases de datos de grafos te ayudan a descubrir información valiosa mediante el modelado de tus entidades de datos y las relaciones entre ellas. JanusGraph es una base de datos de grafos que permite trabajar con grandes cantidades de datos. En este instructivo, se muestra cómo ejecutar JanusGraph en Google Cloud con Google Kubernetes Engine como la plataforma de organización y Bigtable como backend de almacenamiento. El instructivo es para arquitectos de sistemas, administradores de bases de datos y profesionales de DevOps que estén interesados en ejecutar la base de datos de grafos de JanusGraph en Google Cloud con una base de datos administrada como backend de almacenamiento. Suponemos que estás familiarizado conGoogle Kubernetes Engine (GKE), los Pods de Kubernetes, los gráficos de Helm, Bigtable y Elasticsearch. No se requieren conocimientos sobre el framework de procesamiento de grafos de Apache TinkerPop ni sobre el lenguaje y la máquina de recorrido de grafos Gremlin, pero ese conocimiento es necesario usar Janusgraph más allá de los ejemplos proporcionados en este instructivo.

Descripción general

En la terminología de los grafos, las entidades se conocen como nodos o vértices y las relaciones como aristas. En JanusGraph, tanto los vértices como las aristas pueden tener datos asociados adicionales que están disponibles a través de propiedades.

Ejemplo de un grafo de propiedades

La ilustración anterior es un ejemplo de un grafo de propiedades.

Las bases de datos de grafos te ayudan a modelar diferentes dominios y actividades:

  • Redes sociales
  • Transacciones financieras (para análisis de fraudes)
  • Redes de sistemas físicos o virtuales

Cuando se crean bases de datos de grafos, a veces se generan millones o incluso miles de millones de vértices y aristas. Cuando utilizas JanusGraph con Bigtable como la capa de almacenamiento subyacente, puedes ejecutar consultas rápidas (conocidas como recorridos de grafos) y escalar tu capa de almacenamiento de forma independiente según el tamaño. y la capacidad de procesamiento que necesites. JanusGraph también utiliza un backend de indexación conectable a fin de proporcionar indexación de texto completo para las propiedades de vértices y aristas. En este instructivo, implementarás una infraestructura escalable de JanusGraph en GKE. Usarás Elasticsearch como el backend de indexación que se ejecuta en Pods en un StatefulSet, y usarás Bigtable como backend de almacenamiento. Cuando termines, podrás recorrer las relaciones que existen en tus datos de grafos. En el siguiente diagrama, se muestra cómo estos elementos se conectan entre sí.

Implementación de JanusGraph con Bigtable en GKE

En el diagrama anterior, se muestra la implementación de JanusGraph en GKE con Elasticsearch y Bigtable.

Datos de JanusGraph en Bigtable

JanusGraph almacena los datos de grafos como una lista de adyacencia. Cada fila representa un vértice, cualquier vértice adyacente (aristas) y metadatos de propiedad sobre los vértices y las aristas. La clave de fila es el identificador único del vértice. Cada relación entre el vértice y otro vértice y cualquier propiedad que defina aún más la relación, se almacena como una columna de arista o de propiedad de arista. El calificador de columnas y el valor de columna almacenan datos que definen la arista, de conformidad con las prácticas recomendadas de Bigtable. Cada propiedad de vértice se almacena como una columna separada, usando el calificador de columna y el valor de columna para definir la propiedad.

En el siguiente diagrama, se muestra esta estructura de almacenamiento.

Estructura de almacenamiento de la lista de adyacencia de JanusGraph.

El diagrama muestra la estructura de almacenamiento lógica para un fragmento de grafo pequeño con detalles lógicos para dos filas de vértices. En el diagrama, las dos filas de ejemplo representan dos vértices. El primer vértice está etiquetado con una sola propiedad de vértice y está relacionado con otros dos vértices mediante dos aristas independientes. El segundo vértice tiene columnas que contienen dos propiedades y una arista.

En la siguiente ilustración del modelo de datos lógicos de vértices, se proporcionan algunos detalles sobre los calificadores y los valores de columnas para una columna de arista o propiedad de arista.

Columna de propiedad de aista o de propiedad de arista y JanusGraph

Para cada vértice adyacente, una columna almacena los metadatos sobre esa arista. El calificador de columnas contiene metadatos sobre la relación de arista y la dirección de arista, y un puntero al vértice adyacente. El valor de la columna contiene la etiqueta de arista y cualquier propiedad de arista adicional. Debido a que los recorridos se pueden seguir en cualquier dirección, las aristas se almacenan dos veces, una para cada extremo de la relación de arista. El almacenamiento arista bidireccional aumenta significativamente el rendimiento del recorrido, pero conlleva algunas compensaciones debido a la redundancia del espacio de almacenamiento adicional y las mutaciones de arista no atómicas.

En el siguiente diagrama, se muestra el modelo de datos lógico de una columna de propiedad de vértice.

Valores de columna de JanusGraph para una columna de propiedad.

En la ilustración anterior, se proporcionan detalles sobre los calificadores y los valores de una columna de arista.

Cada propiedad de vértice se almacena como una columna separada. El calificador de columna es un identificador único para la clave de propiedad. El valor de columna contiene un identificador para la propiedad y el valor de la propiedad.

JanusGraph también se basa en el orden lexicográfico de Bigtable de las filas y los calificadores de columna para mejorar el rendimiento de las consultas.

Objetivos

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices las tareas que se describen en este documento, puedes borrar los recursos que creaste para evitar que continúe la facturación. Para obtener más información, consulta Cómo realizar una limpieza.

Requisitos previos

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the Bigtable, Compute Engine, and GKE APIs.

    Enable the APIs

  5. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  6. Make sure that billing is enabled for your Google Cloud project.

  7. Enable the Bigtable, Compute Engine, and GKE APIs.

    Enable the APIs

Prepare el entorno

En este instructivo, usarás Cloud Shell para ingresar comandos. Cloud Shell te brinda acceso a la línea de comandos en la consola de Google Cloud e incluye Google Cloud CLI y otras herramientas que necesitas para el desarrollo en Google Cloud. Cloud Shell aparece como una ventana en la parte inferior de la consola de Google Cloud. Es posible que la inicialización tome unos minutos, pero la ventana aparecerá de inmediato.

  1. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

  2. En Cloud Shell, configura las variables de entorno para la zona de Compute Engine en la que crearás el clúster de Bigtable y el clúster de GKE, y el nombre, el tipo de nodo y la versión de tu clúster de GKE:

    export PROJECT_ID=PROJECT_ID
    export GCP_ZONE=REGION
    export GKE_CLUSTER_NAME=GKE_CLUSTER_NAME
    export GKE_NODE_TYPE=n1-standard-4
    export GKE_VERSION=1.20
    

    Reemplaza lo siguiente:

    • PROJECT_ID por el identificador de tu proyecto.
    • REGION por la zona en la que se crearán el clúster de Bigtable y el clúster de GKE.
    • GKE_CLUSTER_NAME por el nombre de tu clúster de GKE.

    El comando debe ser similar al siguiente ejemplo:

    export PROJECT_ID=bt-janusgraph-project-id
    export GCP_ZONE=us-central1-f
    export GKE_CLUSTER_NAME=janusgraph-gke
    export GKE_NODE_TYPE=n1-standard-4
    export GKE_VERSION=1.20
    
  3. Crea un clúster de GKE en el que se implementará JanusGraph:

    gcloud container clusters create ${GKE_CLUSTER_NAME} \
        --zone=${GCP_ZONE} \
        --cluster-version=${GKE_VERSION} \
        --machine-type ${GKE_NODE_TYPE} \
        --scopes "https://www.googleapis.com/auth/cloud-platform"
    

Crear una instancia de Bigtable.

Para el backend de almacenamiento de JanusGraph, en este instructivo se usa Bigtable, que puede escalar con rapidez para adaptarse a tus necesidades. En el instructivo se usa un clúster de nodo único, una opción rentable y suficiente para este caso. Puedes comenzar tus proyectos con un clúster más pequeño y, luego, pasar a un clúster más grande cuando estés listo para trabajar con datos de producción. La documentación de Bigtable incluye un análisis detallado sobre el rendimiento y el escalamiento que te ayudará a elegir el tamaño de clúster para tu trabajo.

  1. En Cloud Shell, configura la variable de entorno para el identificador de instancias de Bigtable:

    export BIGTABLE_INSTANCE_ID=BIGTABLE_INSTANCE_ID
    

    Reemplaza BIGTABLE_INSTANCE_ID por el identificador de tu instancia de Bigtable.

  2. Crea la instancia de Bigtable:

    gcloud bigtable instances create ${BIGTABLE_INSTANCE_ID} \
        --cluster-config=id=${BIGTABLE_INSTANCE_ID}-${GCP_ZONE},zone=${GCP_ZONE},nodes=1 \
        --display-name=${BIGTABLE_INSTANCE_ID}-${GCP_ZONE}
    

Instala y configura Helm

Helm se utiliza para implementar aplicaciones en tu clúster de Kubernetes. En este instructivo, usarás Helm para implementar los servicios de JanusGraph y Elasticsearch en tu clúster de GKE.

  1. En Cloud Shell, instala Helm:

    curl -fsSL -o get_helm.sh \
        https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
    chmod 700 get_helm.sh
    DESIRED_VERSION=v3.5.0 ./get_helm.sh
    
  2. Agrega el repositorio de chart elastic para que se pueda encontrar la dependencia del chart de Elasticsearch durante la implementación del chart de JanusGraph:

    helm repo add elastic https://helm.elastic.co
    

    Elastic, el creador de Elasticsearch, aloja este repositorio de chart.

Usa de Helm para instalar JanusGraph y Elasticsearch

En esta sección, usas un gráfico de Helm para implementar JanusGraph y Elasticsearch en tu clúster de Kubernetes.

El chart de Helm se extrae de GitHub. El Deployment incluido en el repositorio de chart de Helm implementa un conjunto de tres Pods de JanusGraph detrás de un Service que iniciará un balanceador de cargas de aplicaciones interno. Cuando los Pods se ejecutan, los sondeos de inicio y funcionamiento realizan solicitudes HTTP para realizar verificaciones de estado en el servidor de JanusGraph de cada Pod. Además, el chart incluye un chart de dependencias proporcionado por Elastic que implementa tres Pods de Elasticsearch en un StatefulSet.

  1. En Cloud Shell, configura las variables de entorno para los nombres de Helm y JanusGraph:

    export HELM_REPO=bigtable-janusgraph-helm
    export JANUSGRAPH_VERSION=0.5.3
    export HELM_CHART_RELEASE_VERSION=1
    export HELM_CHART_RELEASE_TAG=${JANUSGRAPH_VERSION}-${HELM_CHART_RELEASE_VERSION}
    export HELM_CHART_RELEASE_TAG_HASH=f8b271a4854d4a553dd5e9ba014d077fb098d9ab
    export HELM_CHART_NAME=janusgraph-bigtable
    
  2. Extrae el chart de Helm desde GitHub:

    git clone https://github.com/GoogleCloudPlatform/${HELM_REPO} \
       --branch ${HELM_CHART_RELEASE_TAG}
    
  3. Navega al directorio del gráfico de Helm:

    cd ${HELM_REPO}
    
  4. Por motivos de seguridad, verifica mediante el hash de confirmación:

    HEAD_COMMIT_HASH=$(git rev-parse --verify HEAD)
    if [ _${HEAD_COMMIT_HASH} == _${HELM_CHART_RELEASE_TAG_HASH} ]
    then
        echo "Commit hash verified"
    fi
    

    Si el resultado no es similar al siguiente, no continúes, ya que la integridad de la etiqueta clonada no se verificó.

    Commit hash verified
    
  5. Actualiza las dependencias del chart:

    helm dep update
    
  6. Navega al directorio superior:

    cd ..
    
  7. Establece variables de entorno para los nombres de las entidades de Helm y JanusGraph:

    export HELM_RELEASE_NAME=janusgraph-bigtable-elastic
    export ELASTICSEARCH_CLUSTER_NAME=${HELM_RELEASE_NAME}-elasticsearch
    export BIGTABLE_JANUSGRAPH_TABLE=janusgraph-table
    
  8. Crea un archivo values.yaml que proporcione a Helm las propiedades de configuración que se deben usar cuando se implemente el chart de JanusGraph:

    cat > values.yaml << EOF
    
    image:
      repository: docker.io/janusgraph/janusgraph
      tag: 0.5.3
      pullPolicy: IfNotPresent
    
    replicaCount: 3
    
    service:
      type: LoadBalancer
      port: 8182
      serviceAnnotations:
        networking.gke.io/load-balancer-type: "Internal"
    
    elasticsearch:
      deploy: true
      clusterName: ${ELASTICSEARCH_CLUSTER_NAME}
    
    properties:
      storage.backend: hbase
      storage.directory: null
      storage.hbase.ext.google.bigtable.instance.id: ${BIGTABLE_INSTANCE_ID}
      storage.hbase.ext.google.bigtable.project.id: ${PROJECT_ID}
      storage.hbase.ext.hbase.client.connection.impl: com.google.cloud.bigtable.hbase2_x.BigtableConnection
      storage.hbase.short-cf-names: true
      storage.hbase.table: ${BIGTABLE_JANUSGRAPH_TABLE}
      index.search.backend: elasticsearch
      index.search.hostname: ${ELASTICSEARCH_CLUSTER_NAME}-master
      index.search.directory: null
      index.search.elasticsearch.health-request-timeout: 90s
      cache.db-cache: true
      cache.db-cache-clean-wait: 20
      cache.db-cache-time: 180000
      cache.db-cache-size: 0.5
      cluster.max-partitions: 1024
      graph.replace-instance-if-exists: true
    
    persistence:
      enabled: false
    
    debugLevel: INFO
    EOF
    
  9. Implementa el gráfico de Helm de JanusGraph mediante el archivo values.yaml que creaste:

    helm upgrade --install \
                 --wait \
                  --timeout 600s \
                  ${HELM_RELEASE_NAME} \
                  ./${HELM_REPO} \
                  -f values.yaml
    

    El proceso de instalación espera hasta que todos los recursos estén listos antes de finalizar. Este proceso puede tardar varios minutos.

Verifica la implementación de JanusGraph

Cuando finalice el proceso de instalación de Helm, se mostrará una sección NOTES en la que se describe una experiencia de inicio. Puedes seguir los pasos detallados en la sección NOTES para verificar que tu entorno JanusGraph funcione.

  1. En Cloud Shell, verifica que los componentes del chart de Helm se hayan implementado en GKE:

    1. Comprueba la implementación de JanusGraph:

      kubectl get deployments
      

      Si la implementación se realizó de forma correcta, el resultado es el siguiente:

      NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
      janusgraph-bigtable-elastic   3/3     3            3           3m28s
      
    2. Verifica el StatefulSet de Elasticsearch:

      kubectl get statefulsets
      

      Si todo funciona, el resultado es el siguiente:

      NAME                                               READY   AGE
      janusgraph-bigtable-elastic-elasticsearch-master   3/3     4m13s
      
  2. Establece una variable de entorno para el nombre de un Pod de Kubernetes que ejecute el servidor JanusGraph Gremlin. La etiqueta app para el Pod que ejecuta el servidor Gremlin se deriva del nombre del gráfico de Helm que se define en el archivo Chart.yaml.

    export APP_LABEL_FROM_CHART_NAME=${HELM_CHART_NAME}
    export POD_NAME=$(kubectl get pods \
                         --namespace default \
                         -l "app=${APP_LABEL_FROM_CHART_NAME}, \
                             release=${HELM_RELEASE_NAME}" \
                         -o jsonpath="{.items[0].metadata.name}")
    
  3. Conéctate al Pod y ejecuta la consola Gremlin, una shell de bucle de lectura, evaluación e impresión (REPL). El nombre del contenedor también se deriva del nombre del chart de Helm en Chart.yaml.

    export GREMLIN_CONTAINER=${HELM_CHART_NAME}
    kubectl exec \
            -c ${GREMLIN_CONTAINER} \
            -it $POD_NAME \
            -- /opt/janusgraph/bin/gremlin.sh
    
  4. En la consola Gremlin conéctate al servidor Apache TinkerPop:

    1. Inicia la sesión:

      :remote connect tinkerpop.server conf/remote.yaml session
      

      El resultado es similar al siguiente:

      ==>Configured localhost/127.0.0.1:8182-[b08972f2-a2aa-4312-8018-bcd11bc9812c]
      
    2. Conéctate al servidor:

      :remote console
      

      El resultado es similar al siguiente:

      ==>All scripts will now be sent to Gremlin Server - [localhost/127.0.0.1:8182]-[b08972f2-a2aa-4312-8018-bcd11bc9812c] - type ':remote console' to return to local mode>
      
  5. En la consola Gremlin verifica que el servidor Gremlin se ejecute correctamente mediante la inspección de la variable graph, que representa la instancia de grafo:

    graph
    

    El resultado indica que el servidor de JanusGraph se ejecuta con una base de datos compatible con HBase (en este caso, Bigtable) como backend de almacenamiento.

    ==>standardjanusgraph[hbase:[127.0.0.1]]
    
  6. En Gremlin, crea dos vértices

    v1 = graph.addVertex(label, 'hello')
    v2 = graph.addVertex(label, 'world')
    

    Si el resultado de la consola es similar al siguiente, indica que se agregaron los dos vértices:

    ==>v[4344]
    ==>v[4152]
    
  7. Crea una arista que conecte los dos vértices:

    v1.addEdge('followedBy', v2)
    

    Si el resultado de la consola es similar al siguiente, indica que se agregó la arista entre los dos vértices:

    ==>e[17j-3co-4fmd-oe054][4344-followedBy->4152]
    
  8. Confirma la transacción:

    graph.tx().commit()
    

    Si el resultado de la consola es null, indica que las operaciones se confirmaron:

    ==>null
    

    En el siguiente diagrama, se ilustra el grafo que crean los comandos.

    Vértices y arista de ejemplo de JanusGraph.

    El vértice etiquetado como hello está conectado por una arista dirigida con la etiqueta followedBy al vértice etiquetado como world.

  9. Genera una consulta de Gremlin para ver cuál es la etiqueta del vértice que sigue a una arista etiquetada como followedBy desde el vértice que está etiquetado como hello:

    g.V().has(label, 'hello').out('followedBy').label()
    

    La sintaxis de la consulta se explica en la siguiente sección. Por ahora ves la palabra world como el resultado de la consulta:

    ==>world
    

Carga y consulta un conjunto de datos de muestra

Ahora que implementaste JanusGraph y puedes conectarte a él mediante Gremlin, es momento de comenzar a cargar y consultar tus propios datos. Para ver cómo se ve ese proceso, carga el conjunto de datos de muestra que viene incluido en JanusGraph: Graph of the Gods (Gráfico de los dioses), que representa las deidades mitológicas del pantheon clásico y sus propiedades.

  1. En Gremlin, carga el grafo que creaste antes:

    GraphOfTheGodsFactory.load(graph)
    

    El resultado es el siguiente:

    ==>null
    
  2. Genera una consulta de recorrido de grafos que encuentre todos los hermanos de Júpiter:

    g.V().has('name', 'jupiter').out('brother').values('name')
    

    En la siguiente tabla, se explican los pasos que recorre la consulta.

    Paso de recorrido Explicación
    g.V() Comienza con la recolección de vértices.
    has('name', 'jupiter') Encuentra uno que tenga la propiedad name con el valor jupiter.
    out('brother') Desde allí sigue cualquier arista etiquetada como brother.
    values('name') Para los vértices a los que conducen esas aristas, obtén la propiedad name.
    El resultado es el siguiente:

    ==>neptune
    ==>pluto
    

    Para familiarizarte con las consultas de recorrido que son posibles en el conjunto de datos del grafo de los dioses, prueba otras consultas de muestra incluidas en los documentos de JanusGraph.

Verifica que los datos estén almacenados en Bigtable

Ahora que ya creaste datos de muestra en el clúster de JanusGraph, puedes verificar que Bigtable se haya usado como backend de almacenamiento.

  1. Cierra la consola Gremlin:

    :q
    
  2. En Cloud Shell, verifica que los datos se hayan conservado en la tabla janusgraph en Bigtable:

    cbt -project=${PROJECT_ID} \
        -instance=${BIGTABLE_INSTANCE_ID} \
         count ${BIGTABLE_JANUSGRAPH_TABLE}
    

    El resultado es similar al siguiente.

    2021/03/02 02:32:19 -creds flag unset, will use gcloud credential
    101
    

    El valor 101 en el resultado representa la cantidad de filas en la janusgraph table y puede ser diferente en tu caso.

Verifica la creación del índice de búsqueda en Elasticsearch

  1. En Cloud Shell, configura las variables para el índice y el nombre de los Pods de Elasticsearch:

    export ELASTICSEARCH_POD_ORDINAL=0
    export ELASTICSEARCH_POD_NAME_ROOT=${ELASTICSEARCH_CLUSTER_NAME}-master
    export ELASTICSEARCH_POD=${ELASTICSEARCH_POD_NAME_ROOT}-0
    

    Los nombres de los Pods de Elasticsearch se definen mediante las dependencias de Helm para Elasticsearch. Los nombres de los Pods constan del nombre del clúster que se proporciona en el archivo values.yaml que creaste, la palabra master y un número ordinal no indexado, todo separado por guiones. Para este paso, debes elegir el primer Pod, representado como cero (0).

  2. Usa la API de REST de Elasticsearch Alias para inspeccionar los índices:

    kubectl exec \
            -c elasticsearch \
            -it ${ELASTICSEARCH_POD} \
            --  \
            curl -XGET "127.0.0.1:9200/_aliases?pretty=true";
    

    El resultado muestra dos índices, janusgraph_vertices yjanusgraph_edges, creados por JanusGraph para proporcionar búsquedas eficientes mediante vértices y propiedades perimetrales:

    {
      "janusgraph_vertices" : {
        "aliases" : {
          "janusgraph" : { }
        }
      },
      "janusgraph_edges" : {
        "aliases" : {
          "janusgraph" : { }
        }
      }
    }
    
  3. Consulta los valores de uno de los índices mediante la API de REST de búsqueda de Elasticsearch:

    kubectl exec \
           -c elasticsearch \
           -it ${ELASTICSEARCH_POD} \
           --  \
           curl -XGET "127.0.0.1:9200/janusgraph_edges/_search?pretty=true&q=*";
    

    Los resultados de la búsqueda muestran que hay entradas en los índices creados por JanusGraph. El resultado que ves es similar a los siguientes resultados truncados, que muestran que hay entradas en el índice janusgraph_edges.

    {
     "took" : 94,
     "timed_out" : false,
     "_shards" : {
       "total" : 1,
       "successful" : 1,
       "skipped" : 0,
       "failed" : 0
     },
     "hits" : {
       "total" : {
         "value" : 6,
         "relation" : "eq"
       },
       "max_score" : 1.0,
       "hits" : [
         {
           "_index" : "janusgraph_edges",
           "_type" : "_doc",
           "_id" : "6bvp-5ovc-b2t-2yko",
           "_score" : 1.0,
           "_source" : {
             "reason" : "loves waves"
           }
         },
         {
    …
    

Borra el proyecto

Para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo, borra el proyecto que contiene los recursos o conserva el proyecto y borra los recursos individuales.

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

¿Qué sigue?