このブログでは初めましての長野雅広(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で動かしている各種スクリプトを分散できないかということも個人的に漠然と考えてたりします。形になればこのブログにて書いていきたいと思います。

仕事を複数抱えた場合のコンテキストスイッチに高いオーバーヘッドを伴っているmikioです。今回は、近頃はじめたインディーズ機能についての思いを僭越ながら述べてみます。

インディーズ機能とは

インディーズ機能とは、実験的なサービスを早い段階で公開してユーザの皆さんからのフィードバックを反映させてくことにより、より斬新で親しみやすいサービスをより迅速にお届けするための枠組です。一般的には「ベータ機能」とか呼ばれている枠組に相当しますが、サイト自体が「ベータ」を標榜しているので「アルファ」になり、しかしそれもわかりにくいということで、紆余曲折あった末に「インディーズ」という名前に落ち着きました。

mixi開発チームでは日々新しい機能やサービスを模索し、また既存のサービスや機能の改善を図っているわけですが、当然ながら、現在開発中もしくは企画中のものを投入した暁にそれがヒットするかどうかは、実際に投入してみなければわかりません。ネット上の他のサイトの動向を見て似たようなサービスを提供するいわば追従型の案件の場合はまだ影響を読みやすいのですが、mixiならではの新しいサービスを提供しようとするとそれは一気に難しくなります。そのため、正式なサービスに仕上げる一歩二歩手前の段階であるインディーズ機能を公開して、それを作りこむ価値があるかをユーザの皆さんに評価いただこうと考えました。

正式なサービスにしようとすると、スケーラビリティ/アベイラビリティ/インテグリティなどを確保するために多くの工数が必要になり、デザインやユーザビリティを向上し、コストやリスクを軽減するために様々な検討と施策をしなければなりません。したがって、リソースの問題から全てのアイデアを実装することは現実的には不可能です。そして、提案したもののペンディング(つまり雑誌の休刊と同じで復活の見込みはほとんどない)に処される企画は山ほどありますし、開発途中でペンディングにされるプロジェクトすら出てきます。これはちょっぴり悲しいことですが、有限のリソースの中でヒットするサービスを提供していくには、ある程度の淘汰はしかたのないことでしょう。

しかし、淘汰されたプロジェクトの中にももしかしてヒットするものがあったかもしれません。また、ユーザの皆様から不評だったならまだしも、社内プロセスで挫折したことで日の目を見ることなく淘汰されたプロジェクトでは、担当者の無念は筆舌に尽くしがたいものがあります(もちろん、ひとつのアイデアに固執せずにアノ手コノ手を駆使するのがハッカーなわけですが)。そこでインディーズ機能の登場です。社内的にいえば、インディーズ機能の位置づけは、エンジニア主導でプロジェクトを推進するための枠組です。比較的少ない工数と、公開した際のその機能への誘導が貧弱であることと引き換えに、実験的な機能を世に評価してもらう機会を得るのです。そしてもちろん、コンセプトが評価されれば正式サービスに格上げすべく作り込むことになるでしょう。逆に不評であれば、累積コストの低いうちに撤収され、担当者の黒歴史をささやかに彩ることになるでしょう。

インディーズのキモチ

巷で言うところの「indies」とは「independent labels」の略だそうで、つまりメジャーレーベル群の傘下からは独立した存在である中小のレーベル群を指す言葉です。そして我々mixi開発チームも、既存の体制や価値観から独立した新しいサービスや機能を提供していく気概で、この名前を選びました。

通常、新しい企画を発案し推進していく際には、その企画の目標が立てられることになります。ページビューがどれだけとれるかとか、滞在時間がどれだけ増えるかとか、広告枠でいくら売り上げるかとかが主な指標になります。一方で、インディーズ機能はそういったメジャーな価値観から一歩距離を置いて、こんなコンテンツが喜ばれるかもしれない、こんな機能が欲しかった、この操作感の方が使い易いんじゃないか、この技術を適用したら不可能が可能になるんじゃないかといった視点でものを考えます。ともすればエンジニアの暴走などと揶揄されるかもしれませんが、そもそもmixiはそうやって始まったサービスなのです。今現在はメジャーではないかもしれないが、しかし、マイナーではなくインディーズでありたい。そういう気概で新しいサービスや機能を提供していきたいと思います。

キーワード関連情報

毎度のことですが前置きが長くて恐縮です。ここからが本題で、インディーズ機能の第1弾として、「キーワード関連情報」という機能をリリースしました。これは、日記キーワードランキングでランクインした言葉に関連する日記やコミュニティなどの付加情報を提示する機能です。

screen.png

日記キーワードランキングで自分が多少なりとも知っている言葉がランクインしている場合はいいのですが、全く見たこともないような言葉があると何だか悔しい気分になりますよね? 例えば今ではすっかりおなじみの「おっぱっぴー」ですが、ランクインしているのを最初に見た時は何じゃそれと思ったものです。そういう場合にはその言葉を使っている日記を見て内容を把握するわけですが、全く知らない言葉の場合はなかなか手間がかかります。「最近の流行が一目で把握できる」というキーワードランキングのコンセプトにおいては、必死こいて内容を調べなくてはならないのはイマイチです。そこで、「その言葉はどういう意味なのか」「その言葉がなぜランクインしているのか」という簡潔な情報を提供したいと思いました。流行のキーワードの意味と理由まで把握できれば昼休みは無敵です。

以前、キーワードランキングの秘密という記事でキーワードランキングのアルゴリズムについて述べましたが、キーワード関連情報はその考え方を発展させたものです。頻度を集計する際の副産物である共起頻度をもとに算出した関連語を提示するのがその核になります。例えば、「おっぱっぴー」が何だかわからないとしても、その関連語が提示されれば推測がしやすくなります。この例では、「お笑い芸人」「小島よしお」「ぐるナイ」などが関連語として提示されれば、おそらくバラエティ番組に出演した小島よしおという芸人のギャグで、それが非常に面白かったからランクインしているのだろうなと推測できるということです。

アルゴリズム

では、関連語をどうやって算出しているのでしょうか。その答えは簡単で、元となる言葉と同じ日記または同じ文章でよく使われる言葉は関連語だと判断するのです。「おっぱっぴー」がランクインした日の日記には、「昨日ぐるナイ見てたら出てきた小島よしおがやばかった。おっぱっぴーww。まじやばい」などという表現がよく見られます。この例文から「昨日」「ぐるナイ」「小島よしお」「おっぱっぴー」という4つのキーワードが抽出されたとしたら、「昨日:ぐるナイ」「昨日:小島よしお」…「小島よしお:おっぱっぴー」という、12パターンの組合せ(正確に言えば順列)が得られます。この処理を対象となる全ての日記に対して行うとパターン数が半端じゃないことになるわけですが、そこをうまくフィルターしつつデータベースにいれていくと、共起頻度データベースができあがります。あとは、集計時に「おっぱっぴー:*」というクエリで共起頻度データベースを検索すれば、「おっぱっぴー」の共起語を頻度順に取得することができるわけです。

ただし、上記の方法だけだと言い替え表現がうまくとれないという弱点があります。「エヴァ」の関連語として「エヴァンゲリオン」を取りたいとしても、文中で「エヴァ」を使うことを選択した人は同時に「エヴァンゲリオン」を使うことは少ないので、うまくいかないのです。この問題に対しては、共起語の傾向の類似性を見ることで対処しています。例えば「エヴァ」の共起語が「映画」「アスカ」「シンジ」「初号機」であり、「エヴァンゲリオン」の共起語が「映画」「アスカ」「ミサト」「初号機」である場合、両者の傾向が類似しているので、「エヴァ」と「エヴァンゲリオン」が言い替えなのではないかと推測できます。実際には共起率をベクトルとして両者のコサインをとって類似度とみなします。このような共起類似度と共起率をもとに関連語を選択するとかなりうまい具合にいくようです。

その他の関連情報の作り方もかなり簡単です。関連日記は、関連語を多く含む日記です。単にキーワードを含んだ日記を検索したものよりも、対象の話題について深く論じていることが多いので、関連日記を1個か2個読むだけでその話題がどういうものかを高い確率で把握できるでしょう。例文は、そのキーワードおよび関連語からなる文章を抽出したものです。関連コミュニティは、関連日記を書いている人が共通して入っているコミュニティです。それらは対象の言葉についてより深く知りたい場合にポインタとして役立つはずです。関連URLは、関連日記の中で頻出するURLを集計したものです。ニュースリソースとして関連URLを見ていただけると役立つでしょう。

まとめ

キーワード関連情報をインディーズ機能としてリリースしました。キーワードランキングの楽しさを倍加させ、mixi日記のコンテンツとしての価値を再発見できる機能であると自負しています。ただ、夜なべも休出も厭わず自分のOSS製品のメンテもろくにせずに開発に没頭した私としては可愛くってしょうがないこの機能ですが、ユーザの皆さんにどれだけうけるかどうかは正直見当がつきません。社内で提案しても淘汰されていたものですが、このたびインディーズ機能として皆様に御披露目できて感無量です。

インディーズ機能は今後も第2弾、第3弾と続けていく予定ですので、どうぞご期待ください。こちらの紹介ページからインディーズ機能についての日記を書くことができますので、ご意見ご要望などいただけると幸甚です。