Neo4J入門中。
週末の疲れを癒そうと(?)、SpringBootからNeo4Jを使おうと思ったのだが、github上で結構メジャーと思われるライブラリの導入に失敗する。
- gradle経由で使おうとしたのが良くなかったのかもれない(mavenの方が依存性解決をしっかりするのだとか) => mavenでも失敗する。
- windows上のcygwinでライブラリは動かそうとした。LinuxやMacなら => いずれも失敗 (注、Neo4J自体は全ての環境ですぐに導入できている)。
- SpringBootやめてscalaのライブラリで... => またも失敗。
...たまたま自分の環境が悪かっただけなのかもしれないが、最近、JVM上のライブラリ導入であまりトラブったことがなかったもので、何かあるのではと、、初心者丸出しで、、メジャーになりつつある気がするNeo4Jを改めて調べてみる。
出発点は、
≈Neo4jユーザーグループの方々による記事、
"グラフ構造のデータを高速検索するグラフ型データベース「Neo4j」の勘所"。
いろいろと興味深い。
1)記事を読んで、気になったこと
① 高パフォーマンス
最短ルートの計算は、1000ノード程度のデータでさえ、MySQLの1000倍のスピード
RDB向けのSQLではJOINを駆使して書くようなところを複雑なクエリをNoSQLでは...という話の一例かと思ったが、そもそもが違うという話らしい。RDBが集合演算(全部で何件など)に強みを持つのに対し、Neo4Jのグラフ演算は局所的な最短ルート検索に強みがある、という話。
いずれにせよ、クエリ言語Cypherにも慣れグラフDBの視点をある程度理解したところで、改めてパフォーマンスについて調べてみる必要がありそう。
=> Neo4Jはまだまだバージョンアップが続いているので、以前のバージョンに比べ近時パフォーマンスがアップしているのだとか。
グラフデータベース間でのベンチマークなんていうものもある:
https://github.com/socialsensor/graphdb-benchmarks
Cypherのオープンソース化(OpenCypher)によって、データ構造は似通っていであろう他のグラフデータベースでもおそらくは似たようなクエリがかけられるようになるのだろう。ライパルのグラフデータベースも出揃ったところで、そもそもRDBより高パフォーマンスなのは如何なの場合なのか、はっきりしていくのだろう。
② 実装言語
Neo4jは、Neo Technologyが開発しているオープンソースNoSQLのグラフ型データベースです。実装言語は、JavaとScalaを使用しています。2003年から開発を始めて、2007年に公開されました。ソースコードはGitHub上にあります。
Javaベースのデータベースと紹介されることが多い、Neo4J。
...が、Neo4Jコミュニティ版のコミット状況を見ると、確かにScalaもかなり使われている(ファイル数ベースではJavaの1/3強)。...ついでに、ブラウザ側ではJSに加え、coffeescriptも結構使われている。
scalaは、近時のバージョンで導入されたクエリ言語のCypherとテストコード周りで多用されている。Neo4Jの中身を推し測るのにscalaのテストコードはとっかかりとなってくれるのかもしれない。
チラ見:
package org.neo4j.internal.cypher.acceptance
import org.neo4j.cypher.{SyntaxException, NewPlannerTestSupport, ExecutionEngineFunSuite}
import org.neo4j.graphdb.Node
class AggregationAcceptanceTest extends ExecutionEngineFunSuite with NewPlannerTestSupport {
test("should handle aggregates inside non aggregate expressions") {
executeWithAllPlanners(
"MATCH (a { name: 'Andres' })<-[:FATHER]-(child) RETURN {foo:a.name='Andres',kids:collect(child.name)}"
).toList
}
test ("should be able to count nodes") {
val a = createLabeledNode("Start")
val b1 = createNode()
val b2 = createNode()
relate(a, b1, "A")
relate(a, b2, "A")
val result = executeWithAllPlanners(
s"match (a:Start)-[rel]->(b) return a, count(*)"
)
result.toList should equal(List(Map("a" -> a, "count(*)" -> 2)))
}
test("should sort on aggregated function and normal property") {
createNode(Map("name" -> "andres", "division" -> "Sweden"))
createNode(Map("name" -> "michael", "division" -> "Germany"))
createNode(Map("name" -> "jim", "division" -> "England"))
createNode(Map("name" -> "mattias", "division" -> "Sweden"))
val result = executeWithAllPlanners(
"""match (n)
|return n.division, count(*)
|order by count(*) DESC, n.division ASC""".stripMargin
)
result.toList should equal(List(
Map("n.division" -> "Sweden", "count(*)" -> 2),
Map("n.division" -> "England", "count(*)" -> 1),
Map("n.division" -> "Germany", "count(*)" -> 1)))
}
(略)
org.neo4j.graphdb.Nodeモジュールのテストを行っているファイル。scala組み込みモードで動かす際の参考になるか。createNodeで作ったノード構造に対し、Cypherでクエリをかけている。
③日本ではこれから
最後に、Neo4jの適用分野ですが、欧米の実機としては、多くの業界で使われています。テレコム業界ではネットワークグラフやソーシャルグラフ、金融ではペイメントグラフやポートフォリオ分析、医療では診療グラフや患者グラフです。
データをグラフ構造にして、分析、解析などを素早く検索するというシステムは、すべての業界で必要とされています。欧米では、多くの導入実績がありますが、日本では、これから導入されていく技術です。
確かに、日本ではメジャーでないような気が。冒頭のNeo4J動作しない問題をちょっとググッた時に、日本語でのめぼしい情報が引っかからなかった...
近時、日本語の書籍が執筆されたこともあり、徐々に盛り上がっていくのだろう。
[追記]遅さの秘密?
Eightの中の人による1年ちょっと前の記事:
http://the.igreque.info/posts/2014-06-08-neo4j.html
優れた特性を持つNeo4jですが、 この「index-free adjacency」という性質のせいで、却って著しく遅くなってしまうケースがあります。 「index-free adjacency」は「速さの秘密」であるとともに、「遅さの秘密」でもあるのです。 そうしたケースを、CyberAgentの松本陽介さんが書いた論文が示してくれました。 松本陽介さんによれば、ノードにつながるリレーションの数が、 1万以上まで達すると、探索が非常に遅くなってしまい、使いものにならなくなってしまうそうです。
"ノードにつながるリレーションの数"がボトルネックに。このあたりは、neo4J界隈で問題となっていたらしい:
http://www.palladiumconsulting.com/2014/04/fitting-the-data-neo4j-cypher/
そして、今月の記事(Neo4J 2.3)では、次のように述べられている:
Neo4j 2.3 Fully off-heap cache
That’s not all of the changes, but they are the headline ones. See a theme? Performance. And, as Sebastian likes to say, “Speed is a Feature“. So, the good news is that it is faster, possibly much, much faster. But the bad news is, apparently it wasn’t that fast before.
う〜ん、試してみないことにはよくわからない。が、あらゆる場合で速い銀の弾丸なんてない、という話ではあるのだろう。
2) さて、どこから...
なんとなくpythonで実行
ポエムっぽいことだけ書いて終わるのはなんか負けた気がするので、とりあえず、neo4Jを外から叩いてみよう。
JVM上では負けたので、なんとなくpythonで。
py2neoというのが老舗らしい。
https://github.com/nigelsmall/py2neo
pipが入っている環境ならば、一行で導入できる。
sudo pip install py2neo
バージョンがなんか違うが、気にせずに以下のマニュアルに従って書いてみる。
http://py2neo.org/2.0/
$ python
Python 2.7.10 (default, Aug 22 2015, 20:33:39)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from py2neo import authenticate, Graph
>>> graph = Graph("http://neo4j:yourpassword@localhost:7474/db/data/")
>>> from py2neo import Node, Relationship
>>> alice = Node("Person", name="Alice")
>>> bob = Node("Person", name="Bob")
>>> alice_knows_bob = Relationship(alice, "KNOWS", bob)
>>> graph.create(alice_knows_bob)
(<Relationship graph=u'http://localhost:7474/db/data/' ref=u'relationship/3' start=u'node/7' end=u'node/8' type=u'KNOWS' properties={}>,)
>>> alice.properties["age"] = 33
>>> alice
<Node graph=u'http://localhost:7474/db/data/' ref=u'node/7' labels=set(['Person']) properties={'age': 33, 'name': u'Alice'}>
>>> for record in graph.cypher.execute("MATCH (p:Person) RETURN p.name AS name"):
... print(record.name)
...
Garo
Alice
Bob
うん、cypherのクエリまで呼べたのでとりあえず満足しておく。
ポイントとしては、ブラウザ上の初期画面でneo4jのパスワードをyourpasswordとしている場合、
graph = Graph("http://neo4j:yourpassword@localhost:7474/db/data/")
として、接続を図るところくらいか。あれ、Garoって誰だ...??
要点:グラフ構造を手になじませる
NoSQLとしてかなりメジャーになったmongodbなんかと比べ、neo4Jがちょっととっつきにくい感じがするのは、JSONのように手慣れたツリー構造でさくっとデータを投入できないからなのかな。
とはいえ、データ投入時にやることは大したことはない。
基本形:
1) ノードの記述:AliceとかBobとか個別の情報を叩き込む
2) リレーションの記述:個別の情報間の関係を記述する #例、Relationship(alice, "KNOWS", bob)
3) リレーションについてのプロパティの記述(一方向の関係、双方向の関係か 等)
このあたりは単に慣れの問題と思われるので、手に馴染む言語でデータを投入するバッチ処理とかを書いてみれば、慣れていける気がする。例えば、自分の興味ある分野のキーワードでwebスクレイピングして、neo4Jにデータ投入なんてのがいいかもしれない。cypherでのクエリかけつつ、にやりとするようなお題で。
その上で、Neo4Jのデータはcsvに吐き出せるようだから、例題データを公開してみるとか、かな。
cf.
http://neo4j.com/blog/export-csv-from-neo4j-curl-cypher-jq/