Neo4jを使い倒してWebメディアを作った話

こんにちは!立ち上げエンジニアのホリです。 今日は、BizHintサービス立ち上げに際して(どさくさに紛れて笑)、新しいデータベースを実戦投入した件について書きたいと思います。

BizHintとは?

時は2016年。ビズリーチ社待望のメディアサービスを立ち上げる機運に満ちあふれていました。 コンセプトとして挙がったのは、「人事に関する(ニッチな)質の高い記事が読める」「閲覧履歴やブックマーク、コメントを通じて自分の興味が可視化できる」でした。

この世界観からまず考えたことは、ログのようなデータを大量に溜め込むシステムになりそうで、特にユーザーと記事の関係性を表すデータが多そうだ、ということです。 さらに、例えば「既読記事ならタイトル文字色をグレーに」など、関係性データを抽出する回数が莫大になりそうです。

これをいつものRDB(MySQL)で実現すると、ユーザーと記事の関係性を表すテーブルが肥大化し性能問題に苦しむことになる、どうにかしなければ、と感じていました。

Neo4jとは?

ノードとエッジからなる「グラフ」データ構造データベース GraphDB の一種です。

グラフ構造 (グラフ構造データの可視化例)

システム運用観点で、下記のように整理しました。

データ構造 マネージドサービス 運用実績 定義変更
MySQL RDB あり 充実 停止を伴う
Neo4j GraphDB なし 少ない 随時

RDBはテーブル単位でサイズ増加スピードを観測し、まずくなったテーブルは物理構造変更やデータ分割といったメンテナンスが必要になります。それに対し、GraphDBは総データ量が集合演算に関係しないデータ構造のため、メンテナンスが理論上必要ありません。

そこで、多くなりそうな記事に関わるデータはNeo4jに、信頼性が問われる金銭に関わるデータ(個人情報も含む)はMySQLと、マスタDBを並べることにしました。

Neo4jを操作する

例えばBizHintで活用している Ruby on Rails だと、ActiveRecord同様に扱えるActiveNodeというgemが用意されています。JavaやPythonにもNeo4j操作ライブラリが存在するようです。 まずはノードを作ってみましょう。

class Kiji
  include Neo4j::ActiveNode
  include Neo4j::Timestamps

  property :title, type: String
  property :description, type: String
  property :body, type: String
end

NoSQLだけに、カラム(プロパティ)定義がクライアント側に来るわけですね。

そして、エッジ(ActiveNodeではリレーションと呼ぶ)を足してみます。

has_many :in, :yondahito, type: :yonda, model_class: 'Dokusya', unique: true

ActiveRecordだと他テーブルキーを格納するカラムに値が入りますが、ActiveNodeだとリレーションが生成され両側のノードと紐付くわけです。

これを検索してみます。例えば 昨日以降に更新された記事を読んだ読者一覧 を抽出します。

Kiji.as(:k).where('k.updated_at >= {yesterday}').params(yesterday: Date.today.yesterday.to_time.to_i).yondahito(:y).pluck(:k, :y)

これでKijiとDokusyaの配列の配列が返ってきて、後はお好きにどうぞ!って具合ですね。

Neo4jを使ったから実現できたこと

  • 放っておいても遅くならない

    RDBだと「最近システムが遅い」とユーザーが体感してしまうレベルになった段階では、大量のレコードを抱えているテーブルが増殖していて、分割などの対策に奔走せざるを得なくなる一方で、Neo4jは性能劣化が気になることはありません。

  • スキーマ変更にシステム停止が必要ない

    NoSQLの特長として、プログラムを書き換えるだけでデータ構造を変更できます。そのため、リリースしたその瞬間から定義変更が完了します。RDBの場合、外部キー貼った小さなテーブルにカラム追加しようとして、思わぬ巨大テーブルにロックがかかりシステムが落ちるなどの事故起こりがちなため、結局夜間メンテになります。NoSQLならそうした夜間の「スキーマ変更」オペレーションから解放されます。

  • 複雑ネットワーク分析の基盤になる

    「集合」の概念はそのままに、その集合がもつ関係をも集計できます。これは、複雑ネットワークの科学と言われる、多くのインターネットサービスで求められる分析となります。

今後の展望

ニッチな話題を扱う「業界特化型メディア」においては、Neo4jはまさに最適で懸念点を探すことが難しいくらいです。現状の数倍〜10倍規模のトラフィックになっても、特段のテコ入れなく捌ききるだろうと考えています。

一点あえて改善点をあげるとすれば、自前で立てたサーバー上に本番機とホットスタンバイ機があるだけの構成で運用しているため、サーバー障害時は切り替えが完了するまでの間システムダウンしてしまいます。そのため、できれば AWS RDS のようなフルマネージドサービスに切り替えたいと思ってます。AWSだと Amazon Neptune が一押しかぁ・・・Neo4j使えなくなっちゃう・・・考えます。笑

まとめ

  • 関係性データが多くなりそうならGraphDB導入は性能メリット大きい
  • Ruby on Rails だとActiveNodeとActiveRecordはほぼ同じ使い方のため、学習コスト少なめ
  • 自前サーバー運用が必要だが、マネージドクラウドサービスが出てきつつある