2020/06/11

Pythonを使ってGremlin ServerとNeo4jを試してみる

pythongremlinneo4j

概要

トランザクションの履歴を簡単に便利に追う方法を調べました。
基本的にはRDBを使っていますが、それだと再起的な問い合わせから芋づる式にデータを抽出するのが困難でした。

送金を例にしてみます。

A -> B -> C -> D

このようなお金のやりとりがあった時

RDBだと

A -> B
B -> C
C -> D

と3レコードで保持され、A -> B -> C -> Dを一括でクエリでとるのに工夫が必要でした。(めんどくさいし、適していない)

何か良いやり方がないか調べてみたところ、graph databaseとそれを良い感じに表示してくれるclient(view側)が今回は適しているかなと思い
試しにGremlin Serverをいじってみることにしました。

実施事項

  • Gremlin Serverをdockerを使って起動しました。
    • 起動時にNeo4j使用するように設定しました。(Gremlin Serverとおんなじコンテナにneo4jのDBができる感じです)
    • Pythonからアクセスできるように設定しました。
  • Python Scriptを書いて、ちゃんとデータが登録・取得できるか試してみました。

Dockerの作成

以下のfileを作成しました。

FROM openjdk:8-alpine
MAINTAINER Hiroki Tanaka

ARG GREMLIN_VERSION=3.4.7

RUN apk add --update bash && rm -rf /var/cache/apk/* && \
    wget -O /tmp/gremlin-server.zip http://ftp.meisei-u.ac.jp/mirror/apache/dist/tinkerpop/${GREMLIN_VERSION}/apache-tinkerpop-gremlin-server-${GREMLIN_VERSION}-bin.zip && \
    unzip /tmp/gremlin-server.zip -d / && \
    mv /apache-tinkerpop-gremlin-server-${GREMLIN_VERSION}/ /gremlin-server/ && \
    chmod +x /gremlin-server/bin/gremlin-server.sh && \
    rm /tmp/gremlin-server.zip

WORKDIR /gremlin-server/

COPY ./grapeConfig.xml /root/.groovy/grapeConfig.xml
RUN /bin/bash -c "/gremlin-server/bin/gremlin-server.sh install org.apache.tinkerpop neo4j-gremlin ${GREMLIN_VERSION}"
RUN /bin/bash -c "/gremlin-server/bin/gremlin-server.sh install org.apache.tinkerpop gremlin-python ${GREMLIN_VERSION}"

COPY ./conf/gremlin-server-custom.yml ./conf/gremlin-server-custom.yml
COPY ./conf/neo4j.properties ./conf/neo4j.properties

CMD ["/bin/bash", "/gremlin-server/bin/gremlin-server.sh", "conf/gremlin-server-custom.yml"]
EXPOSE 8182

また、以下のような設定ファイルを作成しています。

  • conf/gremlin-server-custom.yml
host: 0.0.0.0
port: 8182
evaluationTimeout: 30000
channelizer: org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer
graphs: {
  graph: conf/neo4j.properties}
scriptEngines: {
  gremlin-groovy: {
    plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {},
               org.apache.tinkerpop.gremlin.neo4j.jsr223.Neo4jGremlinPlugin: {},
               org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]},
               org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}}
serializers:
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0] }}            # application/vnd.gremlin-v3.0+gryo
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GryoMessageSerializerV3d0, config: { serializeResultToString: true }}                                                                      # application/vnd.gremlin-v3.0+gryo-stringd
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0] }}        # application/json
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1 }                                                                                                           # application/vnd.graphbinary-v1.0
  - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }}                                                                 # application/vnd.graphbinary-v1.0-stringd
processors:
  - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
  - { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor, config: { cacheExpirationTime: 600000, cacheMaxSize: 1000 }}
metrics: {
  consoleReporter: {enabled: true, interval: 180000},
  csvReporter: {enabled: true, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv},
  jmxReporter: {enabled: true},
  slf4jReporter: {enabled: true, interval: 180000}}
strictTransactionManagement: false
idleConnectionTimeout: 0
keepAliveInterval: 0
maxInitialLineLength: 4096
maxHeaderSize: 8192
maxChunkSize: 8192
maxContentLength: 65536
maxAccumulationBufferComponents: 1024
resultIterationBatchSize: 64
writeBufferLowWaterMark: 32768
writeBufferHighWaterMark: 65536
ssl: {
  enabled: false}
  • conf/neo4j.properties
gremlin.graph=org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraph
gremlin.neo4j.directory=/data/databases/neo4j
gremlin.neo4j.conf.dbms.auto_index.nodes.enabled=true
gremlin.neo4j.conf.dbms.auto_index.relationships.enabled=true

これを docker-compose を使って起動します。

version: '3'
services:
  gremlin-server:
    build:
      context: ./gremlin-server-neo4j
      dockerfile: Dockerfile
    ports:
      - "8182:8182"
    volumes:
      - ./.data/:/data/

Python Scriptの作成

以下を作成しました。

  • user { name: “alice”, id: 1 } と { name: “bob”, id: 2 } を作成しました。
  • user間の「{ amount: 100 } を送金(transfer)」 というリレーションを作成しました。
from gremlin_python import statics
from gremlin_python.structure.graph import Graph
from gremlin_python.process.graph_traversal import __
from gremlin_python.process.strategies import *
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection


graph = Graph()
g = graph.traversal().withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g'))

g.addV('user').property('name', 'alice').property('id', 1).toSet()
g.addV('user').property('name', 'bob').property('id', 2).toSet()

alice = g.V().has('name', 'alice').toList()[0]
bob = g.V().has('name', 'bob').toList()[0]
g.addE('transfer').property('amount', '100').from_(alice).to(bob).toSet()

edgeList = g.E().valueMap().toList()
for edge in edgeList:
    print(edge)

感想

特定のデータの関係性を把握するのに、Graph Databaseは便利そうです。
とはいえ、基本的にRDBを使って管理しているので、実運用上はデータの2重管理になってしまうあたりがネックかなと思っています。
(良い方式とかあれば教えてください。)

以上です。