読者です 読者をやめる 読者になる 読者になる

冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

Neo4Jの検証メモ

SpringBoot 1.4が対応したみたいなので、これから先に予定している機能のこともあり、用途の検討も含めて少し使ってみました。なお、SpringBootからの利用ではなく、Neo4Jそのものの検証です。

そもそもNeo4Jとは?

大体以下から大まかに抜粋。

neo4j.com

Neo4j - Wikipedia

レコード(Neo4J的にはNode)と関連のグラフ構造でデータを表現するグラフデータベースです。データ構造がリレーションモデルではないので、当然系譜的にはNoSQLなわけですが、他のNoSQLとは違ってKeyValue形式ではないのが特長です。

で、他のグラフデータベース(ぶっちゃけ他に何があるか知りませんが)と違うのは、

といったあたりで、Enterprise Editionの可用性とともに、この辺結構プッシュしてた感じです。

ライセンス的にはCommunity EditionとEnterprise Editionがあります。クエリエンジンや基本的なデータベースとしての機能において両者の違いはなく、クラスタリングやキャッシュなどの運用面において違いがあるようです。なお、Community Editionは無料。

使ってみての諸々

インストールはダウンロードして展開してアプリたちあげるだけなので割愛。特に困ったことはありませんでした。

基本

先ほども少し触れましたが、データモデルは「ノード(Node)」「コネクション(Connection)」で表されます。ノードとコネクションのデータ構造管理とクエリ発行がグラフデータベースシステムの役割です。

ノード

今までのRDBMSで言えばレコードに相当します。考え方としてタイプよりもインスタンスを元に作成されるので、レコードほどスキーマ基準の考え方をしなくてもいいようです。

ノードは属性とラベル(グルーピングのために使ったり、スキーマっぽく処理したり)を付与することができます。ノードの属性に指定可能なタイプは以下の通りです。

  • boolean
  • byte
  • short
  • int
  • long
  • float
  • double
  • char
  • string

Javaで作られているので、この辺は基本的にJavaと一致すると思えば良いかと。

コネクション

ノードとノードを結ぶ関係のことで、RDBMSなら外部キーやら制約やらに相当するかと思いますが、Neo4Jではコネクションそのものに値が持てます。コネクションもインスタンスベースとなるので、ラベルなどによるグルーピングはできますが、厳密なスキーマ運用はシステム上ではしていないです。

インデックス、トランザクション

構文やプロシジャのサンプルにもありましたが、今回は使っていません。全文検索インデックスもある模様。

Neo4J用クエリ Cypher

御本尊はここ。

neo4j.com

細かいシンタックスに触れると単に公式ドキュメントの引き写しになるので、使ってみたときのハマりどころとか気づきを主に。

ノードとコネクションのCRAETE

例えば、知り合い同士の関連を表す場合(公式のサンプルから引っこ抜いてきました)。

CREATE (js:Person { name: "Johan", from: "Sweden", learn: "surfing" }),
(ir:Person { name: "Ian", from: "England", title: "author" }),
(rvb:Person { name: "Rik", from: "Belgium", pet: "Orval" }),
(ally:Person { name: "Allison", from: "California", hobby: "surfing" }),
(ee)-[:KNOWS {since: 2001}]->(js),(ee)-[:KNOWS {rating: 5}]->(ir),
(js)-[:KNOWS]->(ir),(js)-[:KNOWS]->(rvb),
(ir)-[:KNOWS]->(js),(ir)-[:KNOWS]->(ally),
(rvb)-[:KNOWS]->(ally)

(js:Person { name: "Johan", from: "Sweden", learn: "surfing" }) がノード、 (ee)-[:KNOWS {since: 2001}]->(js),(ee)-[:KNOWS {rating: 5}]->(ir) がコネクションの定義です。

  • ノードのシンタックス(value:Label {属性1:値,属性2:値})
  • コネクションのシンタックス(ノード条件)-[value:TYPE {属性:値} ]->(ノード条件)

コネクションのノード条件については省略可能で、シンタックスはノードと同じ、条件の value 以外は省略可能です。

ノードもコネクションもクエリ投げる時の注意点は value で、同一ステートメントの中でしか有効じゃありません。というか、ノードに value で名称がつくわけではないようです。これに気づかずにつまんないハマり方をしまして、一度CREATEでノード作った後に別のステートメントでコネクション作ろうとして同じ value 指定してCREATE投げたら、当然別ステートメントだから参照されず、目的のノード間に対するコネクションが作られず、宙ぶらりんな謎ノードがこれでもかと作られました。

というわけで、一度作ったノードに新しくコネクションを貼るときには、MATCH とCREATE を組み合わせて使いましょう。

すでにノードが存在する可能性があるときはMERGEを使います。検索してみて、見つからなければCREATEしてくれます。

スキーマレスなので、属性のあるなしでエラーは多分出ないです。ぶれても大丈夫。

検索の基本、MATCH

ノードとコネクションの関係を表すパターンで検索をします。

ノードの条件検索

ノードに設定された名称を条件に検索します。

MATCH (ee:Person) WHERE ee.name = "Emil" RETURN ee;

コネクションのパターンから検索

ある条件のノードと KNOWS という関係を持つノードを検索します。ルートになるノードに検索条件をつけます。

MATCH (ee:Person)-[:KNOWS]-(friends)
WHERE ee.name = "Emil" RETURN ee, friends

RETURN は複数の項目セットを返すことができます。

コネクションのパターンは一つで終わらせる必要はありません。友達の友達から趣味が合いそうな人をお勧めするみたいなクエリも書けます。

MATCH (js:Person)-[:KNOWS]-()-[:KNOWS]-(surfer)
WHERE js.name = "Johan" AND surfer.hobby = "surfing"
RETURN DISTINCT surfer

関連の長さを指定するパターン

関係の本数が3以上で5を最大とする関連、みたいな長さを基準にしたパターンも可能。

(a)-[*3..5]->(b)

実行計画(PROFILE or EXPLAIN)

クエリに PROFILEEXPLAIN をつけると実行計画を出してくれるようです。

ユーザ定義プロシジャ

Neo4Jではユーザ定義プロシジャーを作って CALL で呼ぶこともできます。必要なAPImavenリポジトリで公開されていて、Javaのクラスで作ります。

neo4j.com

関数の読み込みも、プロシジャーを作ってjarに固めてpluginディレクトリに入れておけばOKです。

とりあえずサンプル写経して、自分でプロシジャを作ってみたリポジトリがこちら。

github.com

普通に gradle build してできたjarをpluginに入れてNeo4Jを起動すれば関数が使えました。

テストをサポートするライブラリなんかも用意されています。サンプル通りにテストコード書いて実行すると若干起動が遅い上に以下のような出力が出るので、jerseyでNeo4Jを起動しているのかもしれません。

8 08, 2016 1:11:41 午後 com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
情報: Initiating Jersey application, version 'Jersey: 1.19 02/11/2015 03:25 AM'
8 08, 2016 1:11:41 午後 com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
情報: Initiating Jersey application, version 'Jersey: 1.19 02/11/2015 03:25 AM'
8 08, 2016 1:11:42 午後 com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
情報: Initiating Jersey application, version 'Jersey: 1.19 02/11/2015 03:25 AM'


Process finished with exit code 0

とりあえず単体で呼ぶことはできたんですが、例えばこれをMATCHの結果から計算してそのままセットする、みたいなことは、まだCypherシンタックスがよくわかってないのでできませなんだ。。。

まあ、そもそもですね、こんな関数的なプロシジャ作るべきではなくて、もっとバッチっぽい用途でビジネスロジックをデータベースエンジン側に寄せるようなやり方に使うべきで、プロシジャの中でクエリ投げろ、ということですね、きっと。

まとめ

まあ、SpringBootのサポート状況はさておき、グラフデータベース自体は結構使えるんじゃないかという感触です。可用性については、公式サイトの導入事例が結構でかいところなので、現段階ではあまり気にしてないというか。