はじめに
gremlin-server コンテナを立ち上げる の続編です。
neo4j のファイル群を使って、gremlin サーバ上のグラフデータを永続化する方法について整理します。
TL; DR
- 勘違いしていた点
- BOLT 接続は、リモートNeo4jサーバ へプロキシすると思っていたが、そうではない
- Cypher を gremlin で使えるようにするらしい (試していないです・・)
- BOLT 接続は、リモートNeo4jサーバ へプロキシすると思っていたが、そうではない
- 設定ファイルの変更点
- gremlin-server.yml をベースに、gremlin-server-neo4j.yml を作成
- もちろん、サンプルの gremlin-server-neo4j.yaml も参考
- docker-compose.override.yml の entrypoint の設定ファイルパスを変更
- gremlin-server.yml をベースに、gremlin-server-neo4j.yml を作成
- 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 へのアクセス、追加、更新は概ね問題ない