大規模SNSのボトルネックとソリューション大規模サイトの舞台裏(1/4 ページ)

SNSは比較的データアクセスが多いアプリケーションであり、負荷対策が難しい部類に入る。本稿では、グリーCTOの藤本真樹氏が、GREEというSNSでの経験を基に、SNSの具体的な負荷軽減ソリューションを紹介する。

» 2008年08月29日 08時00分 公開
[藤本真樹,ITmedia]

はじめに

 昨今では、地方自治体や企業内でもSNSを導入する動きが活発化しているようです。しかし、SNSは比較的データアクセスが多いアプリケーションであり、負荷対策が難しい部類に入ります。そこで本稿では、前回お届けした「大規模SNS実現のためのGREEのアプローチ」の内容からもう一歩踏み込んで、GREEというSNSでの経験を基に、SNSの具体的な負荷軽減ソリューションを紹介します。

SNSのボトルネック

 一定以上のユーザー数、データ量、そして機能を持つSNSでは、普通に構築していくと非常に悩ましいボトルネックが2つ顕在化してきます。1つは、「友達の新着」系の情報取得です。もう1つはアクセスコントロール、つまり「この情報は全体に公開、こちらは友達の友達まで」といった制限です。まずは、ボトルネックについて詳しく見てみましょう。

友達の新着情報

 友達の新着情報として、例えば「友達の新着日記」を考えてみます。ここでは仮にリスト1のように、友達情報、日記情報を持つテーブル(linkテーブルとblogテーブル)がそれぞれ存在するとします。


linkテーブル:
+-----------+---------+------+-----+---------+-------+
| Field     | Type    | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| user_id   | int(11) | YES |      | NULL    |       |
| friend_id | int(11) | YES |      | NULL    |       |
+-----------+---------+------+-----+---------+-------+
blogテーブル:
+----------------------+----------+------+-----+---------+-------+
| Field                | Type     | Null | Key | Default | Extra |
+----------------------+----------+------+-----+---------+-------+
| id                   | int(11)  |      |     | 0       |       |
| user_id              | int(11)  | YES  |     | NULL    |       |
| publication_datetime | datetime | YES  |     | NULL    |       |
| title                | text     | YES  |     | NULL    |       |
| content              | text     | YES  |     | NULL    |       |
+----------------------+----------+------+-----+---------+-------+

リスト1 SNSにおける友達情報/日記情報テーブルの例

 このとき、「user_id=1」の友達の日記を、新着順に取得するには次のようなコードになるでしょう。

SELECT * FROM blog, link WHERE link.user_id=1 AND blog.user_id=link.friend_id ORDER BY blog.publication_datetime


 上のコードでは、次のようにして内容を取得します。

  • blogテーブルのuser_idフィールドの値が自分の友達であるエントリのものを
  • publication_datetime(そのエントリが登録された日時)でソートする

大量データのソートでインデックスが有効活用されない

 さて、規模が大きくなってくるとどうなるか考えてみましょう。まず、linkテーブルとblogテーブルのデータベースサーバが分離されてしまうかもしれません。するとJOINができなくなるので、

SELECT friend_id FROM link WHERE user_id=1


としてから、以下のように2段階でJOIN相当の処理をしなければいけません(INの中には取得したfriend_idの値を並べます)。

SELECT title FROM blog WHERE user_id IN (……) ORDER BY publication_datetime LIMIT 0,5


 ここまでは別にボトルネックではないのですが、blogテーブルが肥大化してくると問題が顕在化します。例えば1日平均で10万件程度の日記が書かれるとすると、1年でおよそ3500万〜4000万レコードのテーブルとなります。すると、WHERE句とORDER BY句が並存した場合にインデックスが効果的に使われない*ため、パフォーマンスが劣化していきます。例えばuser_idとpublication_datetimeにマルチカラムインデックスが張られていても、

SELECT title FROM blog WHERE user_id IN (...)


で取得される件数(つまり自分の友達が書いた日記の総数)が増えれば増えるほど、ソートにコストが掛かります。試しにMySQL 4.0.xでEXPLAINしてみると以下のようになりました。


+-------+-------+---------------+---------+---------+------+------+-----------------------------+
| table | type  | possible_keys | key     | key_len | ref  | rows | Extra                       |
+-------+-------+---------------+---------+---------+------+------+-----------------------------+
| blog  | range | user_id       | user_id | 5       | NULL | 2    | Using where; Using filesort |
+-------+-------+---------------+---------+---------+------+------+-----------------------------+

WHERE句とORDER BY句が並存した場合のインデックス使用状況

 「Using where」とあるように、user_idによる検索にはインデックスが利用されますが、その後のソートは「Using filesort」とあるように、ソートでインデックスが利用されません。これは、user_idカラムとpublication_datetimeカラムの複合インデックスを作成することである程度改善できます*が、データ量が数千万件以上に肥大化した場合には、やはり性能が劣化します。

blogテーブルの分割とSNSの相性

図1 図1 ユーザーの「ホーム」ページ

 また、今度はさらにblogテーブルを、ユーザーIDなどをキーにして分割する必要が出たとします*。この場合、「友達AのデータはblogテーブルAに、友達BのデータはblogテーブルBに」といったように、自分の友達のデータがテーブルや、はたまたデータベースごとに分散してしまうことになります。

 このとき、普通にクエリを発行して問題を解決しようとすると、次のような手順になります。

  1. blogテーブルAに対して友達の新着日記を取得するクエリを発行する
  2. blogテーブルBに対して1と同じようにクエリを発行。テーブルN個に対して同様にクエリを発行する
  3. 得られた結果をアプリケーション側でマージ、ソートする

 先頭からN件だとまだ簡単ですが、200件目から5件、などという条件でデータを取得しようとすると、考えただけで面倒な処理が発生します。

 また、GREEではログイン後の初期画面(いわゆる『ホーム』。図1)で、友達の新着情報を各種(日記、フォト、レビューなど……)表示させていますが、ホームはサイト内でもかなりアクセスの多いページで、毎回前記のような動的ページ生成を行うと、非常にコストが高くなります。

 この問題に対処するため、単純にページキャッシュなども利用できますが、

  • 表示されるデータがユーザーごとに異なるため、キャッシュのヒット率が非常に低い
  • データの性質上即時性が求められるため、あまりキャッシュしたくない

という問題があり、キャッシュの利用も避けたいところです。

このページで出てきた専門用語

WHERE句とORDER BY句が並存した場合にインデックスが効果的に使われない

この現象は、特にMySQLを使っている場合に顕著。

複合インデックスを作成することである程度改善できます

特にMySQL以外のデータベースを使っている場合に効果が高い。

ユーザーIDなどをキーにして分割する必要が出たとします

インデックスを正しく利用したり、クラスタリングシステムの利用で問題を解決できることもあるが、やはり大量のレコード数が存在する場合には、MySQLを利用している場合に限らず、テーブルの分割が必要になるようだ。


       1|2|3|4 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.