Tuesday, December 27, 2011

Neo4j 使い方メモ

今、Neo4jを調査しているのでメモ。

Neo4jはグラフDBとよばれるNoSQL系のデータベースです。Twitterのフォロー関係のような、有向グラフ構造を保存できます。

http://neo4j.org/

Neo4jはJavaからは組み込みで利用できますが、その他ネイティブ系のバインディングが全然ありません。そのかわり、RESTによるJSON/HTTPのインターフェイスが用意されています。

ここではRESTサーバーとRubyクライアントのセットでNeo4jを利用する例を紹介します。

サーバーのインストール

Neo4jサーバーをコマンドラインから起動するだけなら、ダウンロードページから落としてきて起動するだけ。

サービスに登録するためのコマンドも用意されています。

sudo ./bin/neo4j install

サーバー起動

$ cd NEO4J_HOME
$ bin/neo4j start

以下のURLからWebの管理画面を表示できます。

http://localhost:7474/webadmin

サーバー終了

$ bin/neo4j stop

データの削除

以下のフォルダを削除すると、全てのデータを消去できます。

NEO4J_HOME/data/graph.db
ただし、サーバーが停止しているときに消さないといけないみたいです。消したつもりが復活したりしました。

データ削除インターフェイスを追加するアドオンとかも見つけましたが、1.5までの対応のようです。(執筆時点でのNeo4j最新は1.6。無理矢理動くかは未検証)

https://github.com/jexp/neo4j-clean-remote-db-addon

ドキュメント

RESTインターフェイスについては以下にドキュメントがあります。

http://docs.neo4j.org/chunked/milestone/rest-api.html

ただ、そこまで充実しているわけではありません。 Java APIも含め、全体を網羅したPDFもありました。

http://docs.neo4j.org/pdf/neo4j-manual-stable.pdf

Neography

僕はRubyから利用したいので、NeographyというNeo4jのRESTクライアントを使います。

https://github.com/maxdemarzi/neography

インストールはgemで。

gem install neography

Neography以外にも、RailsのActiveModelのように動作するものとして、以下のようなものがあるようです。

  • neology: Neographyのラッパー
  • architect4r: よりActiveModelに近いが、クエリーのみ実装されていて、index等はサポートしない

traverse

ノードやリレーションの作成などのシンプルな操作はgithubのサンプルを見てもらうとして、グラフをトラバースする方法についてメモしておきます。ちょうどneographyを使ったfacebookの友達サジェストのサンプルがありました。

https://github.com/maxdemarzi/neography/blob/master/examples/facebook.rb

サジェストする部分を見てみます。

def suggestions_for(node)
  @neo.traverse(node, "nodes", {
    "order"         => "breadth first",
    "uniqueness"    => "node global",
    "relationships" => { "type" => "friends", "direction" => "in" },
    "depth"         => 2,
    "return filter" => {
      "language" => "javascript",
      "body"     => "position.length() == 2;"
    }
  }) 
end

最初の引数nodeはグラフ走査の起点です。第二引数"nodes"はノードの走査をする、ということを指定しています。 第三引数のオプションとして、それなりの指定があります。

orderでは幅優先"breadth first"を指定しています。他に深さ優先"depth first"が指定できます。

return filterでは、検索条件を指定します。ここではnodeから2ホップのノード(友達の友達)を返すように指定しています。

depthでは、トラバースする際の最大の深度を指定しています。

Cypher

Neo4j特有のクエリー言語として、Cypherがあります。

http://docs.neo4j.org/chunked/snapshot/cypher-query-lang.html

neo.execute_queryというメソッドでCypherクエリーを実行できます。 たとえば先ほどの友達サジェストは、以下のように書けます。

require 'rubygems'
require 'neography'

@neo = Neography::Rest.new

def create_person(name)
  @neo.create_node("name" => name)
end

def make_mutual_friends(node1, node2)
  @neo.create_relationship("friends", node1, node2)
  @neo.create_relationship("friends", node2, node1)
end

def suggestions_for(node)
  n = Neography::Node.new node
  q = "START n=node(#{n.neo_id}) MATCH n -[:friends]-> f -[:friends]-> t WHERE not(t.name = n.name) RETURN t.name"
  result = @neo.execute_query(q)
  result["data"].map { |f| f.first }
end

johnathan = create_person('Johnathan')
mark      = create_person('Mark')
phill     = create_person('Phill')
mary      = create_person('Mary')
luke      = create_person('Luke')

make_mutual_friends(johnathan, mark)
make_mutual_friends(mark, mary)
make_mutual_friends(mark, phill)
make_mutual_friends(phill, mary)
make_mutual_friends(phill, luke)

puts "Johnathan should become friends with #{suggestions_for(johnathan).join(', ')}"
結果:
Johnathan should become friends with Mary, Phill

ここで実行しているクエリーはこんな感じです。

START n=node(#{n.neo_id})
MATCH n -[:friends]-> f -[:friends]-> t
WHERE not(t.name = n.name)
RETURN t.name

traverseの例とちょっと論理的に同等とは言えないかもしれませんが(同値チェックのあたり)、この例ではうまく動きます。 Cypherではこのように、STARTで起点、MATCHで検索条件、WHEREでフィルター条件、RETURNで戻り値を記述します。MATCHの記法はなかなか強力で、a -[:link]-> t <-[:link]- bのように、逆向きのリレーションも扱えます。

その他

TinkerPopというところが、グラフDBのためのソフトウェアスタックを用意しています。

http://tinkerpop.com/

グラフのトラバースなどに特化したクエリー言語Gremlinや、RESTインターフェイスのRexsterなどがあります。 本当ならNeo4jを直に使うよりこちらを経由した方が良いと思うのですが、ちょっとまだこなれてない感じがしました。

今日のところは、とりえあえずここまで。