yuw27b’s blog

技術メモと雑記

Neo4j + Rubyでグラフデータベース探索(Neo4j v3.1.2 + Ruby)

久しぶりにグラフデータベースをいじったのでメモ。

Neo4j・・・NoSQLに分類されるグラフデータベース。
公式ページ:Neo4j, the world's leading graph database - Neo4j Graph Database
少し前になりますが、パナマ文書の解析にも使用されたと紹介されていました。
こちらの日本語記事がとても興味深いです:「パナマ文書」解析の技術的側面 – Keiichiro Ono – Medium

グラフデータベースなので、人物の相関図(Twitterのフォロー・フォロワー関係のような)や、路線図を格納・検索するのには適していますが、テーブル構造のデータには当然リレーショナルデータベースを使うべきなので、「これはNeo4jを使おう!」という場面は私のお仕事の範囲ではまだそれほど多くありません。また、データ自体がグラフ構造でも検索目的によってはRDBが適している、という場合もあります。
ということで、久しぶりになってしまったので、最低限のデータロードと探索方法のメモです。

Neo4jの起動

Neo4jはメジャーバージョンが3になっていました。
個人で非商用に使うぶんには無料ですので、https://neo4j.com/download/ からダウンロードしてインストールします。
バージョン2系のときはコマンドラインから起動していたのですが、3からは簡単なGUIアプリケーションが付属しています。
これを起動してサーバをスタートします。
サーバが起動していれば、ブラウザから http://localhost:7474 にアクセスするとコンソールが表示されます。
パスワードを聞かれますが、初期設定は「neo4j」です。ログインするとまず「パスワード」を変更するようにうながされますので、変更します。
(そのまま「neo4j」にするとエラーになるので、何か別のものにする必要があります。)

Rubyから操作する

RubyからNeo4jを操作するためのドライバは、Neo4jの公式ページで紹介されているものでは、

  • neography
  • neo4j-core
  • neo4j (これはRubyのドライバとしての名前で、データベース本体の「Neo4j」とは別物)

の3つがあります。
neographyは何年も前からあり、私も以前使用していたのですが、GitHubを見ると最近はリリース・開発ともにあまり活発ではないようです。(ある程度完成している、という面もあるのかもしれませんが。)
neo4jも以前からありますが、こちらはRubyOnRailsやActiveRecordと合わせての利用向けという印象で、「スクリプトを書いて探索したいだけ」の用途にはオーバースペックに思えて使用したことがありませんでした。
そういう人が他にもいたのかどうか分かりませんが、neo4jからローレベルAPIだけを取り出したneo4j-coreというものができていましたので、今回はこちらを使ってみます(もしかしたらだいぶ以前から存在していたのかもしれませんが・・・)。

GitHubhttps://github.com/neo4jrb/neo4j-core
Gem:https://rubygems.org/gems/neo4j-core/

ドライバ本体は、

gem install neo4j-core

でインストールできます。

ここから先のサンプルコードは全部まとめてGitHubに置きました。
GitHub - yuw27b/neo4j_ruby_sample: Load and search data on Neo4j with Ruby driver

サンプルデータ:都道府県の隣接関係

サンプル用にこんな感じのデータを用意してみました(*)。くっついている都道府県のリストです。接続方法が橋かどうか、という情報を持っています。

#pref1#pref2#bridge
北海道青森県1
岩手県青森県
秋田県青森県
宮城県岩手県
つづく・・・

グラフのロード

Neo4j本体にはCSVロード用のコマンドラインツールも付属していますが、わざわざインポート用CSVの成形をするくらいなら、Rubyでデータをパースしながらneo4j-core経由でロードしていくほうがやりやすく感じます。
(手元のCSVファイルがneo4jのツールでそのまま使えるフォーマットであれば別ですが、私の経験上そういうケースはあまりないので。)

ロード用スクリプト
https://github.com/yuw27b/neo4j_ruby_sample/blob/master/load.rb

ロードしたらコンソール(http://localhost:7474)で確認してみます。
f:id:yuw27b:20170402223532p:plain
※コンソールの左側メニューにあるボタンは、25ノードor25エッジまで表示します。あまり大きなネットワークをプレビューしようとすると、ブラウザが重くなってくるので、自分でCypher queryを実行する場合も100くらいでLIMITをかけておいたほうが無難です。

グラフの探索

Cypher Queryの一例です。


新潟県のお隣のお隣

MATCH (a{name: '新潟県'})-[*2]->(b) RETURN DISTINCT b

橋でつながっているところ

MATCH (a)-[:Adjoin{connection: 'bridge'}]->(b) RETURN a, b


新潟県から神奈川県までの経路10個まで。ただし、経由する都道府県は9まで。

MATCH p = (a{name: '新潟県'})-[*..10]->(b{name: '神奈川県'}) RETURN p LIMIT 10

※グラフなので、経路探索の場合は容易に組み合わせ爆発が起こります。経路の長さやマッチさせる数に制限をかけないといつまで経ってもレスポンスが帰ってきません。


新潟県から神奈川県までを最短経路で
この場合は、距離を無視しているので、経由する都道府県が最も少ない、という検索になります。また、同様に組み合わせ爆発が起きる可能性があるので、経由数の上限もつけてあります。

MATCH p=shortestPath((a:Pref {name: '新潟県'})-[*0..10]-(b:Pref {name: '神奈川県'})) RETURN p

2つ以上ある場合に全部取るなら、

MATCH p=allShortestPaths((a:Pref {name: '新潟県'})-[*0..10]-(b:Pref {name: '神奈川県'})) RETURN p
ドライバ経由の探索

ドライバ(neo4j-core)経由の場合は、
そのままCypher Queryを投げるやり方

Neo4j::Session.query("MATCH (a{name: '新潟県'})-[*2]->(b) RETURN DISTINCT b.name")

と、

Cypher DSL APIを利用して、もう少し読みやすい(?)感じで書くやり方

Neo4j::Session.query.match('(a)-[*2]->(b)').where(a: {name: '新潟県'}).pluck('DISTINCT b.name')

があります。
個人的には別にDSLじゃなくてもいいかなあ、と思っていますが・・・。

簡単なコード例はこちらにあります:
GitHub - yuw27b/neo4j_ruby_sample: Load and search data on Neo4j with Ruby driver




以下、公式サイトなどのリンクです。
こういうのはできるのかな?どうやるのかな?というときはこのあたりを調べるとだいたいあるような気がします。
公式ページ https://neo4j.com/
Cypher Queryのドキュメント https://neo4j.com/developer/cypher/
Cypher Queryチートシート https://neo4j.com/docs/cypher-refcard/current/

また、Rubyのドライバ(neo4j-core)に関してはGitHubリポジトリにドキュメントがあります。
Home · neo4jrb/neo4j-core Wiki · GitHub


※データはこちらを参考にさせてもらいました:http://uub.jp/prf/rinsetsu.html