こんにちは、某Perl界隈のIRCチャンネルでPythonがマイブーム的なKY誤爆をしてしまったtmaesakaです。

先日、以前から興味のあったGoogle App EngineMemcache APIについて少し調べ、こちらに英文で報告したのですが、今日は日本語で要約したまとめを紹介します。

まず軽く前置きですがGoogle App Engine (GAE)とは、Googleが提供しているウェブアプリケーションをGoogleのインフラ上でスケーリングや冗長化など、ある程度のノウハウや資金を要求される面倒な事を気にせずに運営できるプラットフォームです。つまり、典型的なPaaSの例であり、サービスの運営コストをelastic(伸縮)にします。昨今バズワード化しつつあるクラウドコンピューティングの一種でもあります。

GAEのインフラはGoogleより提供されているAPIセットを用いて利用します。その中にはon memory cacheがMemcache APIという形で提供されています。インターフェイスはmemcachedと同様Key/Valueベースのもので、アプリケーションのトータルパフォーマンス向上に役立つAPIです。

このAPIはmemcachedを連想させられるネーミングですが、実際にGAEのドキュメントを読むと、こう記述されています:

The Memcache API has similar features to and is compatible with memcached by Danga Interactive.

つまりmemcached互換で、なおかつ似ていると書かれていているだけです。普通はこの辺で納得するのでしょうが、私はこういった文を見ると調べたくなる性格なので、ちょっと深入りしてみました。

実際にMemcache APIを使うのは簡単で、’memcache’ モジュールをGAEパッケージからインポートします:

from google.appengine.api import memcache

あとはアプリケーションから必要に応じて、各種APIメソッドをコールするだけです。

プロトコル違反なKeyでセットしてみる

memcachedのASCIIプロトコルでは、Keyの長さは250バイトまでという制限があります。それ以上の長さのKeyを送信するとmemcachedはERRORを返します。では、GAEはどうでしょう?

遊び気分で300バイトのKeyで適当な値をSetしようとする以下のコードを走らせてみたところ:

from google.appengine.api import memcache
 
memcache.flush_all()
test_key = 'x' * 300
 
if not memcache.set(test_key, 'some_val'):
    print 'Failed to set'
    quit()
 
print "Looks like we're good = " + memcache.get(test_key)

以下のエラーがローカルのApp Serverから返ってきました:

Keys may not be more than 250 bytes in length, received 300 bytes

あらあら、上記のエラーだけを見ると明らかにmemcachedっぽい動作ですが、memcachedに合わせているだけかもしれません。Keyの長さ制限はドキュメントに記述されていないので、このエラーに遭遇したら驚くデベロッパーもいるかもしれませんね(レアなケースですが)。

さて、次はもっとマジメに独特な情報で比較しましょう。

メモリ消費量で比較してみる

memcachedではサーバインスタンスがどれだけのデータ(総バイト数)をキャッシュしているかをstatsコマンドで容易に取得する事が可能です。同様に得られる情報は制限されるものの、Memcache APIでも同じ事が可能です:

from google.appengine.api import memcache
 
stats = memcache.get_stats()
if stats: print stats['bytes']

ここでトリビアですが、memcachedから取得できるキャッシュサイズは純粋に全てのkeyとvalueを合計した値ではなく、各レコードのオーバヘッド(item構造体のサイズ)を加算した値です。

この値を実際にGoogle上にデプロイしてあるアプリが返す結果と比較してみました:

1 x 128 byte value with a 5 byte key
Memcache API: 133 bytes
memcached-1.2.6: 184 bytes

64 x 128 byte values with 5 byte keys
Memcache API: 8512 bytes
memcached-1.2.6: 11776 bytes

128 x 128 byte values with 5 byte keys
Memcache API: 17024 bytes
memcached-1.2.6: 23552 bytes

なんとGAEは一切オーバヘッドを報告しません。これでMemcache APIのバックエンドに様々な可能性が広がりましたね。例えばネットワーク越しに分散されているGoogle Sparse Hashかもしれないし、アプリケーションのキャッシュマネージメントの事情でstats情報を独立した仕組みで保持しているのかもしれません。次のセクションで分散やマネージメントに関する推理を紹介します。

運用を考えてみる

特定のアプリケーションに関するキャッシュ情報の取得」は言葉にすると簡単に聞こえるものの、実際は簡単ではありません。まず考えなくてはならない事は、アプリケーションがどの様にキャッシュスペースを与えられているのかという点です。

例えばアプリケーションに対して必要に応じた数の専用インスタンスを用意するか、「専用」という概念を捨て、Keyに対しApplication Identifierをappend/prependして、他アプリケーションとキャッシュプールを共有するといったモデルです。どちらのモデルを採用するにしても、キャッシュのstatsという概念はインスタンス毎に存在するものなので、「このアプリケーションがxxx」という情報の管理と保持には独立した仕組みが必要と考えられます。つまりアプリケーション毎にindexが必要だという事です、例えばInverted Indexあたりのデータストラクチャ(appid -> stats_postings)で保持するなど。

上記の様な工夫を行わなければ、statsリクエストに対して毎回、ルックアップと演算がオンザフライで発生する事になり、低効率なシステムとなります。したがって下の層でmemcachedを使っていたとしても、統計管理が独立していれば、オーバヘッド抜きの純粋な値が保持される可能性もあります。GAEが保持と管理が楽な情報だけをstatsインターフェイスで提供している説明にもなります。

  • hits
  • misses
  • byte_hits
  • items
  • bytes
  • oldest_item_age

さいごに

Google App Engineで遊んでいて感動した事は、ドキュメンテーション(英語のやつ)がとてつもなく解り易い事です。あまり賢くなくて挫折屋な私でも、すぐに自立して簡単なコードが書けるレベルまでいけたほどです。

プログラミング言語面では現状、Pythonしかサポートされていませんが、もし今後いろいろな言語、特にRubyあたり(私は書けませんが)が対応されたら実に凄いプロダクトになるんじゃないかな〜、と思いました。Perl対応も完了したらかなり盛り上がるかも。

会社にも家にもグル~ミ~グッズが置いてある、グル~ミ~好きのmilanoです。
ミクコレも当然のようにグル~ミ~です。
こんにちは。

このたび、mixi OpenIDコンテストというものを始めました。
mixi OpenIDといえばプラットフォーム開発チームということで、私がエンジニアブログでも紹介させてもらいます。

mixi OpenIDコンテストは学生の方を対象にしたコンテストで、mixi OpenIDを使用した新しくて楽しいサービスを公募し、私たちも気づいていないソーシャルグラフの新しい活用方法を提案していただこうというものです。

審査員はエンジニアブログでもおなじみ、弊社技術顧問の小山浩之のほか、なんとmixi公認ユーザーの高橋名人も!
見事選考を勝ち抜けば、高橋名人にプレゼンする機会に恵まれるわけです。
楽しそうですね。
果たして高橋名人はどんなサービスに興味を示すのでしょうか。

さらに「Software Design」や「WEB+DB PRESS」などの雑誌で有名な技術評論社の馮富久さんも加わっていただいています。
馮さんには審査員賞として技術評論社の雑誌の年間購読権も提供していただきました。

二次選考後、最優秀賞に輝いたグループには15インチMacBook Proを1台プレゼントします。
このMacBook Proを使って、サービスをさらに進化させていってもらえたらと思います。

サービスの公開に必要となるWebサーバーはこちらで用意することも考えているので、アイデアとそれを実装する技術さえあればコンテストに参加することができるので、たくさんの学生のみなさまの応募をお待ちしています。

#エンジニアブログなのにいつもコードとか掲載しなくてすみません。

openid_contest.jpg
■コンテスト概要
【応募方法・選考の流れ】

12月17日(水)18:00まで:応募締め切り
Webサービスの概要を企画書にし、コンテスト運営係 openid@mixi.co.jp 宛にお送りください。
提出物:Webサービス企画書(PDF形式)、応募者・グループの氏名・学校名・学年、連絡先となる電話番号・メールアドレス、既にサンプル作品が完成している場合は、WebサービスのURL

12月24日(水):一次選考結果発表
応募者もしくは代表応募者のメールアドレスにご連絡いたします。

2009年1月13日(火):一次選考通過者プレゼンテーション
株式会社ミクシィのオフィスにて、一次選考を通過した応募者・グループによる企画内容のプレゼンテーションを行って頂きます。審査員がアドバイザーとなり、今後の企画の実現およびブラッシュアップのサポートをいたします。

2009年2月13日(金):一次選考通過者最終プレゼンテーション・表彰
株式会社ミクシィのオフィスにて、一次選考を通過した応募者・グループによる最終プレゼンテーションを行って頂きます。また、最優秀賞、審査員賞の発表も行います。

【応募資格・条件】
1.応募者もしくは応募グループ(最大5名)全員が学生であること
2.mixi OpenIDを活用したWebサービスであること
3.友人やグループで楽しめる、または役に立つWebサービスであること
4.公序良俗に反しないWebサービスであること

こんにちわ、skimuraです。

はじめに

mixi上に存在するコミュニティは、現実社会でのコミュニティの写像のようなものではないかと考られます。つまり図1に示したイメージのように、現実社会で盛り上がった事象は、mixi上のコミュニティでも盛り上がる仕組みになっているのではないかと考えています。今回インディーズ機能でリリースした「コミュニティランキング」はそのような仮説を数値的に明らかにするための試みの一つでもあります。本エントリーではこのような研究の過程で作成された「コミュニティランキング」について解説します。

community_image4.png
図1.現実社会とmixiのコミュニティ

コミュニティランキングとは

コミュニティランキングとは、各コミュニティにおけるトピックでのコメント増加数の推移などを指標としてコミュニティをランキング表示する機能です。コミュニティランキングには「総合ランキング」、「カテゴリー別ランキング」、「トピックランキング」の3種類のランキングがあります。

総合ランキング
すべてのカテゴリーを対象として、上位30位までのコミュニティを表示します。

カテゴリー別ランキング
mixiのコミュニティの6つのカテゴリーごとにランキングを作成し、それぞれ上位30位までのコミュニティを表示します。

トピックランキング
すべてのコミュニティを対象として、上位30位までのトピックを表示します。

コミュニティランキングを作成した背景

mixiにはコミュニティを探すためのツールとして、「コミュニティ検索」機能があります。コミュニティ検索は通常のウェブ検索エンジンと同様に、キーワードを含むコミュニティを検索結果として得ることができます。したがって、コミュニティ検索では何かしらの自発的に知りたいと思う事柄がなければ新たにコミュニティの情報を得ることがありません。

しかし、mixi上では日々、様々な話題のコミュニティが盛衰をみせています。盛り上がりをみせているようなコミュニティの中には自分では気がつかなかったような潜在的に興味のあるコミュニティや、情報としての価値が高いコミュニティがあります。たとえば、mixiのコミュニティではアーティストのアルバム発売や、コンサートのチケット販売開始などが始まると、それに関するコミュニティのトピックでのコメントが爆発的に増加したりします。しかし、現状の機能ではこのようなコミュニティを発見するのは困難です。これらの情報を可視化し、提供することでmixiをもっと楽しめるのではないかと考えました。

コミュニティランキングの実装

このような盛り上がりを示す仕組みとして、コミュニティをランキング表示する機能をリリースすることとなりました。しかし、何を指標としてランキングを作成するかは単純には決められません。たとえば「参加人数の増減を指標としてランキングを作成してみてはどうか」とか、「トピックの増加数で盛り上がりを見たらどうか」とか、様々な要素を指標としてランキングを作成することができます。このような試行錯誤をした結果、今回はコミュニティのトピックのコメントの増加の推移をメインの指標として用いることにしました。ランキングの指標に関しては、話題になっているコミュニティを上位として抽出できるように、これからもパラメータ調整や新たに指標の要素を追加したりして精度向上に努めなければなりません。

コミュニティランキングの考察

コミュニティランキングは、話題になったニュース、有名人の誕生日、スポーツなど、世の中でも関心が高まっている事柄に関するコミュニティが上位になりやすい傾向にあります。例として2008年11月13日のランキング結果上位3位の例を示します(図2参照)。1位はJリーグクラブチーム、ガンバ大阪に関するコミュニティです。ガンバ大阪は前日12日にアジア・チャンピオンリーグで初のアジアでのクラブ1位になったためコミュニティでの発言が活発になったためだと考えられます。2位には歌手の倖田來未さんに関するコミュニティがランクインしています。11月13日は倖田來未さんの誕生日であるため、コミュニティの発言が活発になったためだと考えられます。このようにコミュニティは何か話題になった事柄の情報を調べるにも役立ちます。それぞれのコミュニティでは話題になった事柄に関して議論されていることが多いので、ニュースとはまた違った質の情報が得られます。今日はどんな日なのか、という大雑把な情報を短時間で収集するのにも便利かと思います。

community_ranking_1113_modify.png
図2.コミュニティランキングの具体例

コミュニティランキングの仕組みに関して

コミュニティランキングのシステム的な話をしたいと思います。コミュニティランキングではmixi上にある約200万以上ものコミュニティのトピックコメント数をカウントしています。このような処理のように、ポイントの重みづけをする時などに、コミュニティIDをKeyとしてデータを何度も引くというような処理がよくあります。このようなディスクアクセスが大量に発生するような処理は、MySQLなどのRDBMSからデータを直接扱うと処理速度が遅くなったり、DBに負荷がかかります。したがって、必要なデータをRDBMSから一度dumpし、ローカルのDBMに保存して使いまわしています。DBMはTokyo Cabinetを用います(図3参照)。Tokyo Cabinetを用いれば、データがメモリ上に乗る容量であればオンメモリで利用できるため高速に処理できますし、Disk I/Oも低減できます。

dbm1.png
図3. コミュニティランキングでTokyo Cabinetを使用箇所の概要図

今回はTokyo Cabinetのhashデータベースを利用しています。hashデータベースを選択した理由は様々なkeyにランダムにアクセスする可能性が高いためです。文末に今回のサービスのランキングデータをTokyo Cabinetに書き込み、読み込みをしている処理のサンプルコード(Perl)を記述しておきます。大量のレコード数がある時には、DBMの設計時にチューニングを施したほうが高速に処理できます。しかし、たいていはサンプルのようにとてもシンプルなコードで高速にデータの読み書きが可能ですので、同じような処理をする場合はぜひご利用してみてはいかかがでしょうか。

まとめ

今回は、インディーズ機能でリリースした「コミュニティランキング」機能に関して紹介しました。本機能はmixiでのコミュニティの盛り上がりを可視化する施策として初めての機能となります。
mixiには検索ではなかなか発見できないようなおもしろい情報がたくさんあります。今後もmixiをより楽しく、便利に使えるようになる機能を開発できるよう努めていきたいと思います。

Tokyo Cabinetを使ったサンプルコード (Perl)

- Tokyo Cabinetのインスタンスを作成するためのサブルーチン

sub open_dbm {
    my $arg      = shift;
    my $dbm_path = $arg->{path};
    my $mode     = $arg->{omode};
    return if !$dbm_path || !$mode;
    my $hdb      = TokyoCabinet::HDB->new();
    if ($mode eq 'truncate') {
        # 新しくDBMを作るモード : すでにDBMが存在したら上書きされます
        $hdb->open($dbm_path, $hdb->OWRITER | $hdb->OCREAT | $hdb->OTRUNC)
            or die "cant open dbm: $dbm_path";
    } elsif ($mode eq 'write') {
        # DBMを追加書き込みするモード
        $hdb->open($dbm_path, $hdb->OWRITER | $hdb->OCREAT)
            or die "cant open dbm: $dbm_path";
    } elsif ($mode eq 'read') {
        # 読み込みのモード : read onlyで書き込みはできません
        $hdb->open($dbm_path, $hdb->OREADER) 
            or die "cant open dbm: $dbm_path";
    } else {
        die "error: open_dbm() mode: truncate || write || read";
    }
    $hdb;
}

- Tokyo Cabinetを使ってデータの読み書きをする処理

 
#!/usr/bin/perl
 
use strict;
use warnings;
use TokyoCabinet;
 
# ランキングなどのデータを作成する(これはdummyデータです)
# key:id  val:point
my %ranking_data = (
    1 => 10,
    2 => 5,
    3 => 15,
    4 => 1,
    5 => 22,
);
 
# Tokyo Cabinetのdbmインスタンスを作成する
my $hdb = open_dbm({
    path  => 'ranking.hdb', 
    omode => 'truncate',
}) or die "error: cant open dbm";
 
# ランキングデータをTokyo Cabinetに保存する
while (my ($key, $val) = each %ranking_data) {
    $hdb->put($key, $val);
}
$hdb->close();
 
# Tokyo Cabinetのdbmをオープンする
$hdb = open_dbm({
    path  => 'ranking.hdb',
    omode => 'read',
}) or die "error: cant oepn dbm";
 
# dbmのデータをイテレーションする
$hdb->iterinit();
while (my $key = $hdb->iternext()) {
    my $val = $hdb->get($key);
 
    # MySQLにinsertするなどの処理を書く
 
    print "[finish] id:$key val:$val\n";
}
$hdb->close();
 
__END__

はじめまして。めっきり寒くなってきたので短パン出社を諦めた oinuma です。求人情報サイト Find Job ! の開発や運用を担当しています。

私はいつも livedoor Reader を利用しているのですが、今更ながら登録数ランキングがAutoPagerize / LDRizeに対応しましたのエントリに触発されて、Find Job !の求人情報検索結果画面を AutoPagerize と LDRize に対応させてみました。今回はそのときに学んだことをサラサラっと書きたいと思います。

AutoPagerizeとLDRizeについて

AutoPagerize は現在見ているページの一番下に来たら、次のページを自動的にロードして現在のページに表示してくれるというものです。これのおかげでマウスで「次のページ」のようなリンクをクリックする必要がなくなります。

LDRize はキーボードの”j”, “k”を使うことで「次のデータ」「前のデータ」に進んだり、”p”を押して今見ているデータにピンをつけたりすることができます。

  • ひたすら “j” でページを読んでいき
  • “p” で気になったコンテンツにピンをつけておいて
  • 一通り読み終わったら “o”

こうすることでピンをつけていたものをまとめて別のタブで開いてくれるのでとても便利です。

上はFind Job !で “p” を押してピンをつけた状態です。

まとめると、この2つを利用することでマウスを使わずにキーボードだけで快適にブラウジングできるようになります。(おかげで自分は肩こりが軽くなりました)

AutoPagerizeに対応させる方法

サイトを AutoPagerize に対応させるには、SITEINFO というものを書く必要があります。SITEINFO とは、次のページへのリンクがどれかを示す nextLink と、ページの中の本文部分を示す pageElement を XPath で記述するもので、wedata で管理されています。

url:  ^https?://www\.find-job\.net/fj/(?:search|new)\.cgi
nextLink:  id("contents")//li[@class="foreward"]/a
pageElement:  id("contents")/div[contains(@class,"search_result_list02") or contains(@class,"clear_space")]
exampleUrl:  http://www.find-job.net/fj/search.cgi?shokushu=0

上は Find Job !の例ですが、このように書くとGreasemonkeyスクリプトである AutoPagerize がこの SITEINFO を取得して次のページを現在のページに展開してくれます。各項目の仕様についてはこちらにわかりやすく記載されているので、実際に SITEINFO を書く場合に役立つと思います。wedata は OpenID さえあれば編集できるようになっているので、自分がよく見るサイトが対応してなかったら追加しちゃいましょう!

ちなむと、Find Job !の検索結果画面は以前どなたかが対応してくださっていたのですが、8月にデザインをリニューアルしたために動かなくなっていたので今回はこちらで対応させていただきました。

LDRizeに対応させる方法

LDRize も AutoPagerize と同様に SITEINFO がWeb上で管理されており、その仕様についてはこちらで説明されています。また、 SITEINFO も Wiki から編集することが可能です。私は XPath にあまり自信がなかったので、まずはローカルのスクリプトを修正して正しい SITEINFO が書けたことを確認してから Wiki を更新しました。

スクリプトを編集するには、Firefox 上の右下のGreasemonkey のアイコンを右クリック→ユーザスクリプトの管理→LDRizeを選択→[編集]をクリックしてください。 そして31行目付近を

var SITEINFO = [ {
name:      'Find Job !',
domain:    '^https?://www\.find-job\.net/fj/(new|search)\.cgi.*',
paragraph: '//div[contains(@class,"search_result_list02")]',
link:      'div//strong/a',
view:      'div//strong/a/text()',
} ]

のように編集して LDRize の挙動を確認します。デバッグが終わったら、Wikiを更新した後ローカルのスクリプトは元に戻して、M-x LDRize::update-siteinfo を忘れずに実行しましょう。こうすることで現在開いている Firefox に更新された Wiki の SITEINFO がロードされます。

閑話休題 – microformats について

AutoPagerize も LDRize も microformats に対応しているので、自分が管理しているサイトであればHTMLを直してしまってもよいでしょう。AutoPagerize の SITEINFO との対応表を書くと以下のようになります。

SITEINFO microformats
nextLink 「次のページへ」のリンクの <a> タグに rel=”next” 属性を追加
pageElement ページのコンテンツとして切り出すノードのタグに class=”autopagerize_page_element” という属性を追加
insertBefore(*1) 読み込んだページから切り出したコンテンツを挿入する際に基準となるタグに class=”autopagerize_insert_before” 属性を追加

*1) insertBefore を省略した場合は pageElement(autopagerize_page_element) の直後が指定されたものとして扱われるので、たいていの場合は指定しなくてもOKです。

LDRize は hAtom 0.1 と xFolk RC1 に対応しているのでこんな感じでしょうか。

SITEINFO microformats(hAtom 0.1) xFolk RC1
paragraph ページのコンテンツとして切り出すノードのタグに class=”hentry” 属性を追加 class=”xfolkentry”を追加
link “v”で開くリンクのタグに rel=”bookmark” 属性を追加 class=”description”を追加
view “p”でピンを付けて右下に表示したい部分のタグに class=”entry-title” 属性を追加 class=”taggedlink”を追加

まとめ

AutoPagerize と LDRize はニッチなツールかもしれませんが、Webブラウジングをかつてないほど便利にしてくれる素晴らしいツールです。自分がよく見るサイトがまだ対応していなかったら、ちょっとした正規表現と XPath を書くだけで対応させることができるので、ぜひ皆さんも試してみてください。