前書き
メタップスアドベントカレンダー第一日目の記事です。
Neo4jのドキュメントやグラフデータベースの概要をいくつか読んで、良さそうだな、と思った点をピックアップして書き下します。
また、グラフデータベースとはなんぞやについてはいっぱい記事があるので書きません。
名前と概要を読んだことがあるくらいしか知らないグラフデータベースを、少し掘り下げて調べました。
最短経路検索やSNSの繋がりの分析などに使われるということで、あまり身近ではないと思っていましたが、調べていくと意外と使う機会もありそうな気がしてきます。
カテゴリ検索
参考
これはなかなかすごいと思います。SQLではこんな感じになってしまうところを
SELECT p.ProductName
FROM Product AS p
JOIN ProductCategory pc ON (p.CategoryID = pc.CategoryID AND pc.CategoryName = "Dairy Products")
JOIN ProductCategory pc1 ON (p.CategoryID = pc1.CategoryID
JOIN ProductCategory pc2 ON (pc2.ParentID = pc2.CategoryID AND pc2.CategoryName = "Dairy Products")
JOIN ProductCategory pc3 ON (p.CategoryID = pc3.CategoryID
JOIN ProductCategory pc4 ON (pc3.ParentID = pc4.CategoryID)
JOIN ProductCategory pc5 ON (pc4.ParentID = pc5.CategoryID AND pc5.CategoryName = "Dairy Products")
cypherだとこんな感じで書けるそうです。
MATCH (p:Product)-[:CATEGORY]->(l:ProductCategory)-[:PARENT*0..]-(:ProductCategory {name:"Dairy Products"})
RETURN p.name
階層構造を持ったカテゴリーをRDBで扱うと階層構造が増えるほどにクエリがめんどくさくなりますね。階層構造を増やすにはクエリに手を入れなければなりませんし…。アプリケーション側で再帰的に取得していけば処理自体はシンプルにかけますが、ソートしたり検索しようとした場合全くスケールしないものになってしまいそうです。カテゴリ分けの機能を入れるなら、分類するアイテムをグラフデータベースに連携してカテゴリーはグラフデータベース側で管理するというのは良いかもしれません。階層構造を扱うと場合でいうと、組織図のようなものにも使えるかもしれません。
ただ、データベース間で連携するべきものが増えるたり連携が双方向になったりすると、データの整合性を取る事が非常に煩雑になりそうです。カテゴリーの例で言えば、カテゴリーに関連するデータはRDB側には置かないようにする、など制限を設けると良さそうに思います。
推薦
クエリの書き方が結構簡単でいいです。
Aさんが観た映画と同じものを観た人が観た別の映画みたいなクエリを描きやすいです。
MATCH (u:User {name: 'A'})-[:WATCHED]->(:Movie)<-[:WATCHED]-(:User)-[:WATCHED]->(m:Movie)
RETURN m.title
直感的にもわかりやすい。
また方向を無視して3回辿れば同じ情報に出会えるので、以下でも同じ結果になりそうですね。簡潔。
MATCH (:User {name: 'A'})-[:WATCHED*3]-(m:Movie)
RETURN m.title
SQLで書くと煩雑になるので、こういう関連を辿った検索する場合は重宝しそうです。以下のような感じ。。?
SELECT MOVIES.TITLE FROM WATCHED
INNER JOIN WATCHED W2 ON WATCHED.MOVIE_ID = W2.MOVIE_ID AND W2.USER_ID != :A_USER_ID
INNER JOIN MOVIES ON W2.MOVIE_ID = MOVIES.ID
WHERE WATCHED.USER_ID = :A_USER_ID
クエリが書きやすいと分析もしやすいので、こういったデータはグラフデータベースに流し込んでおくと良さそうです。
あとcorrect関数は地味に便利そうでした。SQLには似たようなものあったか覚えていませんが、collect集計関数は配列を返してくれます。これはgraphデータベースのメリットというよりはcypherのメリットですが。
// Find similar movies by common genres
MATCH (m:Movie)-[:IN_GENRE]->(g:Genre)
<-[:IN_GENRE]-(rec:Movie)
WHERE m.title = 'Inception'
WITH rec, collect(g.name) AS genres, count(*) AS commonGenres
RETURN rec.title, genres, commonGenres
ORDER BY commonGenres DESC LIMIT 10;
感想
クエリがすんごい便利だな!と思います。関連データを持っていて、検索したいのであれば導入してみるのもありかなと思いました。
パフォーマンスに関しては、関係もモデル化しているから早いんだ!みたいに言われていますが、RDBでもちゃんとインデックスはればそこそこいけるのでは。。?という気もします。内部的にどうなっているのかまだよくわからないのでなんとも言えません。検証とかしてみたいですね。
ただ、正直サービスが小さいうちはデータが分散するし運用コストも学習コストもかかるので効率を考えれば採用はしたくないですね。将来的にグラフデータベースのノウハウは貯めておくと役立ちそうなので、そういう意味では使ってみたいなぁと思います。
とりあえず、もう少し調べたいです。