Neo4jとCypherに入門して使いどころを考えてみた


前提

  • 作業マシンにインストールして使ってみる
  • 作業マシンはOS X El Capitan(バージョン10.11.4)

install

brewで簡単に入る

brew install neo4j

Javaが必要なので入ってなければ入れる

brew cask install java

起動

➜  ~ neo4j start
Unable to find any JVMs matching version "1.7".
Starting Neo4j Server...WARNING: not changing user
process [57371]... waiting for server to be ready............... OK.
http://localhost:7474/ is ready.

http://localhost:7474/ブラウザで管理画面みたいなのにアクセスできる
最初にID,PW入力を求められるが最初はneo4j,neo4jでログインできる

neo4jのディレクトリ構成

/usr/local/Cellar/neo4j/2.3.2

├── bin   ・・・起動系スクリプト
│   ├── neo4j -> ../libexec/bin/neo4j
│   ├── neo4j-import -> ../libexec/bin/neo4j-import
│   └── neo4j-shell -> ../libexec/bin/neo4j-shell
└── libexec
    ├── bin ・・・起動系スクリプト
    │   ├── neo4j
    │   ├── neo4j-import
    │   ├── neo4j-installer
    │   ├── neo4j-shell
    │   └── utils
    ├── conf ・・・設定系ディレクトリ
    │   ├── neo4j-http-logging.xml
    │   ├── neo4j-server.properties
    │   ├── neo4j-wrapper.conf
    │   ├── neo4j.properties
    │   └── ssl
    │       ├── snakeoil.cert
    │       └── snakeoil.key
    ├── data ・・・実データやlogが出力されるディレクトリ
    │   ├── dbms
    │   │   └── auth
    │   ├── graph.db
    │   │   └── store_lock
    │   ├── log
    │   │   └── console.log
    │   └── neo4j-service.pid
    ├── lib ・・・coreライブラリ群
    ├── plugins ・・・プラグイン
    │   └── README.txt
    └── system ・・・neo4jのコア

まぁ各ディレクトリにREADMEあるので読めば詳しくわかります

試しのテストデータの準備

  • 異なるジャンルのお店が2つあり、それぞれの商品を一般消費者が購入することをイメージ
  • データ構造は下記
├── category1
│   └── shop1
│       ├── item1
│       └── item2
└── category2
    └── shop2
        └── item3
  • category1という食品カテゴリにはshop1があり、商品item1(9500円),item2(1200円)がある
  • category1という雑貨カテゴリにはshop2があり、商品item3(15000円)がある
─── users
    ├── user1
    ├── user2
    ├── user3
    └── user4
  • 消費者が4人いる
  • user1item1を購入
  • user2item1を購入
  • user3item2を購入
  • user4item3を購入

上記のデータをCSVで用意

users.csv
user_id:ID,name,email,age:int,industry,gender,:LABEL
user1,user_name1,[email protected],37,サービス業,male,user
user2,user_name2,[email protected],17,ネットサービス,male,user
user3,user_name3,[email protected],27,公務員,female,user
user4,user_name4,[email protected],44,公務員,male,user
orders.csv
order_id:ID,user_id,shop_id,category_id,price,item_id,:LABEL
order1,user1,shop1,cate1,9500,item1,order
order2,user2,shop1,cate1,9500,item1,order
order3,user3,shop1,cate1,1200,item2,order
order4,user4,shop2,cate2,15000,item3,order
items.csv
tem_id:ID,name,shop_id,:LABEL
item1,商品A,shop1,item
item2,商品B,shop1,item
item3,商品C,shop2,item
shops.csv
shop_id:ID,name,category_id,:LABEL
shop1,ショップA,cate1,shop
shop2,ショップB,cate2,shop
category.csv
category_id:ID,name,:LABEL
cate1,カテゴリ1,category
cate2,カテゴリ2,category

データの投入

CSVからデータ投入は2種類ある

LOAD CSV

  • 既存のDBに対してノードや関係性の追加や削除、属性の追加、削除、更新をする
  • Javaのヒープメモリを気にする必要がある
LOAD CSV FROM 'file:///path/users.csv' AS line
CREATE (:user { email:line[1], gender:line[2], age:toInt(line[3]), industry:line[4] })

neo4j-importコマンド

  • 新規のDBに対してノードや関係性の追加や削除、属性の追加、削除、更新をする
  • Javaのヒープメモリを気にしなくて良い
  • 今回はこっちを利用
➜  neo4j neo4j-import --into /usr/local/Cellar/neo4j/2.3.2/libexec/data/graph2.db --nodes category.csv --nodes items.csv --nodes orders.csv --nodes shops.csv --nodes users.csv
Unable to find any JVMs matching version "1.7".
Importing the contents of these files into /usr/local/Cellar/neo4j/2.3.2/libexec/data/graph2.db:
Nodes:
  /Users/a12091/neo4j/category.csv

  /Users/a12091/neo4j/items.csv

  /Users/a12091/neo4j/orders.csv

  /Users/a12091/neo4j/shops.csv

  /Users/a12091/neo4j/users.csv

Available memory:
  Free machine memory: 17.46 MB
  Max heap memory : 1.78 GB

Nodes
[*>:??--------------------------------------------------------------------------------------|||] 10k
Done in 722ms
Prepare node index
[*DETECT:7.63 MB-------------------------------------------------------------------------------]   0
Done in 22ms
Calculate dense nodes
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 10ms
Relationships
[*>:??-----------------------------------------------------------------------------------------]   0
Done in
Node --> Relationship
[*v:??-----------------------------------------------------------------------------------------] 10k
Done in 12ms
Relationship --> Relationship
[*>:??-----------------------------------------------------------------------------------------]   0
Done in 11ms
Node counts
[>|*COUNT:76.29 MB-----------------------------------------------------------------------------] 10k
Done in 76ms
Relationship counts
[*>:??-----------------------------------------------------------------------------------------]   0
Done in

IMPORT DONE in 2s 354ms. Imported:
  15 nodes
  0 relationships
  67 properties

入った

続いてRelationships設定

  • RelationshipsようのCSVを用意すれば、そのまま投入できるが、ここでは基本データはCSVでCypherからRelationships設定という流れにする

ユーザと購入を紐付け

MATCH (o:order),(u:user) 
WHERE o.user_id = u.user_id 
CREATE (u)-[:BUY]->(o);

購入データと商品を紐付け

MATCH (o:order),(i:item) 
WHERE o.item_id = i.item_id 
CREATE (o)-[:ORDER]->(i);

お店と商品を紐付け

MATCH (s:shop),(i:item)
WHERE s.shop_id = i.shop_id
CREATE (i)-[:PART]->(s);

お店とお店のジャンルを紐付け

MATCH (c:category),(s:shop)
WHERE c.category_id = s.category_id
CREATE (s)-[:CATE]->(c);

管理画面から見てみる


* 色の設定とかはしてあげると綺麗に見える

注意

  • 管理画面からは参照先のDBの変更はできない様子
  • neo4j/2.3.2/libexec/conf/neo4j-server.propertiesの参照DBを変えてる必要あり
    • 変更後はneo4j restart

データを操作する - Cypher QL -

  • CypherはNeo4jのグラフ構造のデータ処理を扱う際に使うSQLライクなQuery Language

実行ツール

Neo4jウェブインターフェース

Neo4j標準でついている。WEBから実行可能(http://localhost:7474/)

Neo4jシェル

neo4j-shellで起動してコンソールからCypherを流して使う

API経由

REST API

Neo4jドライバー

主要な各言語からアクセスして操作できる

基本(取得)

すべてのノードを取得

MATCH (n) RETURN n

すべてのノードとrelashonship取得

MATCH (n -[r]-> m) RETURN n,r,m;

削除

すべてのノードを削除

MATCH n DELETE n;

すべてのノードとrelashonshipを削除

MATCH (n -[r]-> m) DELETE n,r,m;

削除裏技(data配下をrm)

/usr/local/Cellar/neo4j/2.3.2/libexec/data

ここからNeo4j(Graph DB)を活用しているメリット

Graph Algorithms

下記のコンポーネントが含まれてる

  • Shortest paths
  • all paths
  • all simple paths
  • Dijkstra and
  • A*

ひとつひとつをしっかり見ると深すぎるので、分かりやすいShortest pathsを例に扱って見る

Shortest paths = 最短経路

ex1

user_name1user_name3は今は友達ではないが、友達にさせるにはどんな共通項目があるかと調べる

コンソールから

neo4j-sh (?)$ MATCH p=shortestPath(
>   (a:user {name:"user_name1"})-[*]-(b:user {name:"user_name3"})
> )
> RETURN p;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| p                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [Node[11]{user_id:"user1",name:"user_name1",email:"[email protected]",age:37,industry:"サービス業",gender:"male"},:BUY[0]{},Node[5]{order_id:"order1",user_id:"user1",shop_id:"shop1",category_id:"cate1",price:"9500",item_id:"item1"},:ORDER[4]{},Node[2]{item_id:"item1",name:"商品A",shop_id:"shop1"},:PART[8]{},Node[9]{shop_id:"shop1",name:"ショップA",category_id:"cate1"},:PART[9]{},Node[3]{item_id:"item2",name:"商品B",shop_id:"shop1"},:ORDER[6]{},Node[7]{order_id:"order3",user_id:"user3",shop_id:"shop1",category_id:"cate1",price:"1200",item_id:"item2"},:BUY[2]{},Node[13]{user_id:"user3",name:"user_name3",email:"[email protected]",age:27,industry:"公務員",gender:"female"}] |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row
18 ms

分かりにくいので管理画面から

  • ショップAという店で商品を購入していることが共有項目となり、結び付けることができる

ex2

user_name1user_name2は今は友達ではないが、友達にさせるにはどんな共通項目があるかと調べる

管理画面から

  • 今度は商品Aという同じものを購入していることが共有項目となり、かつより近いところでつながり、結び助けることができる

RDBだったら面倒なSQLがCypherなら超楽

ex1

ショップAで男性購入者に人気の商品は(購入回数の多いのは)何か?を抽出しようとした時

mysqlの場合

  • RDBの設計によるが大まかこんな感じになる。tableをjoinしまくってselectする
  • またはsqlとプログラムを組み合わせて抽出する
select * 
from 
  購入Table 
left join 商品Table 
  on 購入Table.item_id = 商品Table.id
left join ユーザTable 
  on 購入Table.user_id = ユーザTable.id
left join ショップTable 
  on 購入Table.shop_id = ショップTable.id
where
 ユーザTable.gender = 'male'
AND
 ショップTable.name = 'ショップA'

Cypherの場合

MATCH (u:user {gender: "male"})-->(o:order)-->(i:item)-->(s:shop) 
WHERE s.name = "ショップA"
RETURN i.name 

まとめ、使いどころ、雑感

  • Graph Algorithmsを活用したSNS的なサービスの提供が可能(まぁみんなそう言ってる)
    • 〇〇の友達がいいねと言っています。みたいなやつ
    • 〇〇さんは友達ではないでしょうか?みたいなやつ
  • Graph Algorithmsを活用したサービスの分析(CV分析)が可能
    • 何かプロダクトがあり、様々なログをFluentあたりで収集して、Neo4jに突っ込む。
    • プロダクトの会員データもどこかのタイミングでNeo4jに突っ込む
    • あとはCypherをフル活用して分析してプロダクトへ反映する
  • RDBだったら複雑なSQLで計算量も時間もかかっていたのが、何分の1かで実現できるので良い感じのリコメンド機能とか割と簡単にできそう
    • どの商品にはどの属性のユーザが多く購入をする的なデータはすぐ計算・抽出する
    • 抽出したデータをもとにレコメンドようのデータKVSに突っ込む
    • ユーザの属性情報に基づいたレコメンドをKVSから取って表示してあげる
  • まぁレコメンドはまたいろんなアルゴリズムが存在するし、一筋縄ではいかないよなぁ。。。
  • Cypherをあまり詳しく記載しなかったが、http://neo4j.com/docs/stable/index.html を見ると面白そうなのがいっぱいある。Cypherの学習は面白そう
  • データ分析ならぬデータ遊びで色々な傾向のデータが出せそうなきがする
  • まぁ使いこなせればデータ分析系には強い武器になると思う
  • ある程度つかんできたら、プロダクションのデータを一部持ってきて触った方が、よりリアリティがあるし、楽しくできると思う