Twitterのスケール関係で、面白い記事を発見したのでまとめ。一時期「スケールしない」とか「動作が不安定」だとか言われ続けていたTwitter。5月ごろにslashdot.jpでも話題になっていた。論調は総じて「Twitterがスケールしないのは、Rubyを使っているから」というもの。Insiderに掲載される。Cook氏によるもの。Cook氏によれば、TwitterのスケールとRubyは何の関係もないという。post.Twitterの場合、スケールの問題に、開発言語(Ruby)が少しでも関係している可能性はゼロといっていい。原因は、レスポンスをキャッシュすることが非常に困難であるというTwitterのアーキテクチャ上の問題だろう。ユーザはみんな(フォローリストの中身によって)それぞれ違うデータを取得するわけだから、ユーザ間でキャッシュの使いまわしができない。ある特定のユーザのみをとって見ても、彼がフォローするユーザが発言するたびに取得すべきデータが頻繁に更新されてしまうわけだから、キャッシュしてもあまり意味が無い。この問題を解決するにはかなり特殊なキャッシュ・アーキテクチャを実装する必要がある。Ruby責任説を完全否定し、Twitterのスケール問題はそのアーキテクチャに問題があるからだと言っている。I」で触れられている。scaling.Twitterをスケールさせることの何がそんなに難しいのか、みんなわかっちゃいないみたいだ。Railと関係があるって人もいるし、データセンターとか運用の問題だろうという人もいる。分散システムを導入すれば問題が解決するとか考えてるやつもいる。そんなことしても状況が悪化するだけだ。大規模なWebアプリケーションを構築することが簡単だと考えるのは馬鹿げている。(それほど難しいことだというのに)そういったWebアプリケーションのスケール周りをコモディティ化してくれる会社が数えるほどしかないっていうのは、ある意味驚くべきことだ。message.実装面を別にすれば、インフラ面からTwitterを考えた場合、大きく二つのサブシステムに分けることができる。一つ目は届いたメッセージを、フォロアーにIMやSMS、emailを使って届ける「ディストリビューションシステム」。二つ目は、自分がフォローしているユーザや、その他のユーザについての、ステータス一覧(最新メッセージ)を取得する「クエリシステム」だ。一つ目のディストリビューションシステムを構築するのは簡単じゃないけど、スケールさせるのはずっとたやすい。誰が誰をフォローしているかを表す"サブスクリプションリスト"は静的だし、リストのサイズも大抵は小さい。Twitterでは、あるユーザの発言は、フォロアー全員に届ければいいわけだから、どのメッセージを誰に届けるのかについて色々と計算する必要ない。services.そういうわけだから、あがってきたメッセージを配送することに関しては、規模が大きくなってもそれほど難しくはないし、実際こういった類のアプリケーションはかなりスケールして、パフォーマンスも良好、且つ安定稼動させることができるってことが、過去に示されている。Twitterがこんな風に、PUSH要素しかないシステムであればスケールの問題なんておきなかっただろうし、競合に対しても技術優位性を確保できなかったから、競争はある意味もっと厳しかっただろう。「クエリシステム」はそれほど簡単な話じゃない。DBへの問い合わせが簡単なwebメールサービスとか(自分のメールボックスに新しいメールが来ていないかどうかを調べるだけでいい)と違って、Twitter上で自分のホームページをリロードする(自分の友達全員のページそれぞれに、何らかの更新があるかどうかを調べなきゃいけない)ってのは、かなり複雑な問い合わせをする必要がある。Twitterの使われ方では、ユーザの読みこみ/書きこみ比率が、他のWebサービスと比べてかなり違う。ATOM).何か大きなイベントがあった時にTwitterがダウンしてしまうのはこういった処理のせいだ。アクセスのたびに複雑で反復的ななクエリを実行する必要があるから処理に時間がかかる。リクエストがタイムアウトしたり、運が悪けりゃシステム全体がラグってしまう。ユーザごとにカスタムされた情報を表示しないといけないから、生成負荷は重いし、キャッシュも難しい。この処理では、まずユーザがフォローしている人のリストを取得する。それぞれの人に対して、ステータスが公開か非公開か、もし非公開だった場合アクセスしたユーザに閲覧権限があるかどうかを調べる。権限があれば最新の数件のメッセージを取得する。いったことをフォローリスト全員に対してやらなきゃいけない。全部終わったら、取得したメッセージを集めて、時系列で並び替えて、最新の数件だけをJSONだとかXML、RSS、ATOM形式に変換する。required.全部のメッセージが単一のデータベースに入っているなら、こういった抽出処理は、データベースの負荷は重いけれども、シンプルでわかりやすいクエリですませることができる。データがテーブルひとつに収まりきらなくなったり、サーバ一台では容量がたりなくなってしまったとき、アプリケーションは複数のデータベースにリクエストを投げなきゃいけない。データベースの分割は、スケールの手段として多くのWebアプリケーションで有効だけれど、(Twitterのようなソーシャルアプリケーションでは)データベースの分割は、システムの実行速度を遅くさせてしまう。データベースが分割されていた場合、クエリを処理する上で有効だったインデックスの威力が減退してしまうからだ。あるユーザがフォローしている友達が全員同じデータベースに載っていなかったとしたら、別のデータベースを探しにいかなきゃいけない。Twitterが苦しんでいた原因は、ソーシャルアプリケーション特有のデータ処理が、RDBMSにとって、かなり取っ付きにくい類の処理だったからだ。TwitterのようなWebサービスは、どうすればスケールしてくれるのか。sent.ユーザごとにデータを重複して持たせることが、この問題に対する解法のひとつだ。emailと同じように、それぞれのユーザに「メールボックス」みたいなものを持たせて、誰かがステータスを更新したら、そのユーザをフォローしている人全員のメールボックスにそれぞれステータスのコピーを入れる。すればWebメールと同様、データベースを分割した状態で、いくらでもスケールさせることができる。欠点のひとつは全体のデータサイズがかなり大きくなってしまい、ディスクスペースを浪費すること。欠点のもうひとつは、一度送られてしまったメッセージが削除されたようなときにかなりややこしい処理をするはめになること。ユーザからアクセスがあって初めて情報(フォローしている人のステータス)を探しにいくのではなく、誰かのステータスが更新された時点で、その人をフォローしているユーザ全員のページ情報を更新し、どこかに保管しておくというもの。すれば、情報の更新自体は、「デリバリーシステム」とほぼ同じ処理なので簡単にスケールするし、ユーザが友達の最新ステータスを取得しにきたときには、既に準備された情報を送るだけなので、Webメールと同様、やはり簡単にスケールすることができる。Twitterが直面したアーキテクチャ上の問題は、Twitterに限った話ではない。FacebookやPownce、LinkedIn、mixi等、ソーシャルネットワークをベースに組み立てられたコミュニケーションサイトであれば、程度の差こそあれ、同様の課題をアーキテクチャに抱えているといえるだろう。この問題への対処は、SNS系サービスを大きくする上で避けては通れないはず。日本のGREEがこの問題に対してどのように対処しているか、ITmediaの記事「大規模SNSのボトルネックとソリューション」で詳しく解説されている。大規模SNSのボトルネックとソリューション一定以上のユーザー数、データ量、そして機能を持つSNSでは、普通に構築していくと非常に悩ましいボトルネックが2つ顕在化してきます。1つは、「友達の新着」系の情報取得です。1つはアクセスコントロール、つまり「この情報は全体に公開、こちらは友達の友達まで」といった制限です。(中略)blogテーブルが肥大化してくると問題が顕在化します。1日平均で10万件程度の日記が書かれるとすると、1年でおよそ3500万〜4000万レコードのテーブルとなります。BY句が並存した場合にインデックスが効果的に使われない*ため、パフォーマンスが劣化していきます。(...)で取得される件数(つまり自分の友達が書いた日記の総数)が増えれば増えるほど、ソートにコストが掛かります。(中略)また、GREEではログイン後の初期画面(いわゆる『ホーム』。図1)で、友達の新着情報を各種(日記、フォト、レビューなど......)表示させていますが、ホームはサイト内でもかなりアクセスの多いページで、毎回前記のような動的ページ生成を行うと、非常にコストが高くなります。表示されるデータがユーザーごとに異なるため、キャッシュのヒット率が非常に低いデータの性質上即時性が求められるため、あまりキャッシュしたくないという問題があり、キャッシュの利用も避けたいところです。GREEはこの問題にどう取り組んだのか。これら2つの問題について、目下のところGREEでは、「フックを利用したイベント通知機構」を利用して解決しています(図2)。リクエストがあったタイミングでデータを取得するのではなく、データ更新のタイミングであらかじめ必要なデータを非同期に構築しておくわけです。考え方としては、個人ごとに「メールボックス」のようなものを準備するHammer-Lahav氏の説明と大体同じで、且つデータのDBへの挿入は非同期で処理し、フロント側(webサーバ)を待たせるようなことはしないと。フロント側の処理を迅速に終わらせることは、Webサーバリソース(プロセスやスレッド)の回転効率を上げることにつながり、サーバのメモリ使用効率が高まる。このあたりの話は、httpでいかにC10K問題を解決するか、という文脈で語られることが多い。beautiful(中島聡さん)の記事「マルチスレッド・プログラミングの落とし穴、その2」でも紹介されている。DeliveryエンジンであるNouncer開発プロジェクトに従事されているということで、これを使えば開発者は簡単にスケールするMicro-blog(Twitterみたいなサービスの総称)サービスを作れちゃうらしい(サイトを見たところプロジェクトが停滞しているようだけれど..)。やってまたひとつ技術がコモディティ化していくんだなー。00:07わかり易い解説で、大変勉強になりました。大規模サイトの開発は、ボトルネックをどう解決していくのかを考えるのが面白いですね。非常に刺激になりました。programmerさん、コメントありがとうございました。異形の廃墟ビル『メタボ岡崎』に引っ越しましたライフネット。