1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

gremlin-server コンテナを立ち上げる② ー neo4j embeded

Posted at

はじめに

gremlin-server コンテナを立ち上げる の続編です。
neo4j のファイル群を使って、gremlin サーバ上のグラフデータを永続化する方法について整理します。

TL; DR

  • 勘違いしていた点
    • BOLT 接続は、リモートNeo4jサーバ へプロキシすると思っていたが、そうではない
      • Cypher を gremlin で使えるようにするらしい (試していないです・・)
  • 設定ファイルの変更点
    • gremlin-server.yml をベースに、gremlin-server-neo4j.yml を作成
      • もちろん、サンプルの gremlin-server-neo4j.yaml も参考
    • docker-compose.override.yml の entrypoint の設定ファイルパスを変更
  • Python 経由で使っても、問題なく動作する
  • コード一式(設定ファイル変更前)は、こちら

環境

対象 バージョン
OS macOS Big Sur (11.5.2)
docker Docker version 20.10.8, build 3967b7d
docker-compose docker-compose version 1.29.2, build 5becea4c
python Python 3.8.10

やったこと

設定ファイルを変更し、docker-compose.override.yml で設定ファイルへのパスを変更しました。

設定ファイルの変更

gremlin-server.yml との差分

~/p/gremlin ❯❯❯ diff conf/gremlin/server/gremlin-server{,-neo4j}.yml                                               ✘ 1 
17a18,27
> # Note that TinkerPop does not include Neo4j dependencies in its
> # distributions. This file cannot be used as a configuration file to 
> # Gremlin Server unless Neo4j dependencies are installed on the 
> # Gremlin Server path with:
> #
> # bin/gremlin-server.sh -i org.apache.tinkerpop neo4j-gremlin x.y.z
> # 
> # Note that unless under a commercial agreement with Neo4j, Inc., 
> # Neo4j is licensed AGPL. 
> 
23c33
<   graph: conf/tinkergraph-empty.properties}
---
>   graph: conf/neo4j.properties}
27c37
<                org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {},
---
>                org.apache.tinkerpop.gremlin.neo4j.jsr223.Neo4jGremlinPlugin: {},
~/p/gremlin ❯❯❯                                                                                                    

.properties ファイルの指定を変更し、Neo4jGremlinPlugin のところを変えました。(前半の差分は、コメントなので略)

docker-compose.override.yml の変更

以下に、サービス gremlin-server の部分だけを抜粋します。

  gremlin-server:
    build:
      context: ./
      dockerfile: docker/gremlin/server/Dockerfile
    image: $project.gremlin-server
    container_name: $project.gremlin-server
    ports:
      - "8182:8182"
    volumes:
      - ./data/:/data/
      - ./conf/gremlin/server/gremlin-server.yml:/opt/gremlin-server/conf/gremlin-server.yml
      - ./conf/gremlin/server/gremlin-server-neo4j.yml:/opt/gremlin-server/conf/gremlin-server-neo4j.yml
      - ./conf/gremlin/server/neo4j.properties:/opt/gremlin-server/conf/neo4j.properties
    entrypoint: >
      /bin/bash bin/gremlin-server.sh conf/gremlin-server-neo4j.yml
      # /bin/bash bin/gremlin-server.sh conf/gremlin-server.yml

永続化確認

github リポジトリ をクローンし、上記の設定変更が終わっているとします。

サービス群の起動

make

サービス群の起動確認

起動できていることを確認します。以下のように gremlin.gremlin-server が起動していればOKです。
※ gremlin.app は、python の VSCode用開発環境用のコンテナ(Ubuntu 20.04)です

~/p/gremlin ❯❯❯ make ps
docker-compose ps
         Name                       Command               State                    Ports                  
----------------------------------------------------------------------------------------------------------
gremlin.app              /bin/bash                        Up                                              
gremlin.gremlin-server   /bin/bash bin/gremlin-serv ...   Up      0.0.0.0:8182->8182/tcp,:::8182->8182/tcp
~/p/gremlin ❯❯❯ 

データがないことを確認

~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/ref.gremlin
(省略)
gremlin> g.V().values('name')
gremlin> g.V().count()
==>0
gremlin> :exit
~/p/gremlin ❯❯❯                                                        

データ投入

~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/test.gremlin
(省略)
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.24.0.2:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.24.0.2:8182] - type ':remote console' to return to local mode
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.24.0.2:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.24.0.2:8182] - type ':remote console' to return to local mode
gremlin> g.addV('person').property('name','hello')
==>v[38]
gremlin> g.addV('person').property('name','world')
==>v[39]
gremlin> g.V().values('name')
==>hello
==>world
gremlin> g.V().count()
==>2
gremlin> :exit
~/p/gremlin ❯❯❯ 

2 ノードが追加されていることがわかります。

再起動(停止&起動)

永続化されていることを確認するため、一度 再起動(停止&起動)をしておきます。

~/p/gremlin ❯❯❯ make reup
docker-compose down
Stopping gremlin.gremlin-server ... done
Stopping gremlin.app            ... done
Removing gremlin.gremlin-server ... done
Removing gremlin.app            ... done
Removing network gremlin_default
docker-compose up -d app gremlin-server
Creating network "gremlin_default" with the default driver
Creating gremlin.app            ... done
Creating gremlin.gremlin-server ... done
~/p/gremlin ❯❯❯ 

再起動後の確認(永続化確認)

~/p/gremlin ❯❯❯ make gremlin-console < src/gremlin/ref.gremlin
(省略)
gremlin> :remote connect tinkerpop.server conf/remote.yml
==>Configured gremlin-server/172.25.0.3:8182
gremlin> :remote console
==>All scripts will now be sent to Gremlin Server - [gremlin-server/172.25.0.3:8182] - type ':remote console' to return to local mode
gremlin> g.V().values('name')
==>hello
==>world
gremlin> g.V().count()
==>2
gremlin> :exit
~/p/gremlin ❯❯❯ 

gremlin-server の 再起動(起動&停止)後でも、再起動前のデータが入っていることが確認できました。
つまり、永続化できていることを意味します。

python で動作確認

めでたく永続化できたので、Python から使っても、特段異常が起きないことを確認します。

まず、import と Gremlin 操作を 隠蔽するクラス(GremlinProvider)を作っておきます。

from gremlin_python.process.anonymous_traversal import traversal
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection


class GremlinProvider(object):
    def __init__(self):
        self.g = traversal().withRemote(
            DriverRemoteConnection("ws://gremlin-server:8182/gremlin", "g")
        )

    def clear_nodes(self):
        self.g.V().drop().iterate()
        return self

    def add_person(self, name: str):
        node = self.g.addV("person").property("name", name).next()
        return node

    def add_age(self, name: str, age: int):
        assert name and 0 <= age <= 150
        node = self.g.V().has("person", "name", name).property("age", age).next()
        return node

    def add_job(self, name: str, job: str):
        assert name and job
        node = self.g.V().has("person", "name", name).property("job", job).next()
        return node

    def get_name(self, v):
        name = self.g.V(v).values("name").toList()[0]
        return name

    def get_age(self, v):
        age = self.g.V(v).values("age").toList()[0]
        return age

    def add_knows(self, v1, v2, weight=0.75):
        edge = self.g.V(v1).addE("knows").to(v2).property("weight", weight).iterate()
        return edge

    def find_person_node(self, name: str):
        node = self.g.V().has("person", "name", name).next()
        return node

    def find_person_whom_knows(self, center: str):
        whom_knows = self.g.V().has("person", "name", center).out("knows").toList()[0]
        return whom_knows

次が、実験コードです。

if __name__ == "__main__":
    grp = GremlinProvider()
    grp.clear_nodes()

    # marko を登録
    v1 = grp.add_person("marko")
    assert grp.get_name(v1) == "marko"
    print("v1:", grp.get_name(v1))
    # v1: marko

    # stephen を登録
    v2 = grp.add_person("stephen")
    assert grp.get_name(v2) == "stephen"
    print("v2:", grp.get_name(v2))
    # v2: stephen

    # 年齢を追加・更新
    grp.add_age("marko", 35)  # insert
    grp.add_age("marko", 31)  # update
    grp.add_age("stephen", 32)

    # 職業を追加・更新
    grp.add_job("marko", "SWE")
    grp.add_job("stephen", "SWE")

    # リレーション(knows) を追加
    e1 = grp.add_knows(v1, v2, 0.1)
    print(e1)
    # [['V', v[34]], ['addE', 'knows'], ['to', v[35]], ['property', 'weight', 0.1], ['none']]

    # marko を検索
    marko = grp.find_person_node("marko")
    print("marko:", grp.get_name(marko))
    # marko: marko

    # marko が知っている人を検索
    v = grp.find_person_whom_knows("marko")
    print("marko knows:", grp.get_name(v))
    print("marko knows2:", grp.g.V(v1).outE().inV().values("name").toList()[0])
    # marko knows: stephen
    # marko knows2: stephen

    # ノードオブジェクトから年齢を取得
    print("marko.age:", grp.get_age(v1))
    assert grp.get_age(v1) == 31
    print("stephen.age:", grp.get_age(v2))
    assert grp.get_age(v2) == 32
    # marko.age: 31
    # stephen.age: 32

    # 職業が ”SWE" である人物の名前を取得(リスト)
    print("SWE:", grp.g.V().has("person", "job", "SWE").values("name").toList())
    # SWE: ['marko', 'stephen']

上記の2ブロックのコードを、1つのファイル(src/trial2.py)にして実行します。
(make bash を実行後、python -m src.trial2 を実行)

~/p/gremlin ❯❯❯ make bash
docker-compose up -d app gremlin-server
gremlin.app is up-to-date
gremlin.gremlin-server is up-to-date
docker-compose exec app bash
dsuser@7d907178380d:~/workspace$ python -m src.trial2
v1: marko
v2: stephen
[['V', v[36]], ['addE', 'knows'], ['to', v[37]], ['property', 'weight', 0.1], ['none']]
marko: marko
marko knows: stephen
marko knows2: stephen
marko.age: 31
stephen.age: 32
SWE: ['marko', 'stephen']
dsuser@7d907178380d:~/workspace$ 

以上により、ざっくりと、以下のことが確認できたことになります。

  • node オブジェクトの生成
  • 与えられた node オブジェクトの name プロパティを取得
  • 与えられた 2つの node オブジェクトから edge オブジェクトの生成
  • name から node オブジェクトを検索(取得)
  • 与えられた node オブジェクトから、edge を辿って、エッジの先の node オブジェクトを取得
  • 既に登録した node に対して、プロパティを追加・更新(age, job)
  • ノードオブジェクトから、各種プロパティ(age, job)を取得
  • 特定のプロパティ値を持つ node の name リストを取得

まとめ

  • 永続化のために、Neo4j ファイルを使うようにするには、設定ファイルを少し変更するだけで対応できる
    • BOLT 接続は、リモートの Neo4j に接続するためのものではない(らしい/未確認)点に注意
  • ある程度、Graph で操作したいことを、Python から実行できることを確認( See src/trial2.py
    • node, property へのアクセス、追加、更新は概ね問題ない

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?