開発部・システム運用グループの長野です。9月10日・11日に東工大大岡山キャンパスで開催されたPerlのカンファレンス、YAPC::Asia 2009に参加してきました。
昨年は2つのセッションをやらせて頂きましたが、今年は1つだけ発表をしましたので、資料を公開します

大規模画像配信とPerl

SlideShareで公開しています。

一部アニメーションを利用していますので、PowerPointもあわせて参照してください。

mixiの画像配信については、このブログや技術評論社様の雑誌等を通して何度か紹介していますが、今回は携帯向けの画像配信、特に画像の動的変換について取り上げました。
画像を扱うライブラリはいくつも種類があり、変換速度や変換後の画像に違いがあります、今回の発表ではその比較もしていますので、参考になれば幸いです。
ちなみに、資料中のカワイイ子供は息子(7ヶ月)です。

YAPC::Asiaは今回から日本でPerlの普及を広めて行くための団体”JPA”の主催になります。コーポレートトラック等新しい試みを開始されました。
ミクシィもJPAに賛同し、Perlの技術・文化の啓蒙・促進にPerlコミュニティと共に取り組んで行きます。

息子が3ヶ月になり、日に日に出来る事が増えていくのを見て、親馬鹿度合いがますます増えているかぜぶろです。

さて、もう手に取って読んで頂いた方も多くいらっしゃると思いますが、技術評論社様からでているWEB+DB PRESSのVol.50から「大規模Webサービスの裏側――inside mixi’s backend」と題して、mixiのシステム運用についての連載をスタートしました!

mixiは2008年12月末で、ユーザー数1,630万人、月間ページビューはPCとモバイルを合わせ143億PVとなり、日本でも有数の規模を誇るサイトに成長しています。
このブロクでも何度か紹介してきていますが、mixiの成長とともに大規模・複雑化しているmixiのサービスを支えるシステムの大半は、サービス開始当初から現在までオープンソースのソフトウェアで構築され、運用が行なわれてきています。WEB+DB PRESSの連載ではmixiのシステム構築・運用のインフラ、アプリケーション両面に焦点を当てて紹介してく予定です。

連載1回目の内容は、
mixi Engineers’ Blog » ロングテールな画像配信 その1
mixi Engineers’ Blog » ロングテールな画像配信 その2 – 3,000万の画像を配信するシステム
でも、紹介をした大規模な画像配信についてになります。

mixiの画像配信は、画像の種類や量、トラフィックのパターンによっていくつかの方法を使い分けています。記事では配信方法を使い分ける理由とそれぞれのポイント、また多くの画像を配信する際のWebサーバの設定のポイントについて説明させて頂きました。

Vol.50表紙

WEB+DB PRESS Vol.50の目次は技術評論社様のサイトで確認できます
http://gihyo.jp/magazine/wdpress/archive/2009/vol50

ぜひ手に取って読んで頂けたら幸いです。

朝晩冷えてきましたね。風邪など引いていませんでしょうか。さて、年末が近づいてくるこの時期に弊社のエンジニアが最も気になるのは、お正月。それも来年1月1日を迎えた瞬間です。

1日1日0時に何があるのでしょう?そう、mixiのサービスで最も日記が書き込まれるタイミングになるのです。個人的に「あけおめことよろアタック」と呼んでいます。今年は日記だけではなく、エコーでもメッセージが飛び交うことでしょう。この時期は携帯電話のキャリアでもさまざまな対策を行っていますが、ミクシィでも年末年始でもユーザの方に快適にサービス提供ができるように努めています

以下は昨年の年末年始の日記投稿数の推移です。青色が12/31から1/1、赤色が1/1から1/2になります

diary_posting.png

1/1の方が全体的に多いですが、特に年が変わる前後の投稿数は倍近くなっていることがわかります。この時に負荷により日記の投稿がしづらい状態になっていたので、もっと多くの日記が書かれていたと思われます。

日記の投稿でエラーが起きた原因は、日記のIDを生成するシステムの過負荷にあります。mixi日記では全体で1つの通し番号が付いていますが、このIDを生成するシステムに負荷が集中し、IDが取得できなくなり日記が書けないという状態が発生してしまっていました。

■ID Generatorの改善

そこで来年に向けて、まずはIDを生成するシステムの改善に手を付けました。
日記のID生成システム、「ID Generator」はMySQLのLAST_INSERT_ID()を利用して実装されており、以下のようなテーブルとスクリプトを用いています。

テーブル

create table idpot (
    id bigint unsigned
);

スクリプト

my $next_id;
my $rv  = $dbh->do('UPDATE id_pot SET id=LAST_INSERT_ID(id+1)');
if ($rv == 1) {
    $next_id = $dbh->{'mysql_insertid'};
}

この実装で通常のサービスで問題がでることはありませんが、正月明けのようなアクセスが特に集中する時には、この部分でMySQLがdead lockを引き起こしてしまいます。実は、この問題は既に去年の11月に当ブログで紹介させて頂いてます。dead lockはMySQLに接続するクライアント数が多くなると発生します。

mybenchを利用したテストでは、接続数が250を超えるとエラーが発生しテストが正常に終了しません。

clients count/client total_count fastest slowest average q/sec
100 10 1000 0.0001 0.085 0.019 19.33
150 10 1500 0.0001 0.13 0.047 71.54
200 10 2000 0.0005 0.17 0.10 206.96
250 10 2020 0.158 0.07 182.51

(MySQL 5.0でテスト。クライアント数250では正確な数値は取得できず)

2008年でもこの問題について把握はしていましたが、クライアント数についての見積もりがあまく、dead lockが予測よりも速く発生してしまったため、日記が書けない事態が発生しました。そこで今年はID Generator自体に手を加えました。

ID GeneratorはMySQLへの接続本数を減らすことで改善を目指しました。具体的にはアプリケーションサーバから直接MySQLに接続するのではなく、専用のAPIサーバを置いて間接的に接続するようにしました。APIのサーバはmod_perlのhandlerとして構築をし、ApacheのMaxClientも1台あたり10程度としました。アプリケーションサーバからの接続はtcpのbacklogに積まれますので、MaxClientが少なくても処理が詰まったりしない限りは問題ありません。もしAPIからIDが取得できない場合は、アプリケーションサーバから直接MySQLを参照します。

id_generate_server_blog.png

APIの性能は「ab」を利用してテストしたところ、1台あたり2000req/s以上の性能がでています。簡単なDBのシミュレーションでも秒間数百の処理ができることも確認しています。

■最新情報DBへの書き込みを非同期に

ID Generatorを改善することで現在顕在化しているボトルネックの解消はできましたが、今年はもう一つ、次に問題になりそうなDBの改善にも取り組みました。mixiの日記DBは弊社でLevel2分散と読んでいるユーザパーティショニングを行っています。日記のDBはノードと呼ばれる日記を保存するDBと、マネージャーと呼ぶ、ユーザの日記がどのノードにあるかを管理するDBの2種類があります。ユーザの日記を表示する時にはマネージャーのDBに接続をし、ユーザIDから、どのノードに日記が保存されているのを問い合わせ、その結果得られたノードDBに再度接続をするようになっています。

diary_partitioning1.png

ただし、この状態ではホームなどのマイミク最新日記を取得する場合に、全部のノードに1つ1つ接続をしてマイミクの最新の日記を検索しなればならないので、ノードとは別に最新日記のIDとタイトルだけを入れる最新情報DBを用意してあります。ちょうど1週間のデータを保存していますので、Weekly DBと呼んでいます。日記が投稿された際には、各ノードとWeekly DBの2カ所にデータを保存しています。

Weekly DBは負荷的な問題は現状ありませんが、1月1日のような日記が集中して書かれる瞬間では、書き込みが多くのアプリケーションサーバから一斉に行われるため、ボトルネックになることが予測されます。

diary_weekly.png

そこでエコーでも用いたQ4MというMySQLのStorage Engineとして開発されているJob Queueのシステムを日記投稿にも投入しました。

diary_weekly_queue.png

書き込みを一旦Q4Mでキューイングし、表側の非同期にWeekly DBへと書き込んで行きます。日記の投稿が集中した場合、Weekly DBではDisk IOを分散することが難しいですが、Q4Mを用いる事でIOを時間軸で分散することができるのでWeekly DBへの負荷が集中し、サービスに影響が出てしまうのを防ぐ事ができます。また、Q4M自体の負荷が高まった場合は、単純に台数を増やす事で解決ができます。

Q4Mを導入した事でもう一つ耐障害性が高まった部分があります。Weekly DBにハードウェア障害が起きた場合にも最新日記をQ4M上に貯めておき、DBが復旧した時点であらためてDBに挿入していくことができます。これによってマイミクの最新日記をQ4M導入前よりも確実に届けることができます。

■まとめ

2009年1月1日にむけて2つの改善を行いました。1つはIDを生成するDB、もう一つは最新情報DBへの保存の非同期化です。2009年”は”ユーザの皆様の「あけおめことよろ」をすべて正常に受け付けることができると思います。来年1月1日に良い結果がでれば、またエンジニアブログで紹介したいと思います。

Squidを検索する度に最初に表示される画像検索の結果に吹き出しそうになる開発部・システム運用グループの長野です。前回のロングテールな画像配信のその2ということで、実際の画像配信システムについて書かせて頂きます。

■プロフィール画像の配信について

前回紹介しましたが、mixiにおいてプロフィール写真を設定を設定しているユーザ数は全体の約70%、1,000万人の方が設定をされています。現在配信をしているプロフィール画像のサイズは180×180、76×76、40×40と3サイズあり、合計3,000万以上のファイル数になっています。また、もっともよく使われる76×76のサイズ1,000万件において、1日にアクセスされる画像の数は800万ファイル以上、うち97%が30回以下と非常に広範囲に渡ってアクセスされています。そのため大量の画像を配信できる仕組みが必要になります。

■配信システムの全体像

プロフィール画像の配信システムの全体像です。

longtail_profile_image2-0.png

画像の保存の部分から説明していきたいと思います。

■ストレージ層

edit_photoの画面や携帯メールからプロフィール画像をアップロードすると、アプリケーションサーバから変換(サイズの変更)と転送を行うサーバへ画像が渡されます。画像を変換するライブラリはメモリを多く使うことが多いのでWeb画面とは別のサーバで変換を行っています。
画像を変換を行った後、配信兼ストレージサーバにコピーされます。ストレージサーバはバックアップと負荷分散を目的に2台1組で構成されており、画像のアップロードは必ず2台へ行われます。
どのグループに保存されるのかは、画像のファイル名の番号の剰余によって決まります。もしストレージサーバに障害がおきて保存が出来なかった場合には、ファイル名を変更して別のサーバに保存をします。

longtail_profile_image2-1.png

実はこの変換の時には180×180のサイズしか作成していません。あとのサイズは配信時にオンザフライで生成しています。これでファイル数を一気に1/3まで減らす事ができます。ディスクIOのコストは画像の変換にかかるCPUのコストよりも高いと考えています。

■Reverse Proxy層

今度はもっとも外側のサーバです。プロフィール画像へのリクエストはまずこのサーバで受ける事になります。これはmod_proxy(mod_proxy_balancer)とmod_rewriteなどを組み込んだApacheです。

RewriteRule ^/photo/member/([0-9]+/[0-9]+/[0-9]+_[0-9]+)([sm]).jpg$ ¥
     balancer://balancer/photo/member/$1$2.jpg [P,L]
RewriteRule ^/photo/member/([0-9]+/[0-9]+/[0-9]+_[0-9]+).jpg$ ¥
    balancer://director/photo/member/$1.jpg   [P,L]

76×76と40×40の画像はbalancerに、180×180の大きな画像はdirectorにそれぞれproxyされます。balancerは次に紹介するSquidサーバになり、directorはSquidサーバ群の下にあるサーバになります。180×180の画像についてはアクセスは比較的少ないのでSquidによるキャッシュ層を通さずに配信しています。

■Squid層

Squid層は2つのレイヤーに分かれます。Squidによるcacheサーバとそれを分散するbalancerレイヤーの2つです。

longtail_profile_image2-2.png

最初に紹介しましたが、プロフィール画像は非常に広範囲なURI、1,000万以上の画像にアクセスがあり、これらを高速に配信するためには画像データをメモリに載せる必要があります。Squidにキャッシュさせる画像のサイズのうちの1つ、76×76の容量の平均は約2.7KBになるので、必要なメモリは最低27GB以上のメモリが必要になります。1台のサーバ(最近のメモリの値段から言えば不可能ではないですが)ではメモリが足りないので、キャッシュデータを複数のサーバに分散していく必要があります。そこで利用したのがSquidに備わっているCARP(Cache Array Routing Protocol)という機能です。

*参考資料

carp.txt
http://icp.ircache.net/carp.txt

Cache Array Routing Protocol and Microsoft Proxy Server version 2.0
http://www.microsoft.com/technet/archive/proxy/prxcarp.mspx?mfr=true

CARPはmemcached界隈で話題になるConsistent-Hashingと考え方は似ていて、URIとcacheサーバのhash値を組み合わせてアクセスするサーバを決定します。サーバの追加や障害時の切り離しを行っても大幅なキャッシュの組み替えが発生しないという特徴もあります。詳しく知りたい方は上のURLやsquidディストリビューション中ののsrc/carp.cソースコードを参考にされるといいと思います。
SquidでCARPを利用する場合は、squid.confにおいて、cache_peerにcarpと指定します。Squidの制限としてキャッシュを横に並べるsiblingモードでCARPを利用する事ができないため、Squidが2段構成になります。

http_port 80 vhost vport
cache_peer 192.168.1.11 parent 80 0 no-query no-digest carp proxy-only weight=1
cache_peer 192.168.1.12 parent 80 0 no-query no-digest carp proxy-only weight=1
cache_peer 192.168.1.13 parent 80 0 no-query no-digest carp proxy-only weight=1
cache_peer 192.168.1.14 parent 80 0 no-query no-digest carp proxy-only weight=1

balancerとなるSquidサーバではcache等はせずproxyだけの機能で利用しています。上の設定では192.168.1.11〜14がcacheサーバとなりcarpアルゴリズムにてproxyしています。Apacheのmod_proxy_balancerでもbalaceの方式をモジュールで追加することができるので、こちらも研究しています。

CacheサーバとなるSquidでは、cache_dirとしてCOSSを利用しています。COSSは大きな単一ファイルにキャッシュデータを保存する形式で以前からSquidにあるufsやdiskdと比較して格段に性能が高く、効率のよいキャッシュを構築できます。

cache_dir coss /var/spool/squid/coss 8000 block-size=512 max-size=500000
cache_swap_log /var/spool/squid/%s 

http_port 80 vport
cache_peer 192.168.1.15 parent 80 0 no-query no-digest round-robin originserver
cache_peer 192.168.1.16 parent 80 0 no-query no-digest round-robin originserver

cacheサーバから再び、cache_peerでストレージサーバの上にあるdirectorサーバ(192.168.1.15と16)を指定しています。round-robinオプションを指定する事で負荷分散と冗長化も可能です。

■director層

最後の層になるdirectorですが、これは通常のApacheです。mod_proxy(mod_proxy_balaner)とmod_rewriteが組み込まれています。Reverse ProxyやSquidのcacheサーバからリクエストを受けて、そのURIから画像ファイル名の剰余を求め、正しいストレージサーバへproxyします。

longtail_profile_image2-3.png

この層を置かずに、reverse proxyやSquidのcache層から直接にストレージサーバへ接続を行う事もできますが、Squidやインターネットに接するサーバの設定を極力シンプルに保つためにこのような構成になっています。ストレージサーバの障害時にはこのdirectorサーバの設定を変更するだけなど影響を極小化するのにも役にたちます。

■まとめ

2回に分けてロングテールな画像配信という事で、mixiのプロフィール画像の配信について紹介してきました。画像の配信についてあまり気にされること多くはないと思いますが、裏側には様々な工夫があります。今回紹介してきた3,000万ファイル以上あり、広範囲にアクセスされるプロフィール画像の配信システムも、何度かの試行錯誤を経験した上でApacheやSquidなどのオープンソースソフトウェアを利用して高速でかつ、安定している仕組みを作り上げられています。
また機会があれば今回紹介しなかった画像配信についても紹介したい思います。

開発部・システム運用グループの長野です。最近は「サーバ/インフラを支える技術」を読みながら通勤しています。今回はmixiの画像配信について書かせて頂きたいと思います。1回目は画像配信の課題について説明させて頂きます。

■画像配信の種類

これまで画像の配信は大きく分けて2種類あると考え、システムを構築してきました。1つは1ファイルあたりへのアクセスが非常に多くなりますが、ファイル数が少ないもの。もう一つはファイル数が膨大になる代わりに、1つのファイルへのアクセスは少ないものになります。

profile_image1.png

前者はmixiの中で使われるロゴ画像やメニューの画像等のページ部品、また広告画像や絵文字画像になり、後者はユーザがアップロードする日記やアルバムの画像にあたります。ページの部品の画像はファイル数はそれほど多くないものの、サーバへのアクセス数が最大で秒間に数万リクエストにもなります。逆にアルバムや日記の画像は全体で数億以上のファイルがありますが、画像へのアクセスは該当する日記やアルバムを閲覧したユーザからのみになり、1つのファイルへのアクセスは少なくなります。

従来はこの2つの考え方で配信サーバを用意してきましたが、mixiへのアクセスが多くなるとこれらの配信方法だけでは対応ができない種類の画像がでてきました。それはユーザがアップロードをし、アクセスが多く、ファイル数も多いプロフィール画像やコミュニティのロゴ画像になります。

profile_image2.png

ここでは数が多くアクセスも多いプロフィール画像の配信について紹介します。

■プロフィール画像の配信

mixiのプロフィール画像は、プロフィールページで表示される180px x 180px、マイミク一覧やコミュニティのメンバー一覧で利用される76px x 76pxのサイズが従来から使われてきましたが、現在、エコーのリリースに合わせて新しく用意された40px x 40pxを加えて3種類の画像があります。このプロフィール画像を設定しているユーザ数は1,500万ユーザの約70%になり、約1,000万ユーザの方が設定されています。ここで単純にファイル数だけを計算すると1,000万人のユーザが大中小3つの画像を持つ事になるので、延べファイル数は3,000万にもなります。

profile_image3.png

このプロフィール画像へのうち76×76のサイズ(1000万ファイル)へのリクエストについて調査したところ、次回に紹介する配信システムのproxyサーバ4台のうち1台へのアクセスは1日で5,000万回以上記録されていました。この5,000万回のリクエストの中でアクセスされるユニークな画像は、800万ファイルあり、全体の70%以上のファイルへ1日でアクセスされていることが分かりました。

さらに詳しく画像毎のアクセス数を調査した結果が以下になります

リクエスト回数 画像数 画像数×リクエスト数 割合
1 2,020,000 2,020,000 4%
2〜10 5,242,000 23,930,000 43%
11〜30 1,130,000 18,300,000 33%
31〜100 157,000 7,320,000 13%
100〜 18,000 3,830,000 7%

このproxyサーバに対して、1日を通して1回のみアクセスされた画像は200万件になり、全体のリクエストに占める割合は4%、2回から10回アクセスされる画像は500万件でリクエスト数では2,000万以上になります。
合計のリクエスト数では、1件あたりのリクエスト回数が30回以下の画像でアクセス数の8割を占め、ファイル数の割合では97%にもなります。所謂、2割のオブジェクトの通信が8割を占めるというような、2:8の法則には全くなっておらず、偏りが少なく多くのファイルにリクエストが分散されることが分かります。縦軸にリクエスト数をとり、リクエスト数を多い順に画像をグラフ上においていくと以下の様になります。

profile_image4.png

非常に尾の長いロングテールなグラフになります。リクエストが30回以下、97%の画像が尾の部分になり、全体の80%以上のアクセスを占めています。

■まとめ

プロフィール画像の配信では非常に長いロングテールの尾の部分である、大量の画像を配信できるシステムが必要になります。実際のシステムについての解説は次回にさせていただきたいと思います。