開発部・システム運用グループの長野です。5月15日・16日に東工大大岡山キャンパスで開催されたPerlのカンファレンス、YAPC::Asia 2008に参加してきました。2日目にはセッションの時間を2つ頂いて、発表をしてきたのでその資料を公開します。

■memcached in mixi [pdf]

memcachedはmixiのシステムでも重要なアプリケーションの1つになります。発表ではmemcachedの基本から、弊社でのmemcachedの事例、そして分散方法の改善、TokyoTyrantの活用事例について説明させて頂きました。発表の最後時間が足りなくなり説明できなかったスライドも含まれていますのでご覧下さい。
memcachedについては、研究開発グループのtmaesakaによる記事が、またTokyoTyrantの活用事例については、こちらの記事にもありますので参考にして頂けたら幸いです。
memcachedについては、今後も新しい動きがあればこのブログ等で紹介していきます。

■mod_perlをjob workerとして使う方法 [pdf]

以前このブログで紹介したRSSクローラの裏側についての説明になります。RSSクローラを構築するにあたり考えたことや、利用したPerlモジュールの紹介をさせて頂きました。
資料の中でも説明している通り、この構成はRSSクローラだけではなくメールの配信などにも展開しており、まだまだ応用ができると思っていますので何かの参考になれば嬉しいです。

YAPC::Asiaも東京で行われるのが3回目となります。毎回、多くの刺激をもらうことができ、スタッフの皆様、スピーカーの皆様、参加者の皆様には感謝しております。また次回のYAPC::Asiaでお会いできることを楽しみにしています。

このブログでは初めましての長野雅広(kazeburo)です。mixi開発部・運用グループでアプリケーションの運用を担当しています。
12月12日よりmixiのRSSのCrawlerが改善され、外部ブログの反映が今までと比べ格段にはやくなっているのに気付かれた方も多いかと思います。この改善されたRSS Crawlerの裏側について書きたいと思います

以前のCrawlerについて

以前のCrawlerは

以前のcrawler

  1. cronからbrokerと呼ばれるプログラムを起動
  2. brokerはmember DBから全件、idをincrementしながら取得し、外部ブログが設定されていればcrawlerを起動(fork)
  3. crawlerはRSSを取得しDBに格納して終了

このような設計になっていました。
この設計の問題として、member DBを全件走査するという無駄な動作と、一件一件crawlerを起動するためオーバーヘッドが非常に大きいことがあげられます。またXMLを解析するモジュールが古いため解析が重く、対応できないフォーマットもありました

構築にあたり

新しいCrawlerを構築するにあたって、

  • 監視可能であること
  • 管理・運用できること
  • スケールすること

という念頭に具体的には

  • 処理の分散を行う
  • 処理途中でのスクリプトのデプロイを可能にする
  • 長時間動作し続けるプログラムでDBへの接続はしない
  • モダンなRSS解析エンジンを利用する

といったことも設計に含めました。

新システムの概要

そして12月12日に正式に稼働しはじめたCrawlerのシステムは

rsscrawler

のようになりました。

member DBにあったRSSの情報は構築にあたりRSS DBへ移動しました。この移動を開発部ではレベル1分散と読んでいます。rssのテーブルは以下のようになっています。

+---------------+---------------------+
| Field         | Type                |
+---------------+---------------------+
| member_id     | int(10) unsigned    |
| diary_url     | varchar(255)        |
| rss_url       | varchar(255)        |
| status        | tinyint(3) unsigned |
| last_modified | datetime            |
| last_crawl    | datetime            |
| fetcher_seed  | tinyint(3) unsigned |
+---------------+---------------------+

fetcher_seedは60までのランダムな数値が入っています。このランダムの数字はあとで使います。

サーバは2種類に分かれ、RSS ScriptとRSS Crawlerになりました。Scriptサーバにはmanagerとbrokerがあり、Crawlerサーバではcrawlerが動きます。

crawlerはmod_perlのHandlerとして動作させました。実装も一新し、Plaggerを参考に

といったPerlモジュールを利用しています。crawlerはアクセスを受けるとparameterで渡されたurlからRSSを取得して日記のDBやRSS DBを更新します。crawlerはApacheのMaxRequestPerChildで指定されている数までは動き続けるので1件毎のforkがなくなりました。

cronに代わり、crawlerシステムの司令塔としてPOEで作成したデーモン、managerを作成しました。managerは2秒毎にbrokerを監視し一定数のbrokerを起動します。確実にbrokerを動かすために長時間(20分)動いているbrokerを自動でkillします。また指定されたPortをListenし、telnetなどでbrokerの起動数を確認できます。これを用いてnagiosからcrawlerの監視を行っています。

brokerはmanagerから起動され、RSS DBから前回の巡回から2時間以上たっているurlとmember_idを取得します。取得時のSQLにはfetcher_seedを含めます。fetcher_seedはmemcachedのincr機能を利用して取得した数字を60で割った余りを使います。

my $incr = $cache->incr( $FETCHER_SEED_KEY );
my $sth = $dbh->prepare("SELECT * FROM rss WHERE last_crawl <= ? and fetcher_seed=?");
$sth->execute(
    '2007-12-21 14:14:00',
    $incr % 60
);

このようにすることで、複数のプロセスで巡回が行われていても同じ処理が走らないようにできます。
DBから取得したurlとmember_idをHTTP::Asyncという非同期にHTTPのRequestを行うモジュールを使ってcrawlerに渡します。最大1000件のリクエストを行った後brokerは正常終了します。

処理の流れをまとめると

  1. managerはdaemonとして起動
  2. managerがbrokerを起動
  3. brokerはRSS DBからurlを最大1000件取得しcrawlerに1件毎非同期にリクエスト
  4. crawlerは外部サービスからRSSを取得して日記DBに保存、RSS DBの最終巡回時間を更新

となります。

まとめ

新しいRSS Crawlerの構成で、21万件のURLを2時間以内で巡回することが出来ています。取得するRSSが増えて2時間で回りきれなくなったときでも、今回のこの構成をとっていれば、単純にサーバを追加しプログラムとConfigをデプロイすればスケールしていくことができます。ただしRSSの巡回にあたり、過剰に外部のサービスにリクエストが飛んでしまうことは避けなければいけません。今後は巡回の速度をこれ以上あげるのではなく、pingサーバ等を用意が必要だと思っています。
また、同じような構成でcronで動かしている各種スクリプトを分散できないかということも個人的に漠然と考えてたりします。形になればこのブログにて書いていきたいと思います。