2020/06/11
Pythonを使ってGremlin ServerとNeo4jを試してみる
概要
トランザクションの履歴を簡単に便利に追う方法を調べました。
基本的には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重管理になってしまうあたりがネックかなと思っています。
(良い方式とかあれば教えてください。)
以上です。
関連する記事
[Python]ハイフンなし電話番号からハイフン付きに復元
Pythonでハイフンなしの日本の電話番号をハイフン付きのものに変換する
[Python]BeautifulSoup4でhtmlの解析
BeautifulSoup4というPythonのライブラリを使って、特定のURLのコンテンツを取得し、タイトルや説明文を取得できるようにしました。
[Python]銀行コードと支店コードの取扱
Pythonで銀行コード、支店コードデータを取り扱う便利なライブラリzengin-codeを導入しました。
Sendgridを使ってメールの受信を行う
Inbound Email Parse Webhookという機能を利用してメールを受信したらWebhookを呼び出すようにしました