Bigtable を使用して GKE で JanusGraph を実行する

グラフ データベースは、データ エンティティとその間の関係をモデリングすることで、分析情報を得るために役立ちます。JanusGraph は、大量のデータを扱うことをサポートするグラフ データベースです。このチュートリアルでは、オーケストレーション プラットフォームとして Google Kubernetes Engine を使用し、ストレージ バックエンドとして Bigtable を使用して、Google Cloud で JanusGraph を実行する方法について説明します。このチュートリアルは、ストレージ バックエンドとしてマネージド データベースを使用し、Google Cloud で JanusGraph のグラフ データベースを実行することを検討しているシステム アーキテクト、データベース管理者、DevOps プロフェッショナルを対象としています。また、Google Kubernetes Engine(GKE)、Kubernetes Pod、Helmグラフ、BigtableElasticsearch に関する知識があることを前提としています。Apache TinkerPop グラフ コンピューティング フレームワークと Gremlin グラフ走査マシンと言語に関する知識は必要ありませんが、このチュートリアルで説明する例を超えた Janusgraph を使用する場合には、これらの知識が必要になります。

概要

グラフ用語では、エンティティは「ノード」または「頂点」と呼ばれ、関係は「エッジ」と呼ばれます。JanusGraph では、頂点と辺のどちらでも、プロパティを介して使用できるようになる追加の関連データを使用できます。

プロパティ グラフの例

上の図はプロパティ グラフの例です。

グラフ データベースは、さまざまなドメインとアクティビティをモデル化するのに役立ちます。

  • ソーシャル ネットワーク
  • 金融取引(不正行為の分析用)
  • 物理的ネットワークまたは仮想システム ネットワーク

グラフ データベースを作成する際は、数百万、場合によっては数十億の頂点とエッジを作成することがあります。Bigtable をストレージ層の基礎として JanusGraph を使用する場合は、高速クエリ(グラフ走査)を実行し、サイズとスループットに応じてストレージ層を個別にスケーリングできます。JanusGraph はまた、プラグイン可能なインデックス バックエンドを使用して、頂点とエッジのプロパティに対する全文インデックスを提供します。このチュートリアルでは、スケーラブルな JanusGraph インフラストラクチャを GKE にデプロイします。StatefulSet の Pod で実行されるインデックス バックエンドとして Elasticsearch を使用し、ストレージ バックエンドとして Bigtable を使用します。完了すると、グラフデータ内に存在する関係を走査できます。次の図は、これらの要素がどのように組み合わされているかを示しています。

GKE での JanusGraph のデプロイと Bigtable

図は、Elasticsearch と Bigtable を使用した GKE への JanusGraph のデプロイを示しています。

Bigtable での JanusGraph データ

グラフデータは、隣接リストとして JanusGraph によって保存されます。各行は、頂点、隣接する頂点(エッジ)、頂点とエッジに関するプロパティ メタデータを表します。行キーは頂点の一意の識別子です。頂点と別の頂点の関係や関係をさらに定義するプロパティは、エッジ列またはエッジ プロパティ列として保存されます。エッジを定義するデータは、Bigtable のベスト プラクティスに沿って列修飾子と列値の両方に保存されます。列修飾子と列値の両方を使用してプロパティを定義するため、各頂点プロパティは別々の列として保存されます。

次の図は、このストレージ構造を示しています。

JanusGraph の隣接リストのストレージ構造。

この図は、小さなグラフ フラグメントの論理ストレージ構造と、2 つの頂点行の論理の詳細を示しています。この図では、2 つのサンプル行が 2 つの頂点を表しています。最初の頂点は単一の頂点プロパティでラベル付けされ、2 つの別個のエッジによって他の 2 つの頂点に関連付けられています。2 つ目の頂点は、2 つのプロパティと 1 つのエッジを含む列を保持しています。

次の図は、頂点エッジの論理データモデルで、エッジまたはエッジのプロパティ列の列修飾子と値の詳細を示しています。

JanusGraph のエッジとエッジのプロパティ列。

隣接する頂点ごとに、そのエッジに関するメタデータが列に格納されます。列修飾子には、エッジの関係とエッジ方向に関するメタデータ、隣接する頂点へのポインタが含まれます。列の値には、エッジラベルと追加のエッジ プロパティが含まれています。走査はどちらの方向にも可能であるため、エッジはエッジの関係の両端に 1 つずつ、合計 2 回保存されます。双方向のエッジ ストレージでは走査パフォーマンスが大幅に向上しますが、追加のストレージ容量の冗長性と非アトミック エッジ ミューテーションによるトレードオフがあります。

次の図は、頂点プロパティ列の論理データモデルを示しています。

プロパティ列の JanusGraph 列の値。

上の図は、エッジ列の列修飾子と値の詳細を示しています。

各頂点プロパティは、個別の列として保存されます。列修飾子はプロパティキーの一意の識別子です。列の値には、プロパティの識別子とプロパティの値の両方が含まれます。

JanusGraph は Bigtable の行と列の修飾子の辞書順に依存し、クエリのパフォーマンスを向上させます。

目標

  • Bigtable インスタンスを作成する。
  • GKE クラスタを作成する。
  • Helm をインストールする
  • Helm チャートを使用して JanusGraph と Elasticsearch をデプロイする。
  • Gremlin コンソールを使用して JanusGraph に接続する。
  • サンプルデータを読み込んでクエリする。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

前提条件

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  3. Google Cloud プロジェクトで課金が有効になっていることを確認します

  4. Bigtable、Compute Engine、GKE API を有効にします。

    API を有効にする

  5. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  6. Google Cloud プロジェクトで課金が有効になっていることを確認します

  7. Bigtable、Compute Engine、GKE API を有効にします。

    API を有効にする

環境を準備する

このチュートリアルでは、Cloud Shell を使用してコマンドを入力します。Cloud Shell では Google Cloud コンソールのコマンドラインにアクセスできます。また、Google Cloud で開発を行うために必要な Google Cloud CLI やその他のツールも含まれています。Cloud Shell は、Google Cloud コンソールの下部にウィンドウとして表示されます。初期化が完了するまでに数分かかることもありますが、ウィンドウはすぐに表示されます。

  1. Google Cloud コンソールで、「Cloud Shell をアクティブにする」をクリックします。

    Cloud Shell をアクティブにする

  2. Cloud Shell で、Bigtable クラスタと GKE クラスタを作成する Compute Engine ゾーンの環境変数と、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
    

    次のように置き換えます。

    • PROJECT_ID はプロジェクト ID に置き換えます。
    • REGION は、Bigtable クラスタと GKE クラスタが作成されるゾーンに置き換えます。
    • GKE_CLUSTER_NAME は GKE クラスタの名前に置き換えます。

    コマンドは次の例のようになります。

    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. JanusGraph がデプロイされる GKE クラスタを作成します。

    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"
    

Bigtable インスタンスを作成する

JanusGraph ストレージのバックエンドとして、このチュートリアルではニーズに合わせてすぐにスケーリングできる Bigtable を使用します。このチュートリアルでは単一ノードクラスタを使用します。経済的で、このチュートリアルに十分対応できます。小規模なクラスタでプロジェクトを開始し、本番環境データを使用する準備ができたら、規模を大きくしたクラスタに移行してください。作業用のクラスタサイズを選択する際の参考としては、Bigtable のドキュメントに記載されているパフォーマンスとスケーリングに関する詳細な説明をご覧ください。

  1. Cloud Shell で、Bigtable インスタンス識別子の環境変数を設定します。

    export BIGTABLE_INSTANCE_ID=BIGTABLE_INSTANCE_ID
    

    BIGTABLE_INSTANCE_ID は、Bigtable インスタンスの ID に置き換えます。

  2. 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}
    

Helm をインストールして構成する

Helm を使用して、アプリケーションを Kubernetes クラスタにデプロイします。このチュートリアルでは、Helm を使用して GKE クラスタに JanusGraph サービスと Elasticsearch サービスの両方をデプロイします。

  1. Cloud Shell で 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. elastic チャート リポジトリを追加して、JanusGraph チャートのデプロイ時に Elasticsearch チャートの依存関係を検出できるようにします。

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

    このチャート リポジトリは、Elasticsearch の作成者である Elastic がホストしています。

Helm を使用して JanusGraph と Elasticsearch をインストールする

このセクションでは、Helm チャートを使用して、JanusGraphElasticsearch を Kubernetes クラスタにデプロイします。

Helm チャートは GitHub から pull されます。Helm チャート リポジトリに含まれる Deployment では、内部アプリケーション ロードバランサを起動する Service の背後で一連の 3 つの JanusGraph Pod がデプロイされます。Pod が実行されているときに、起動と liveness プローブが HTTP リクエストを作成し、各 Pod の JanusGraph サーバーでヘルスチェックを実行します。さらに、グラフには、Elastic から提供され、StatefulSet に 3 つの Elasticsearch Pod をデプロイする依存関係が含まれています。

  1. Cloud Shell で、Helm 名と 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. GitHub から Helm チャートを pull します。

    git clone https://github.com/GoogleCloudPlatform/${HELM_REPO} \
       --branch ${HELM_CHART_RELEASE_TAG}
    
  3. Helm チャートのディレクトリに移動します。

    cd ${HELM_REPO}
    
  4. セキュリティ上の理由から、commit ハッシュを使用して検証します。

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

    出力が次のように表示されない場合は、クローニングされたタグの整合性が検証されていないため、処理を続行しないでください

    Commit hash verified
    
  5. チャートの依存関係を更新します。

    helm dep update
    
  6. 親ディレクトリに移動します。

    cd ..
    
  7. Helm エンティティと JanusGraph エンティティの名前の環境変数を設定します。

    export HELM_RELEASE_NAME=janusgraph-bigtable-elastic
    export ELASTICSEARCH_CLUSTER_NAME=${HELM_RELEASE_NAME}-elasticsearch
    export BIGTABLE_JANUSGRAPH_TABLE=janusgraph-table
    
  8. values.yaml ファイルを作成します。このファイルに、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. 作成した values.yaml ファイルを使用して、JanusGraph に Helm チャートをデプロイします。

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

    インストール プロセスは、すべてのリソースが準備できるまで待ってから実行されます。このプロセスには数分かかることがあります。

JanusGraph のデプロイを確認する

Helm のインストール プロセスが完了すると、最初の注意事項を説明する NOTES セクションが表示されます。NOTES セクションの手順で、JanusGraph 環境が機能していることを確認します。

  1. Cloud Shell で、GKE にデプロイされた Helm チャート コンポーネントを確認します。

    1. JanusGraph のデプロイを確認します。

      kubectl get deployments
      

      デプロイに成功すると、出力は次のようになります。

      NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
      janusgraph-bigtable-elastic   3/3     3            3           3m28s
      
    2. Elasticsearch StatefulSet を確認します。

      kubectl get statefulsets
      

      すべてが問題なく動作している場合、出力は次のようになります。

      NAME                                               READY   AGE
      janusgraph-bigtable-elastic-elasticsearch-master   3/3     4m13s
      
  2. 環境変数に、JanusGraph Gremlin サーバーを実行している Kubernetes Pod の名前を設定します。Gremlin サーバーを実行している Pod の app ラベルは、Chart.yaml ファイルで定義された Helm チャート名から作成されます。

    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. Pod に接続して Gremlin コンソールを実行します。これは REPL(読み取り、評価、出力のループ)シェルです。コンテナの名前は Chart.yaml の Helm チャート名から取得されます。

    export GREMLIN_CONTAINER=${HELM_CHART_NAME}
    kubectl exec \
            -c ${GREMLIN_CONTAINER} \
            -it $POD_NAME \
            -- /opt/janusgraph/bin/gremlin.sh
    
  4. Gremlin コンソールで、Apache TinkerPop サーバーに接続します。

    1. セッションを開始します。

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

      出力は次のようになります。

      ==>Configured localhost/127.0.0.1:8182-[b08972f2-a2aa-4312-8018-bcd11bc9812c]
      
    2. サーバーに接続します。

      :remote console
      

      出力は次のようになります。

      ==>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. Gremlin コンソールで、グラフ インスタンスを表す graph 変数を調べて、Gremlin サーバーが正しく実行されていることを確認します。

    graph
    

    出力は、JanusGraph サーバーが HBase 互換のデータベース(この場合は Bigtable)をストレージ バックエンドとして実行していることを示しています。

    ==>standardjanusgraph[hbase:[127.0.0.1]]
    
  6. Gremlin で 2 つの頂点を作成します。

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

    コンソール出力が次のようになった場合は、2 つの頂点が追加されていることを示しています。

    ==>v[4344]
    ==>v[4152]
    
  7. 2 つの頂点を接続するエッジを作成します。

    v1.addEdge('followedBy', v2)
    

    コンソール出力が次のようになった場合は、2 つの頂点の間にエッジが追加されたことを示しています。

    ==>e[17j-3co-4fmd-oe054][4344-followedBy->4152]
    
  8. トランザクションを commit します。

    graph.tx().commit()
    

    コンソールの出力が null の場合は、オペレーションが commit されたことを示します。

    ==>null
    

    次の図は、コマンドによって作成されたグラフを示しています。

    JanusGraph の頂点とエッジの例。

    hello というラベルが付いた頂点は、followedBy というラベルの付いた有向エッジによって world という頂点に接続しています。

  9. Gremlin クエリを発行して、hello というラベルの付いた頂点から followedBy というラベルの付いたエッジに続く頂点のラベルを調べます。

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

    クエリの構文については、次のセクションで説明します。クエリの出力として world という単語が表示されます。

    ==>world
    

サンプル データセットを読み込んでクエリを実行する

JanusGraph をデプロイし、Gremlin を使用して JanusGraph に接続できたので、独自のデータの読み込みとクエリを開始できます。このプロセスがどのようなものであるかを確認するため、JanusGraph にバンドルされているサンプル データセットを読み込みます。読み込むのは、ローマの神話の神々とそれらの神の場所のプロパティを表す Graph of the Gods です。

  1. Gremlin で、前に作成したグラフを読み込みます。

    GraphOfTheGodsFactory.load(graph)
    

    出力は次のとおりです。

    ==>null
    
  2. Jupiter のすべての兄弟を見つけるグラフ走査クエリを発行します。

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

    次の表に、クエリで走査する手順を示します。

    走査ステップ 説明
    g.V() 最初に点の集合を調べます。
    has('name', 'jupiter') プロパティ name の値が jupiter となっている点を探します。
    out('brother') そこから、brother というラベルを持つすべての辺に沿って移動します。
    values('name') この辺につながる点の name プロパティを取得します。
    出力は次のとおりです。

    ==>neptune
    ==>pluto
    

    この Graph of the Gods データセットに発行可能な走査クエリをよりよく理解できるよう、JanusGraph のドキュメントで他のサンプルクエリを試してみてください。

Bigtable にデータが保存されていることを確認する

JanusGraph クラスタにサンプルデータを作成できたので、Bigtable がストレージ バックエンドとして使用されたことを確認します。

  1. Gremlin コンソールを閉じます。

    :q
    
  2. Cloud Shell で、データが Bigtable の janusgraph テーブルに永続化されていることを確認します。

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

    出力は次のようになります。

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

    出力の値 101janusgraph table 内の行数を表しますが、実際はこれと異なる場合があります。

Elasticsearch で検索インデックスの作成を確認する

  1. Cloud Shell で、Elasticsearch Pod のインデックスと名前の変数を設定します。

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

    Elasticsearch Pod の名前は、Elasticsearch Helm の依存関係によって定義されます。Pod 名は、作成した values.yaml ファイルで指定されているクラスタ名、master という単語、ゼロ インデックスの序数で構成され、すべてハイフンで区切られています。このステップでは、ゼロ(0)で表される最初の Pod を選択します。

  2. Elasticsearch Aliases REST API を使用して、インデックスを検査します。

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

    出力には、JanusGraph で作成された、頂点とエッジのプロパティを使用した効率的なルックアップを可能にする 2 つのインデックス janusgraph_verticesjanusgraph_edges が表示されます。

    {
      "janusgraph_vertices" : {
        "aliases" : {
          "janusgraph" : { }
        }
      },
      "janusgraph_edges" : {
        "aliases" : {
          "janusgraph" : { }
        }
      }
    }
    
  3. いずれかのインデックスの値のクエリを実行するには、Elasticsearch Search REST API を使用します。

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

    検索結果は、JanusGraph によって作成されたインデックスにエントリがあることを示しています。次のような出力が表示されます。これは、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"
           }
         },
         {
    …
    

プロジェクトを削除する

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ