Amazon Web Services ブログ

Amazon Neptuneを使ってニュースパスのコメント機能を実装・運用する方法

この投稿は Gunosy によるゲスト投稿で「AWS Neptuneを使ってニュースパスのコメント機能をGraphDBで実装・運用する方法」の記事に加筆修正を加えたものです。

Gunosy は「情報を世界中の人に最適に届ける」を企業理念に掲げ、情報キュレーションサービス「グノシー」、KDDI株式会社と共同で提供する、ニュース配信アプリ「ニュースパス」、女性向け総合情報アプリ「LUCRA(ルクラ)」等のメディアの開発・運営をしています。また、これらのメディアを通じたメディア事業のほか、「Gunosy Ads」や「Gunosy Ad Network」等のアドテク事業も行っています。情報キュレーションサービスは、インターネット上に存在する膨大な量の情報の中から、特定の基準に基づき情報を収集し配信するサービスです。Gunosy は、情報の収集・整理を、人の手ではなく、アルゴリズムを用いて、ユーザーが必要とする情報を届けています。

「ニュースパス」は、かんたん操作で話題のニュースがチェックできる無料のアプリです。独自の情報解析・配信技術を用いて、提携メディアが配信するニュースの中から自動的に選定した情報をお届けします。ニュースパスではニュースの記事にコメントをつけることができます。この投稿では Amazon Neptune を使ってどのようにニュースパスのコメント機能を実装し、運用しているかについてお話しします。

なぜAmazon Neptuneを使ったのか?

最初は Amazon Aurora Amazon DynamoDB での実装を検討していましたが、グラフ構造に適しているという理由から Amazon Neptune の採用を決めました。

また、今後記事以外に対するコメントなどといった機能追加も、データ構造的に容易であるという点にも魅力を感じました。そして、簡易的なレコメンド機能の実装も簡単に行えるという利点もありました。

Amazon Neptuneで何を解決したのか

ニュースパスでは、記事へのコメント機能を Amazon Neptune を使用して実現していて、コメントデータは次の図のような形で保持されています。

コメントのデータ構造は下記の要素で構成されます。

  • articlecommentの vertex (頂点)が、aboutのedge(辺)で結ばれている
    (記事へのコメントは記事に紐付く)
  • commentuserの vertex が、postの edge で結ばれている
    (コメントはユーザーによって投稿される)
  • commentuserの vertex が、likeの edge で結ばれている
    (コメントはユーザーからいいねされる)
  • commentuserの vertex が、deleteの edge で結ばれている
    (コメントは投稿者のみ削除可能)
  • comment vertex は、property としてIDと本文を保持している。vertex はいくつでも property を持つことができ、個々の property は配列で持つことができる。
  • likepostdeleteの各 edge は、property としてそれらが結ばれた日時を持っている。edge は vertex とは異なり、property は一つしか持つことができない。

このように、グラフデータベースでは、vertex と edge、そしてそれらの label と property を使って、多種類のオブジェクトや、個々のオブジェクト間の関係を簡単に表現することができます。

また、リレーショナルデータベースのようにかっちりとしたスキーマを決める必要もないため、個々のオブジェクトによって自由に property を増減させることも可能です。
例えば、コメントに対してコメントするスレッド機能を実装するとしたら、コメント対象のcommentオブジェクトに対してabout edge を伸ばし、スレッド内のcomment vertex と結びつければよいということになります。

パフォーマンスの最適化

Amazon Neptune を使うことでコメントを取得する API は 99 パーセンタイルで 40ms という高速なレスポンスを実現することができました。

しかし、高速なレスポンスを維持していくためにはデータ構造や、クエリを最適化していく必要があります。次にデータ構造や、クエリの最適化について説明していきます。

データ構造

ニュースパスでは、運用上の理由で「削除済みのコメント一覧を出す」ということを定期的に行う必要がありました。

当初はcomment vertex の propery にdeleted_atをオプショナルで持たせ、これがあるものが削除済みだというデータ構造になっていました。

Gremlinクエリでは下記のように表されます。

g.V().hasLabel("comment").has("deleted_at")

しかし、このデータ構造ではデータ量が増えるたびにクエリが遅くなっていきました。
そこで、可能な限り property に頼らず、vertex と edge で表現する形にできないかを考え、commentuser の vertex をdeleteの edge で結ぶように変更しました。

その結果、コメント量が数百万件で数十 GB ほどに増えた現在でもクエリの速度が低下するということはなくなりました。

クエリは下記のようになりました。

g.E().hasLabel("delete").inV().hasLabel("comment") 

Gremrinクエリの最適化

またクエリの書き方でもパフォーマンスに大きく影響してきます。
例えば、ニュースパスではコメントデータをCommentIDから抽出するとき、当初はこのように書いていた部分がありました。

g.V().hasLabel("comment").hasId("探したいCommentID")

しかし、このクエリは次のように書いた方が断然早いのです。

g.V("探したいCommentID").hasLabel("comment")

つまり、先にラベルで絞るか、それとも vertex の id で絞るか、ということです。
リレーショナルデータベースにて普通に考えたら、先に id で絞る方が当然早いのですが、そこはグラフデータベースの世界でも同じでした。

まとめ

グラフデータベースではリレーショナルデータベースでは実現が難しい、vertex と edge、そしてそれらの label と property を使って、多種類のオブジェクトや、個々のオブジェクト間の関係を簡単に表現することができます。

実際に1日数万のコメント数に耐えつつレスポンスの速さを維持しているのを実感しています。また、スケールイン・スケールアウトも簡単にできて運用も楽なのが魅力です。

Gunosy の場合は独自に開発した Go のライブラリでアクセスしていますが、各種 Neptune のクライアント公式ライブラリも充実しており、GraphDB のバージョンも複数サポートしているので、開発者にとってアプリ開発もしやすいと思います。

そのため、Amazon Neptuneでは完全マネージドで高パフォーマンスなグラフデータベースを簡単に構築できます。