#69 Neo4j
MySQLなどのリレーショナルデータベースは、堅牢でとても使いやすいですが、システムによっては適切でない場合もあります。例えば、扱うデータ量が莫大だと、MySQLのパフォーマンスは落ちてしまいます。データ構造が定まっていなかったり、複雑なリレーションを扱う必要がある場合も対応できません。
そんなときに使われるのがNoSQLです。ドキュメント型や、キーバリュー型など、要件に合わせて様々な選択ができます。Neo4jは、グラフデータベースのオープンソースプロジェクトで、ネットワーク状の複雑なデータ関係を表現するのに適しています。
それでは、早速試してみましょう!
準備
Dockerで環境構築し、Pythonから操作したいと思います。詳しいインストール方法などは、下記を参考にしてください。
環境構築
Docker ComposeでDBストレージとサーバーを立てます。
docker-compose.yml
networks:
lan:
services:
storage:
hostname: storage
image: neo4j:4.4.8-enterprise
networks:
- lan
ports:
- "9000:9000"
- "9001:9001"
environment:
NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"
NEO4J_AUTH: neo4j/passw0rd
NEO4J_dbms_default__advertised__address: storage
NEO4J_dbms_connector_http_listen__address: storage:9000
NEO4J_dbms_connector_bolt_listen__address: storage:9001
healthcheck:
test: [ "CMD-SHELL", "echo RETURN 1 | cypher-shell -a bolt://storage:9001 -u neo4j -p passw0rd || exit 1" ]
server:
hostname: server
image: neo4j/neo4j-ops-manager-server:latest
depends_on:
storage:
condition: service_healthy
networks:
- lan
ports:
- "8080:8080"
- "9090:9090"
environment:
SPRING_NEO4J_URI: bolt://storage:9001
SPRING_NEO4J_AUTHENTICATION_USERNAME: neo4j
SPRING_NEO4J_AUTHENTICATION_PASSWORD: passw0rd
SERVER_SSL_KEY_STORE_TYPE: PKCS12
SERVER_SSL_KEY_STORE: file:/certificates/localhost.pfx
SERVER_SSL_KEY_STORE_PASSWORD: changeit
GRPC_SERVER_SECURITY_KEY_STORE_TYPE: PKCS12
GRPC_SERVER_SECURITY_KEY_STORE: file:/certificates/localhost.pfx
GRPC_SERVER_SECURITY_KEY_STORE_PASSWORD: changeit
CORS_ALLOWEDHEADERS: "*"
CORS_ALLOWEDORIGINS: "http://localhost:[*],https://localhost:[*]"
JWT_SECRET: please-set-a-random-secret-string-here-for-jwt-signing
volumes:
- type: bind
source: .nom/ssc
target: /certificates
entrypoint:
- "sh"
- "-c"
- "java -jar app.jar ssc -n localhost -o /certificates -p changeit -d localhost.localdomain -i 127.0.0.1 && java -jar app.jar"
起動する前に、コンテナで使用されるディレクトリを先に用意する必要があるので注意です。
以下コマンドで起動できます。
$ mkdir -p .nom/ssc
$ docker compose up -d --build
Pythonもvenvで仮想環境を用意します。
$ python3 -m venv .venv
$ source .venv/bin/activate
仮想環境内で、Neo4jのライブラリをインストールします。
$ pip3 install neo4j-driver
接続
DBとの接続を管理するクラスを用意しました。
connection.py
from neo4j import GraphDatabase
class Connection:
def __init__(self, uri, user, password):
self.uri = uri
self.user = user
self.password = password
self.driver = None
def connect(self):
try:
self.driver = GraphDatabase.driver(self.uri, auth=(self.user, self.password))
except Exception as e:
print("Failed to connect: ", e)
def close(self):
if self.driver is not None:
self.driver.close()
def execute(self, query):
try:
session = self.driver.session(database="neo4j")
response = list(session.run(query))
except Exception as e:
print("Failed:", e)
finally:
if session is not None:
session.close()
return response
とりあえず、データの投入と取得を試してみます。
main.py
from connection import Connection
def main():
uri = "bolt://localhost:9001"
user = "neo4j"
password = "passw0rd"
conn = Connection(uri, user, password)
conn.connect()
query1 = "CREATE (test: Test {name: 'test1'})"
result = conn.execute(query1)
print(result)
query2 = "MATCH (t:Test) RETURN t"
result = conn.execute(query2)
print(result)
if __name__ == "__main__":
main()
$ python3 main.py
[]
[<Record t=<Node element_id='0' labels=frozenset({'Test'}) properties={'name': 'test1'}>>]
しっかり取れているみたいです。
Cypher
Neo4jでは、慣れ親しんだSQLではなく、Cypherという言語でクエリを組み立てます。文法が少し違うだけで、それほど難しいものでもありません。
CREATE
ノードやリレーションをDBに挿入します。
// ノード
CREATE (t: Test {name: 'test1'})
// リレーション
CREATE (t1: Test {name: 'test1')-[:IS_PARENT]->(t2: Test {name: 'test2'})
MATCH
条件に合ったものを取得します。
MATCH (t: Test {name: 'test1'}) RETURN t
SET
値の更新を行います。
MATCH (t: Test {name: 'test1'})
SET t.name = 'test2'
DELETE
レコードを削除します。リレーションがある場合は、先にリレーションを削除しなければいけません。
MATCH (t:Test {name: 'test1'})
DELETE t
// リレーションもまとめて削除
MATCH (t:Test {name: 'test1'})
DETACH DELETE t
まとめ
グラフデータベースで扱っている概念は、リレーショナルデータベースのものとは違うため、慣れるまでは少し手間取りそうですが、グラフでしか扱えない問題もたくさんありそうです。
気になるのはやはりセキュリティ面ですが、アプリの設計によっては、SQLインジェクションならぬCypherインジェクションのような脆弱性も当然生まれてしまうでしょう。深く理解して扱っていきたいです。
EOF