SNSは比較的データアクセスが多いアプリケーションであり、負荷対策が難しい部類に入る。本稿では、グリーCTOの藤本真樹氏が、GREEというSNSでの経験を基に、SNSの具体的な負荷軽減ソリューションを紹介する。
昨今では、地方自治体や企業内でもSNSを導入する動きが活発化しているようです。しかし、SNSは比較的データアクセスが多いアプリケーションであり、負荷対策が難しい部類に入ります。そこで本稿では、前回お届けした「大規模SNS実現のためのGREEのアプローチ」の内容からもう一歩踏み込んで、GREEという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 | |
+----------------------+----------+------+-----+---------+-------+
このとき、「user_id=1」の友達の日記を、新着順に取得するには次のようなコードになるでしょう。
SELECT * FROM blog, link WHERE link.user_id=1 AND blog.user_id=link.friend_id ORDER BY blog.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 |
+-------+-------+---------------+---------+---------+------+------+-----------------------------+
「Using where」とあるように、user_idによる検索にはインデックスが利用されますが、その後のソートは「Using filesort」とあるように、ソートでインデックスが利用されません。これは、user_idカラムとpublication_datetimeカラムの複合インデックスを作成することである程度改善できます*が、データ量が数千万件以上に肥大化した場合には、やはり性能が劣化します。
また、今度はさらにblogテーブルを、ユーザーIDなどをキーにして分割する必要が出たとします*。この場合、「友達AのデータはblogテーブルAに、友達BのデータはblogテーブルBに」といったように、自分の友達のデータがテーブルや、はたまたデータベースごとに分散してしまうことになります。
このとき、普通にクエリを発行して問題を解決しようとすると、次のような手順になります。
先頭からN件だとまだ簡単ですが、200件目から5件、などという条件でデータを取得しようとすると、考えただけで面倒な処理が発生します。
また、GREEではログイン後の初期画面(いわゆる『ホーム』。図1)で、友達の新着情報を各種(日記、フォト、レビューなど……)表示させていますが、ホームはサイト内でもかなりアクセスの多いページで、毎回前記のような動的ページ生成を行うと、非常にコストが高くなります。
この問題に対処するため、単純にページキャッシュなども利用できますが、
という問題があり、キャッシュの利用も避けたいところです。
この現象は、特にMySQLを使っている場合に顕著。
特にMySQL以外のデータベースを使っている場合に効果が高い。
インデックスを正しく利用したり、クラスタリングシステムの利用で問題を解決できることもあるが、やはり大量のレコード数が存在する場合には、MySQLを利用している場合に限らず、テーブルの分割が必要になるようだ。
Copyright © ITmedia, Inc. All Rights Reserved.