朝晩冷えてきましたね。風邪など引いていませんでしょうか。さて、年末が近づいてくるこの時期に弊社のエンジニアが最も気になるのは、お正月。それも来年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日に良い結果がでれば、またエンジニアブログで紹介したいと思います。