Graph Databaseって何？ Neo4jを実際に使って理解する事にしました。

# この記事について

現在、[AWS Certified Database Specialty](https://aws.amazon.com/jp/certification/certified-database-specialty/)の勉強中です。その中で、Amazon Neptuneの話が出てきました。Graph Databaseであるとの事です。`RDB`でも`Key-Value`でもないDatabaseの様で、どのような特性を持つDatabaseかを把握したいと思いました。その検証記録になります。

# 環境

この記事の内容は以下環境で実行しています。

WSL2
Ubuntu 22.04.1 LTS
Neo4j Community Edition 4.4.11

# 記事から勉強

https://aws.amazon.com/jp/nosql/graph/

まずは、発端となったAWSの記事を見てみます。

> リレーションシップの格納とナビゲートを目的として構築されたデータベースです。

との事。不正検出や推奨エンジンがユースケースとして紹介されており、データとデータの関係性を抽出するユースケースで威力を発揮する様です。これだけだと、RDBでも（その名の通りRelational Databaseですし）、出来そうな気がします。

http://jp.drinet.co.jp/blog/datamanagement/graphdbms_featured

こちらの記事にその答えがありました。友達との関係性を調べるユースケースにて、「友達の友達」を調べるケース（深さ２）ではRDBとの差はあまり感じないのに対して、「友達の友達の友達」を調べるケース（深さ３）では差が顕著になり、それ以上だとRDBでは使用に耐えない時間になっています。  
このように、関係性がネットワーク型になっているデータに対して、深さ３以上の関連性を調べる時、というのが適した使い方の様です。

http://jp.drinet.co.jp/blog/datamanagement/graphdbms_casestudy

また、先ほどの記事の続きの記事で、ユースケースを紹介しています。こちらによると

> 新しいデータの追加・修正がより柔軟にできる

という点も強みの様です。RDBですとどうしてもテーブル構造を決めておく必要があります。データの関連性が一様でない場合にもRDBでは対応が難しそうです。


# 実際に使ってみる

表題の通り、実際に使って理解する事にします。

## データを考える

使う為には、Databaseに投入するデータが必要です。自分で作るのは大変なので、既存のオープンデータを使いたい所です。

https://moneyforward.com/engineers_blog/2016/01/13/neo4j/

こちらの記事が色々参考になります。サンプルデータとして、映画と俳優の関係性のデータが準備されている様です。

## Neo4jサーバーを準備する

Amazon Neptuneを使うとお金がかかってしまうので、Neo4jをローカルにインストールして試す事にします。基本先ほどの記事を参考にしながら、公式ページをトレースする事にします。

https://neo4j.com/

ページ中央程にある`Start Free For Developers`を選択すると、`Neo4j AuraDB`というクラウドサービスを使う事になりそうです。ローカルで起動したくてページ内をさまよっていたら、公式dockerイメージがある様です。

https://hub.docker.com/_/neo4j/

WSL2で、Ubuntu 22.04.1 LTSのイメージを起動して、以下のコマンドを実行します。

```bash
# データ保持ディレクトリ作成
mkdir -p $HOME/neo4j/data
# 起動
docker run \
    --publish=7474:7474 --publish=7687:7687 \
    --volume=$HOME/neo4j/data:/data \
    neo4j
```

Windows側で、`http://localhost:7474`へアクセスすると、ログイン画面が表示されました。もうサーバー準備出来た事になります。docker最高です。初期ID/パスワード「neo4j/neo4j」でログイン後、新しいパスワードが求められるので、任意のパスワードを決めるか自動生成して入力します。その後、初期画面が表示されました。

後程実行するチュートリアルの実行した後の画面です。こんな感じでGraph形式で実行結果が表示される（内容によってはTable形式）UIとなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/5f413c22-adf3-7463-2fdd-6752430ad1a7.png)


## チュートリアルを実行する

> :play start

と入力部に表示されているエリアがあります。それぞれ、目的別のチュートリアルの様です。

- Getting started with Neo4j Browser  
  今表示されている画面コンソールの使い方を紹介しています
- Try Neo4j with live data  
  俳優と監督と映画の関係のデータを使用して、一通りの操作をする様です
- Cypher basics
  CypherというNeo4jの操作言語の説明です。

### Getting started with Neo4j Browser  

画面の使い方をレクチャーしています。最後に`Concepts Guide`へ移動するリンクがあります。

### Concepts Guide

Graph Databaseの基本思想を記載しています。自分なりに理解した内容を記載します。

Nodes - 各分類の実体のデータ
Labels - Nodesの分類を表します（例：Person、Car）
Relationships - ノード間の関係の定義（例：出演してる、知っている、友達）
Properties - NodesとRelationshipsに対して、名前と値の情報を付与できます（例：人の名前、映画のタイトル）

### Cypher Guide

`Cypher basics`のチュートリアル通りにコマンドを打ってみます。

```cypher:ノード作成
CREATE (ee:Person {name: 'Emil', from: 'Sweden', kloutScore: 99})
```

```cypher:ノード検索
MATCH (ee:Person) WHERE ee.name = 'Emil' RETURN ee;
```

```cypher:複数コマンド実行
MATCH (ee:Person) WHERE ee.name = 'Emil'
CREATE (js:Person { name: 'Johan', from: 'Sweden', learn: 'surfing' }),
(ir:Person { name: 'Ian', from: 'England', title: 'author' }),
(rvb:Person { name: 'Rik', from: 'Belgium', pet: 'Orval' }),
(ally:Person { name: 'Allison', from: 'California', hobby: 'surfing' }),
(ee)-[:KNOWS {since: 2001}]->(js),(ee)-[:KNOWS {rating: 5}]->(ir),
(js)-[:KNOWS]->(ir),(js)-[:KNOWS]->(rvb),
(ir)-[:KNOWS]->(js),(ir)-[:KNOWS]->(ally),
(rvb)-[:KNOWS]->(ally)
```

```cypher:絞り込み取得
MATCH (ee:Person)-[:KNOWS]-(friends)
WHERE ee.name = 'Emil' RETURN ee, friends
```

### The Movie Graph

基本操作で、ある程度解った気になります。次は`Try Neo4j with live data`のチュートリアル通りにコマンドを打ってみます。これが映画と俳優の関係のデータを使った操作になります。

```cypher:まずクリーンアップ
MATCH (n) DETACH DELETE n
```

```cypher:データ投入（※Playボタンを押すと、コマンドが転記されるようです。）
CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
// 以後省略
```

```cypher:制約作成
CREATE CONSTRAINT ON (n:Movie) ASSERT (n.title) IS UNIQUE
CREATE CONSTRAINT ON (n:Person) ASSERT (n.name) IS UNIQUE
```

```cypher:インデックス作成
CREATE INDEX FOR (m:Movie) ON (m.released)
```

```cypher:クエリ実行
// nameが"Tom Hanks"のPersonノードを取得:
MATCH (tom:Person {name: "Tom Hanks"}) RETURN tom
// titleが"Cloud Atlas"のMovieノードを取得
MATCH (cloudAtlas:Movie {title: "Cloud Atlas"}) RETURN cloudAtlas
// Personノードを10件取得
MATCH (people:Person) RETURN people.name LIMIT 10
// 1990年から2000年より前にリリースされた映画を取得
MATCH (nineties:Movie) WHERE nineties.released >= 1990 AND nineties.released < 2000 RETURN nineties.title
```

```cypher:クエリ実行
// Kevin Baconから４ステップ以内にいるノードを取得
MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..4]-(hollywood)
RETURN DISTINCT hollywood
```

```cypher:クエリ実行
// 組み込み関数shortestPathを使用して、Kevin BaconとMeg Ryanの最短ルートを取得
MATCH p=shortestPath(
(bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"})
) RETURN p
```

こんな結果が出ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/2037b1c0-9477-8540-c7f2-738d9ccd5598.png)

```cypher:クエリ実行
// Tom Hanksの共演者の共演者でTom Hanksと共演した事の無いPersonを取得してルートの件数で逆ソート
MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
    (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors)
  WHERE NOT (tom)-[:ACTED_IN]->()<-[:ACTED_IN]-(cocoActors) AND tom <> cocoActors
  RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC
```

こんな結果が出ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/cd5723c7-56c3-d07c-f449-3bc944db6cc5.png)

```cypher:クエリ実行
// Tom HanksにTom Cruiseを紹介できる（共演者の共演者）Personを取得
MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
  (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cruise:Person {name:"Tom Cruise"})
RETURN tom, m, coActors, m2, cruise
```

こんな結果が出ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/8d0fc98e-658c-24fd-2277-c28516168caa.png)

```cypher:クリーンアップ＆確認
MATCH (n) DETACH DELETE n
MATCH (n) RETURN n
```

## チュートリアルの感想

チュートリアルを一通りこなしてみたました。RDBでは複雑なSQLを組むか、それなりのボリュームのプログラムを作ったりしないと出来ない事を一つの問い合わせ文で実行できる事が判りました。

https://neo4j.com/docs/cypher-manual/4.4/

チュートリアルで使ったのは一例で、操作言語であるcypherのマニュアルを見ると多彩な関数が用意されている事が解ります。冒頭でも述べた通り、関係性がネットワーク型になっているデータに対して、何かの条件下におけるデータを取得するケースで威力を発揮すると思います。

# プログラムから使用してみる

実際のシステムではコマンドラインでなく、プログラムから使用すると思います。ドライバを探してプログラムから使用してみます。

https://neo4j.com/download-center/#drivers

Java、Javascript、Python、.NET、Goのドライバは正式に準備されていて、かつ各言語のコミュニティによってドライバが提供されている様です。

https://github.com/neo4j/neo4j-python-driver

自分の環境ではPythonがお手軽に実行出来るので、Pythonを使います。

```bash
pip install neo4j
```

```python:githubサンプル
from neo4j import GraphDatabase

driver = GraphDatabase.driver("neo4j://localhost:7687",
                              auth=("neo4j", "password"))

def add_friend(tx, name, friend_name):
    tx.run("MERGE (a:Person {name: $name}) "
           "MERGE (a)-[:KNOWS]->(friend:Person {name: $friend_name})",
           name=name, friend_name=friend_name)

def print_friends(tx, name):
    query = ("MATCH (a:Person)-[:KNOWS]->(friend) WHERE a.name = $name "
             "RETURN friend.name ORDER BY friend.name")
    for record in tx.run(query, name=name):
        print(record["friend.name"])

with driver.session() as session:
    session.execute_write(add_friend, "Arthur", "Guinevere")
    session.execute_write(add_friend, "Arthur", "Lancelot")
    session.execute_write(add_friend, "Arthur", "Merlin")
    session.execute_read(print_friends, "Arthur")

driver.close()
```

Neo4jのセッションをまず取得し、そこから書き込み、読み込みそれぞれのメソッドが用意されている様です。  
コールバック関数（？）とそれに使用するパラメーターを前述２種のメソッドに渡す様です。また、cypherコマンドは、そのままコマンド文を実行出来る形の様です。RDBのドライバでSQL文がそのまま実行できるのに似ています。

```console:実行した結果
$ python neo4j_test.py
Guinevere
Lancelot
Merlin
```

Neo4jのコンソールで確認した所、４つのノードがちゃんと登録されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/4e100406-df8a-45ec-8c1c-22486a6a0cc0.png)

# まとめ

今回の目的である、Graph Databaseとはどのようなものか理解できたかと思います。もちろん実際に何らかのアプリで使用されている状況ではもっと深い処理がされているかとは思いますが、少なくとも基本特徴は把握できたと思います。チュートリアル優秀ですね。

今後身の回りで、何らかの関係性を深く調べたいようなケースが出てきたら、Neo4jサーバー起動してデータ投入して調べてみたくなると思います。Neo4jのUIでのGraph表示では、ノードの位置など自由に動かせてその操作自体も楽しかったですｗ