Blog

Neo4jでソーシャルグラフのよくある機能を実装した

はじめまして会員システムグループのkiqkiqです。

みなさんはリレーショナルデータベース以外のデータベースについてはご存知でしょうか?

データベースの中にはRDB以外にも時系列型やキーバリュー型、カラム指向型などいくつかの種類のデータベースがあります。このブログではこれらのデータベースの中でもグラフデータ型のデータベースについて、SNSなどのソーシャルグラフを題材に、グラフDBに関連する技術やSNSなどで見る基本的な機能の実装を紹介しようと思います。

グラフデータベースについて

グラフデータベースはノードとエッジ、その2つに関する属性の3つの要素でノード間の関係性を扱うことに特化したデータベースで、以下の図のようなグラフ構造を扱うデータベースです。

このグラフデータベースは地図などに用いられる道路網の経路探索やSNSなどの友人関係、ECサイトにおける購買情報などを表現するために用いられており、グラフ構造の機械学習モデルであるGNN(グラフニューラルネットワーク)と組み合わせて使用されることもあります。また、グラフデータのデータ分析における基盤としても活用されています。

このグラフデータベースはノードとエッジの集合としてデータを扱うもので、NoSQLの1つに分類されます。

このブログではグラフデータベースの1つのであるNeo4jとそのクエリ言語のCypherを用いて、SNSのよくある機能を実装していきます。

Cypher

Neo4jではCypherというクエリ言語が用いられます。

Cypherは宣言型のクエリ言語で、グラフデータベース用のSQLに相当する言語です。CypherではSQLの SELECTに対して MATCH句を用いてデータの検索を行います。基本的な記述方法は、解のように記述します。

ノード部分は (:ノードのラベル)エッジ部分は [:エッジのタイプ](リレーションシップのタイプ)と記述します。無向グラフは -、有向グラフは <- ->で接続を表現し、 RETURN句の後の識別子を返します。識別子は変数のようなもので、ノードやエッジの :の前に宣言して使用することができます。

これに WHERE句で条件を追加したり、 ORDER BY句で並び替えることができます。複数の処理を組み合わせるときは WITH句で識別子を引き継いで、処理を増やすことができます。

あとはノード・エッジの追加や削除には CREATE DELETE、ノードやエッジに対する属性の登録、更新、削除には SET REMOVEなどが基本的なクエリです。

ソーシャルグラフのよくある機能を実装

環境

  • Python
  • Neo4j
  • FastAPI

実行環境としては、dockerでFastAPIとneo4jのコンテナを立てた環境になります。

共通処理

まずはこれから説明するAPIで共通して必要になる処理について説明します。

主にDBへの接続処理についてですが、この処理を1つのメソッドにまとめています。

一点注意としては GraphDatabase.driverで指定するURLは bolt://コンテナ名:ポート番号で指定するそうです。docker環境の場合コンテナ名はcompose.yamlの container_nameを指定してください。 auth=はcompose.yamlの環境変数でIDとパスワードとして設定しておいたものを指定してください。

また、今回は初期データとして下の図のようなグラフデータを用意しました。これから紹介する各機能はこのグラフデータを基に行なっていきます。

機能1:ユーザーの登録

1つ目のAPIはユーザーの登録機能について説明します。

登録はとてもシンプルで、 CREATE句でノードを追加するだけで、登録することができます。クエリは下記のようなものになります。

今回はノードのプロパティ(ユーザー情報)には名前と年齢の2つのプロパティを定めました。

それぞれのプロパティに対応した $user_name $ageはFastAPIでリクエストされた時のパラメータを格納するために使います。

最終的なAPIの実装は下記のようになりました。

session.run $user_name $ageに変数を指定して、それぞれの値でノードを作成します。

これをFastAPIの /docsで実行してみます。30歳のHさんを追加する場合、実行されるクエリは下記のようなクエリになります。

neo4jの /browserで結果を確認すると下の図のようにノードが追加されていることが確認できます。

機能2:フォロー機能

次に紹介するのはユーザー間でのフォロー機能です。

フォロー機能もノード間のエッジを追加するだけなのでシンプルに実装できます。

基本的には指定したノードを検索し、その2点を接続するためのエッジを CREATE句で作成するだけで実装できます。クエリとしては、下記のようなものになります。

API全体の処理はユーザーの登録機能とほとんど変わらないので省略します。

このAPIでHさんがCさんをフォローするようにAPIをリクエストする場合、実行されるクエリは下記のようなクエリになります。

結果を確認するとHさんからCさんに向けてエッジが貼られていることがわかります。

機能3:フォロー(フォロワー)一覧表示

次はフォロー(フォロワー)一覧表示機能を説明します。

フォロー(フォロワー)一覧表示では、 MATCH句でグラフのノードとエッジのラベル、タイプを指定し、 WHERE句で特定のノードに絞るようなクエリで実現できます。実際のクエリは下記のようなものになります。

フォロー一覧表示

フォロワー一覧表示

API全体では、下記のようになりました。

フォロー一覧表示のAPIでAさん指定してリクエストする場合は下記のクエリが実行されます。

レスポンスとしては以下のデータが返されます。

このようにAさんに対応したノードがエッジを向けている3つのノードが返されているのがわかります。

機能4:フレンドのレコメンド機能(発展)

最後に発展としてSNSなどでよくみる簡単な友達のレコメンド機能をNeo4jのグラフdbで実装してみようと思います。考え方としては「友達から一番接続数(エッジ)の多い友達の友達」を返すようなAPIを実装します。この実装では、クエリがややこしくなってしまうので、これまでと違い無向グラフとしての実装になります。実装内容に関しては友達と友達の友達間でのエッジの本数を”友達の友達”単位でカウントする形になります。

クエリとしては下記のようになります。

このクエリで注意する点としては、1行目のエッジ [:FOLLOW*2..2]の部分で、FOLLOWタイプのエッジを深さ2(半径2のエゴセントリックネットワーク)の範囲まで含めるという記述になります。あとは、 WITH句で必要な識別子を引き継いでいくような記述になります。(もっと簡潔な書き方があるかもしれないです)

API全体の実装としては下記のようになります。

このAPIでCさんを指定してリクエストすると以下のレスポンスが返されます。

レスポンスの内容としては友達からの接続数とそのノードを表しており、Eさんが2人の友たちとも接点があるという意味を示しています。そのためCさんにはEさんをレコメンドするのが最適だとわかります。

まとめ

グラフデータベースであるNeo4jを用いてSNSのよくある機能の実装を紹介しました。グラフデータはRDBに比べて直感的で分かりやすいので、アイデアがあればさまざまな分野に応用できると思います。また、このブログではグラフデータベースのNeo4jを紹介しましたが、グラフデータベース以外にもキーバリュー型のデータベースであるRedisや時系列のデータベースであるInfluxDBなどもあるので、興味がある方は調べて見てください。

We are hiring!

ニフティでは、さまざまなプロダクトへ挑戦するエンジニアを絶賛募集中です!
ご興味のある方は以下の採用サイトよりお気軽にご連絡ください!

カジュアル面談も受け付けています!

Tech TalkやMeetUpも開催しております!
こちらもお気軽にご応募ください!