Neo4jRBでグラフDBをRailsから使う
Neo4j単体でも面白いのだけど、結果を現実世界に出すにはWEB画面を作るのが楽。というわけでNeo4jRBに触れてみる。とりあえず環境構築のみ。
環境構築
- とりあえずDebianで環境構築をやってみる
neo4jの準備
各環境にあったパッケージリポジトリを使用しましょう。
Neo4j Debian Packages
書いてあるとおりやればサービスとしてneo4jが入ります
// リポジトリの追加 $ wget -O - https://debian.neo4j.org/neotechnology.gpg.key | sudo apt-key add - $ echo 'deb https://debian.neo4j.org/repo stable/' | sudo tee /etc/apt/sources.list.d/neo4j.list $ sudo apt-get update // インストール $ sudo apt-get install neo4j
一回DBを起動させておいてパスワード変更しといてください
$ sudo systemctl start neo4j
この後 http://localhost:7474 へ行って、id: neo4j, pass:neo4jでログイン、初回パスをneo4jから違うものに変更しておく。
いざモックアップ作成
とりあえず公式ドキュメントの通りコマンドを叩く、
Setup — Neo4j.rb 7.1.4 documentation
だいたい以下のような感じで進めた。
- 初回はrbenvに入ったrailsコマンドを使いたかったので以下のようになっている
$ rbenv exec bundle install rails $ rbenv exec gem rails new myapp -m http://neo4jrb.io/neo4j/neo4j.rb -O $ cd myapp $ bundle install
Railsは基本的なCRUD処理はscaffoldで作ってくれる。以下は例。
$ rails generate scaffold User name:string email:string $ rails s
とりあえずこれでNeo4Jのノードとちょっとしたアトリビュートを画面から追加できる。
リレーションなどは別APIを作ってみるか?
Neo4jでJOINクエリ
Neo4jでJOINクエリ
課題・やりたいこと
課題
やってること
レポーティング, OLAP
- 関係するテーブルのデータの有無で条件分岐を作成する → 開発当初から条件分岐が多すぎてテストが辛い
- 多少はSQLに対する慣れにより解消したが、しばらくソースを見ないと忘れるし追加があると追加自体が難しい
- これはすでにSQLアンチパターンのスパゲッティクエリの状態である
なぜ、SQLでJOINしまくる羽目になるのか?
- 正規化しているから、は1つの理由だが
- そもそも論
- つまり、関係データベースは名前に反して関係性を管理するのが苦手だ
from グラフデータベース:Neo4j、そしてRDBからの移行手順の紹介 | PPT
やりたいこと
クエリを減らしてメンテナンス性をよくする、こんな風に
from How to do Joins in Apache Cassandra and DSE | DataStax
実践
まずはSQLでいうところのJOINクエリをやっていく、やること
- データ設計の見直し
- Neo4jはグラフDB操作用にCypher (Cypher (query language) - Wikipedia)というクエリ言語をもっている、基本はそいつを使ってクエリを実行する ** あとはSQLとの橋渡しをこの辺を見ながらやってみる
概要と手順
データ設計の見直し
だいたいは、ここの通りに実行 グラフデータベース:Neo4j、そしてRDBからの移行手順の紹介 | PPT
手順
- ノード間の関係を定義する(例)従業員 =(販売)=> 受注
- 外部キーを見つける
- (外部キー)-[:変化]->(関係)
- 外部キーを取り除く
- ジョインテーブルを見つける
- ジョインテーブルがそのまま関係データになる
- 属性付きのジョインテーブルは、プロパティ付きの関係データに変換
- グラフ化完了
使ったクエリ
プラットフォームの選択
Windowsの場合WSLを使用してDebianでインストールしたほうがいい気がする Neo4j Debian Packages
- neo4j-shell
- /etc/neo4j/neo4j.conf のコメントを外しておく
# Enable a remote shell server which Neo4j Shell clients can log in to. dbms.shell.enabled=true # The network interface IP the shell will listen on (use 0.0.0.0 for all interfaces). dbms.shell.host=127.0.0.1 # The port the shell will listen on, default is 1337. dbms.shell.port=1337
インストールした後はserviceコマンドで起動できる。
$ sudo service neo4j start
Neo4jのコンフィグまわり
- デフォルトではCSVファイルインポートができないので、Settingsから以下の設定を有効化
これをやらないとクエリを投げてもLOADできない。
# Determines if Cypher will allow using file URLs when loading data using # `LOAD CSV`. Setting this value to `false` will cause Neo4j to fail `LOAD CSV` # clauses that load data from the file system. dbms.security.allow_csv_import_from_file_urls=true
WSLの環境へヘッダありCSVを読み込む例
まず一度WSLのLinux環境へファイルを送る
$ sudo cp /mnt/c/tmp/xxx.csv /var/lib/neo4j/import/
その後LOADクエリを実行する
> LOAD CSV WITH HEADERS FROM 'file:///xxx.csv' AS line CREATE (:X { head1: line.head1 });
なんか読み込みめっちゃ早い
実際にRDBのデータをGDBに移行
- テーブルのCSVデータを登録してインデックスを張る
LOAD CSV WITH HEADERS FROM 'file:///table-A.csv' AS line CREATE (:TableA { f_key: line.f_key }); CREATE INDEX ON :TableA(f_key); LOAD CSV WITH HEADERS FROM 'file:///table-B.csv' AS line CREATE (:TableB { f_key: line.f_key }); CREATE INDEX ON :TableB(f_key);
- 関係テーブルから関係性を取り出して設定する
LOAD CSV WITH HEADERS FROM 'file:///relation.csv' AS line WITH line.relation_from AS from_key , line.relation_to AS to_key MERGE (from:TableA { f_key: from_key }) MERGE (to:TableB { f_key: to_key }) MERGE (from)-[r:RELATE]->(to);
- さっそくJOINみたいなことをやる
-- マッチしたものだけ返す MATCH (a:TableA)-[r:RELATE]->(b:TableB) return a,b; -- マッチしなかったものも全件返す OPTIONAL MATCH (a:TableA)-[r:RELATE]->(b:TableB) return a,b;
はまりポイント・イケてる点など
はまりポイント
- CSVインポートが結構めんどくさい
- relationを作成するときはインデックスを作成しないと遅い(処理が終わらない)
イケてる点
- 意外に早い
- RDBで関係テーブルを作成する際は関係の意味を複数持たせにくいが、Neo4jならば自由に持たせられる
懸念
- 関係性がテーブルみたいにしっかりと残らないので、関係性の管理とかがちょいわかりにくい?
Neo4jでグラフアルゴリズム
前から調べようと思っていたNeo4jの勉強会に行ってきた。
jp-neo4j-usersgroup.connpass.com
グラフアルゴリズム
AOJ - 深さ優先探索
データセットの用意
- 深さ優先探索 | アルゴリズムとデータ構造 | Aizu Online Judge
- サンプルデータがあるので、それを投入してみる
入力例 2
6
1 2 2 3
2 2 3 4
3 1 5
4 1 6
5 1 6
6 0
- こんな感じか?
- たぶん書き方が冗長になっている気がする
CREATE (g:Graph { u:1, k:2 }); CREATE (g:Graph { u:2, k:2 }); CREATE (g:Graph { u:3, k:1 }); CREATE (g:Graph { u:4, k:1 }); CREATE (g:Graph { u:5, k:1 }); CREATE (g:Graph { u:6, k:0 }); MATCH (s:Graph) WHERE s.u = 1 MATCH (d:Graph) WHERE d.u = 2 CREATE (s)-[:DIRECTED]->(d); MATCH (s:Graph) WHERE s.u = 1 MATCH (d:Graph) WHERE d.u = 3 CREATE (s)-[:DIRECTED]->(d); MATCH (s:Graph) WHERE s.u = 2 MATCH (d:Graph) WHERE d.u = 3 CREATE (s)-[:DIRECTED]->(d); MATCH (s:Graph) WHERE s.u = 2 MATCH (d:Graph) WHERE d.u = 4 CREATE (s)-[:DIRECTED]->(d); MATCH (s:Graph) WHERE s.u = 3 MATCH (d:Graph) WHERE d.u = 5 CREATE (s)-[:DIRECTED]->(d); MATCH (s:Graph) WHERE s.u = 4 MATCH (d:Graph) WHERE d.u = 6 CREATE (s)-[:DIRECTED]->(d); MATCH (s:Graph) WHERE s.u = 5 MATCH (d:Graph) WHERE d.u = 6 CREATE (s)-[:DIRECTED]->(d);
ビジュアライズ
Neo4j Desktopを使ってビジュアライズしてみる
なにこれ高性能過ぎる・・・
アルゴリズム実装
あとはこれをクエリで距離を測ったりいろいろやりたい。
実装するのはこれ
function 深さ優先探索(v) v に訪問済みの印を付ける v を処理する for each v に接続している頂点 i do if i が未訪問 then 深さ優先探索(i)
- ノード3から辿れるものを出力
- これで一応有向グラフをたどることはできてんのかな?
- もともとプログラム側の疑似コードで for each v に接続している頂点 i do ~ done という処理をやらせているのは、グラフの探索のためであった、Neo4j(GQL)では MATCH (src)-[:DIRECTED*]->(dst) にてその指示が完了しているので、そこを考える必要がなくなっている?あとは、後戻り防止のために訪問済みかどうかチェックが付けられればいいのだけど。*1
MATCH (src)-[:DIRECTED*]->(dst) WHERE src.u = 3 RETURN dst;