mixi の年末年始対策 2009-2010
こんにちは。パートナーサービス部の加藤和良です。
2008年末に、mixi の年末年始対策について紹介しました。今回は、ここ数年の年末年始対策の歩みと、今年の対策について紹介したいと思います。実をいうと、設計も実装も自分じゃなかったりするのですが、このまま歴史に埋もれていくのも悲しいので、関係各所に取材してみました。
2008年末をふりかえる
まずは、2008年末をふりかえってみましょう。
あのころはまだ mixi の機能も少なく、年末年始の負荷は主に日記に集中していました。そこで当時は
- ID Generator の改善 - mod_perl をあいだにはさんで MySQL への接続本数を減らす
- 最新情報DBへの書き込みを非同期に - Q4M をつかって負荷を時間軸で分散する
という2つを日記に実装したのでした。
しかし、2008年末から2009年のお正月にかけて、mixi はまたも日記に書き込めない期間をつくってしまいます。Q4M による非同期化はうまくいったのですが、ID Generator の後ろにある MySQL が Too many connections で詰まってしまったのです。
2008年末は個々の mod_perl から ID Generator の MySQL への接続を、一旦専用の mod_perl に集約し、MySQL がうける接続の数を減らすことを目指していました。
ID GeneratorはMySQLへの接続本数を減らすことで改善を目指しました。具体的にはアプリケーションサーバから直接MySQLに接続するのではなく、専用のAPIサーバを置いて間接的に接続するようにしました。APIのサーバはmod_perlのhandlerとして構築をし、ApacheのMaxClientも1台あたり10程度としました。アプリケーションサーバからの接続はtcpのbacklogに積まれますので、MaxClientが少なくても処理が詰まったりしない限りは問題ありません。もしAPIからIDが取得できない場合は、アプリケーションサーバから直接MySQLを参照します。
が、専用の mod_perl でまとめてみても、年末年始の更新量 == 接続数は用意した MySQL の許容量を越えていたのです。
MyISAM ID Generator
そのため、2009年は、年末年始の更新量に対する個々の接続の時間を短くし接続数を一定以下に保つことを目指しました。QPS あげろってことですね。そこで投入されたのが MyISAM をストレージエンジンに使った ID Generator です。
mixi ではほとんどのサービスの MySQL のストレージエンジンに InnoDB を使っています。MyISAM は Senna (というか Tritonn) を使いたい一部にだけ使っていました。ID Generator も当然 InnoDB です。
ただ、ID Generator に必要とされる要件は、他とだいぶ違っています。スキーマとクエリはとても単純ですし、トランザクションは必要ありません。LAST_INSERT_ID() + 1 でどんどん更新していくので、レコードは1件しかなく、テーブルロックもレコードロックも同じです。InnoDB の様々な機能は、実は ID Generator には過剰だったのです。そして、そういう単純な処理であれば、MyISAM を使った方が QPS は高いということがわかりました。
なお、MyISAM の処理速度が比較的良好で間に他のものをはさみたくないこと、そもそも2008年末にあまりうまくいかなかったことから、専用の mod_perl によるリクエストの集約は外しました。
2010年1月の結果と、その対策
こうして迎えた2010年、mixi は初めて、なんの障害も無く新年を迎えることができました。
というわけで、今年の年末年始対策には、前回だめだったところではなく、新しくつくってだめそうなところを探すところからはじめられました。とはいえ、今年追加した様々なサービスにはすでに過去の知見がいかされています。例えばフォトは MyISAM を使った ID Generator を備えていますし、チェック/チェックインには Q4M を使った負荷分散がはいっています。
一点だけ行なったのが Q4M の分割でした。
mixi ではいくつかの処理に Q4M をつかったジョブキューがはいっています。冒頭にふれた2008年末の年末年始対策や、それ以前の mixi ボイスでそれぞれ使った Q4M は、より汎用的な仕組みとしてその後まとめられました。社内では (なんのひねりもないですが) 「汎用ジョブキュー」なんて呼ばれています。
汎用ジョブキューは当初
- light
- heavy
という二系統に数十台がぶらさがって、それぞれ処理するようになっていました。ボイスの投稿といった、軽くて、ユーザーがジョブの結果を即時に確認するようなところには light を、mixi 全体での統計的な処理のような、重くても、遅延が問い合わせの増大を引き起こさないようなところには heavy を、というように使い分けましょう、というのが意図です。
しかし、今年の年末年始は 2009年9月に正式にリリースされた ボイスにおいても、負荷の増大を予想しています。増えたボイスの処理で他が遅くならないよう、あるいは、他の処理が忙しいボイスの処理を邪魔しないよう、今回、ボイスに関しては新たに別系統のキューを用意するようにしました。
蛇足: オレはこう思う
今年の Q4M 分割は、mixi の今後の方向性を示唆していると思います。
mixi は全体で数十台の mod_proxy と、数百台の mod_perl, 数十台の memcached を使っています。このうち、memcached は mixi 全体でひとつのプールとして使っていますし、mod_perl, mod_proxy も、それぞれ PC/モバイル、画像系といったおおまかなプールにわかれています。ジョブキューも (前述のとおり) light/heavy といった区分が主です。
つまり、サービスごとに mod_perl が何台、mod_proxy が何台、というようには分かれていない、ということです。PC のプールにある mod_perl のうち任意の一台を選んだ際に、この mod_perl は
- 日記
- コミュニティ
- メッセージ
- ...
といった様々なサービスについて、リクエストをさばく可能性があります。
この方法には、もし日記の PV が落ちて、ボイスの PV があがっても、その増減の総和が全体の許容量以下であれば、サーバーの台数に手を加えなくてもいい、という長所がありました。
もちろん、PV ないし負荷の増減は新機能の投入や季節要因からある程度は予測しています。しかし、それに合わせて細かくサーバーを増減させるのはコスト面に、予測が外れたら即障害というのはリスク面に、それぞれ問題があります。食費と衣服費がいっしょになってる家計、なんていうとだいぶたとえが悪いですが、ちゃんと理由があるのです。
一方で、この方法には、あるサービスになんらかの障害が発生した場合、mod_proxy やジョブキュー、memcached などの他サービスで共有する資源を介して、その問題がまわりに広まってしまう、という短所がありました。たとえば特定のサービス、特定のページでレスポンスを返す速度が極端に遅くなってしまった場合、mod_proxy がそこにリクエストをふるたびに待たされてしまいます。mod_proxy は他のサービスやページについてもリクエストを処理しているわけで、これは「mixi 全体が遅い」のと変わりません。一つ屋根の下で毎日のように七面鳥を食べている人がいると、靴下も買えなくなってしまうのです。
というわけで、これからは、一枚岩で成長してきた密結合なシステムである mixi を、疎結合なサブシステムの集合として組み直していく必要があるんじゃないかと思っています。思うだけじゃなくて手も動かせるのが中の人の良いところですね。