息子が3ヶ月になり、日に日に出来る事が増えていくのを見て、親馬鹿度合いがますます増えているかぜぶろです。

さて、もう手に取って読んで頂いた方も多くいらっしゃると思いますが、技術評論社様からでているWEB+DB PRESSのVol.50から「大規模Webサービスの裏側――inside mixi’s backend」と題して、mixiのシステム運用についての連載をスタートしました!

mixiは2008年12月末で、ユーザー数1,630万人、月間ページビューはPCとモバイルを合わせ143億PVとなり、日本でも有数の規模を誇るサイトに成長しています。
このブロクでも何度か紹介してきていますが、mixiの成長とともに大規模・複雑化しているmixiのサービスを支えるシステムの大半は、サービス開始当初から現在までオープンソースのソフトウェアで構築され、運用が行なわれてきています。WEB+DB PRESSの連載ではmixiのシステム構築・運用のインフラ、アプリケーション両面に焦点を当てて紹介してく予定です。

連載1回目の内容は、
mixi Engineers’ Blog » ロングテールな画像配信 その1
mixi Engineers’ Blog » ロングテールな画像配信 その2 – 3,000万の画像を配信するシステム
でも、紹介をした大規模な画像配信についてになります。

mixiの画像配信は、画像の種類や量、トラフィックのパターンによっていくつかの方法を使い分けています。記事では配信方法を使い分ける理由とそれぞれのポイント、また多くの画像を配信する際のWebサーバの設定のポイントについて説明させて頂きました。

Vol.50表紙

WEB+DB PRESS Vol.50の目次は技術評論社様のサイトで確認できます
http://gihyo.jp/magazine/wdpress/archive/2009/vol50

ぜひ手に取って読んで頂けたら幸いです。

iPhoneゲームの買い過ぎでついにアプリが7ページ目に突入してしまった bonar こと中野恭兵です。今のお気に入りは手軽に遊べる”frenzic“と本格派ファンタジーパズル”Aurora Feint” 。最高です。

普段はアプリケーション開発グループ ミュージック開発チームに所属していまして、仕事中は常に mixi Radio 付けっぱなしなわけですが(マイブームは”Monica Uranglass(音が出ます)“)、やっぱりコンピューターがある以上、聴くだけでなく自分でも作ってみたいと思うものです。

僕自身弾ける楽器が何もなく、音楽的な教養も無いのですが、まずは最初の一歩を踏み出したいと思い少し調べてみました。

音とは何か

音楽はいろんな音の複雑な合成物なので、音とは何かという部分から考える必要があります。 ご存知の通り、音とは空気の振動です。振動とはつまり一定の周期を持った規則的な波で、 それが鼓膜に伝わり音として知覚されます。この時その振動がどういう波形をしているか(滑らかに 円を描くsin波か、角張った矩形波か等)が音の種類を決め、1秒間に何回その波形が反復 されるかで音の高低が決まります。この「1秒間に何回その波動が起こるか」を“周波数”と 呼び、Hz(ヘルツ)という単位で表現します。1秒間に220回同じ波形が繰り返す音は 220Hzの音である、という言い方をします。

音のデジタル保存

自分でサウンドファイルを作成する際には、どうやって音がデジタル化/記録されるかを知る 必要があります。

本来なめらかなアナログ波形である音をデジタルデータとして保存するという行為は、 アナログな波形を特定の経過時間における音圧スナップショットの連続として記録して行く作業です。 ありふれた例えですが、波形の曲線を棒グラフに置き換えて行くと考えるとイメージしやすいかもしれません。

CDをリッピングする際やオーディオフォーマットを変換する際に、「サンプリングレート」や 「ビットレート」といった値を設定したりします。これらは漠然と、上げれば上げる程音がよくなる 数字として認識されがちですが、実は上記のようなアナログ波形をデジタル化する際のパラーメータです。 棒グラフの例で言えば横軸の棒の数がサンプリングレート(1秒間に何回スナップショットを取るか)で、 各棒グラフの縦の目盛りの細かさがビットレート(音圧をどれくらい細かく計測するか)です。これらが 細かくなればなる程 連続した棒グラフは元の曲線に近くなめらかになるので、再現度が高くなり、 高い音質として感じられるのです。

例)サンプリングレート44.1KHz, ビットレート16bit
= 1秒間に4万4100回スナップショットを取り、その際の音圧を65536段階で記録する

sin波を書く

それでは上記のことを確認するために、引数で受け取った周波数のsin波を.wavファイルとして書き出すプログラムを書いてみましょう。以下がコードです。

http://gist.github.com/97992

import java.lang.Math; 
import java.util.Vector; 
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.*; 
 
public class SinWave { 
    private static final float SAMPLE_RATE = 44100.0f;
    private static final int   SAMPLE_SIZE = 16;
    private static final int   CHANNEL     = 2;
    private static final int   FRAME_SIZE  = 2;
    private static final float FRAME_RATE  = 44100.0f;
 
    private static final int VOLUME = 50;
 
    public static void main (final String[] arg) 
        throws IOException {
 
        Vector<Float> freqs = new Vector<Float>();
        for (int i = 0; i < arg.length; i++) {
            freqs.add(Float.valueOf(arg[i]));
        }
 
        int buffsize = ((int)SAMPLE_RATE * CHANNEL);
        byte[] buff = new byte[(buffsize * freqs.size())];
        int cursor = 0;
 
        for (Float freq : freqs) {
            // SAMPLE_RATE 内で target_hz 個の波形を入れる必要が
            // あるため、一回の波形に割り当てるサンプル数は 
            // (SAMPLE_RATE / target_hz) になる。
            float target_freq = freq.floatValue();
            int samples_per_round = (int)(SAMPLE_RATE / target_freq);
 
            // target_freq 回波形を書く
            for (int i = 0; i < (int)target_freq; i++) {
                // 1回の波形を samples_per_round の点に分けて書く
                for (int x = 0; x < samples_per_round; x++) {
                    float progress = ((float)x / samples_per_round);
                    double radian  = progress * (2 * Math.PI);
                    byte y = (byte)(Math.sin(radian) * VOLUME); 
                    buff[cursor++] = y; // Left
                    buff[cursor++] = y; // Right
                }
            }
        }
 
        // WAVE ファイルとして出力
        AudioFormat fmt = new AudioFormat( 
            // AudioFormat.Encoding encoding
            AudioFormat.Encoding.PCM_SIGNED, 
            SAMPLE_RATE, // sampleRate 
            SAMPLE_SIZE, // sampleSizeInBits 
            CHANNEL,     // channels 
            FRAME_SIZE,  // frameSize 
            FRAME_RATE,  // frameRate 
            true         //int bigEndian 
        );
        AudioInputStream audio_stream = new AudioInputStream(
            new ByteArrayInputStream(buff), fmt, cursor);
        File outfile = new File("sinwave.wav");
        AudioSystem.write(audio_stream
            , AudioFileFormat.Type.WAVE, outfile);
    }
}

これを以下の引数で実行すると sinwave.wav という.wavファイルが作成されます。

$ java SinWave 220 440 880

めんどくさい方はこちらからどうぞ。
sinwave_220_440_880
だんだんと音が高くなっていくのがわかりますね。

WAVEファイルを読む

では出来た .wav ファイルの中身を見てみましょう。

$ xxd -c 8 sinwave.wav | head -n 20
0000000: 5249 4646 a40e 0800  RIFF....
0000008: 5741 5645 666d 7420  WAVEfmt
0000010: 1000 0000 0100 0200  ........
0000018: 44ac 0000 10b1 0200  D.......
0000020: 0400 1000 6461 7461  ....data
0000028: 800e 0800 0000 0101  ........
0000030: 0303 0404 0606 0707  ........
0000038: 0909 0a0a 0c0c 0d0d  ........
0000040: 0f0f 1010 1212 1313  ........
0000048: 1515 1616 1818 1919  ........
0000050: 1a1a 1c1c 1d1d 1e1e  ........
0000058: 1f1f 2121 2222 2323  ..!!""##
0000060: 2424 2525 2626 2727  $$%%&&''
0000068: 2828 2929 2a2a 2b2b  (())**++
0000070: 2b2b 2c2c 2d2d 2d2d  ++,,----
0000078: 2e2e 2f2f 2f2f 3030  ..////00
0000080: 3030 3030 3131 3131  00001111
0000088: 3131 3131 3131 3131  11111111
0000090: 3232 3131 3131 3131  22111111
0000098: 3131 3131 3131 3030  11111100

2b までのヘッダ部分以降は 2 byte 1セットの音圧値がひたすら続いていいて、他には何もありません。また、数値を眺めるだけでもそれが滑らかな曲線を描こうとしていることがわかります。音というのはつかみ所の無いものですが、このように単純な波のスナップショットの連続であることがわかります。感動的ですね。

周波数比の世界

上記の.wavファイル生成では唐突に3つの周波数を出しましたが、220 Hz は「ラ」の音です。調律師の機嫌で音程が変わっては大変なので、この音はこの周波数、というのが決まっています。その後にそれよりも高い 440 Hz, 880 Hz の音が続きます。音を聞いて気づかれた方もいるかもしれませんが、440 Hz は1オクターブ高い「ラ」、880 Hz はさらに1オクターブ高い「ラ」になります。通常楽器の調律等はこの 440Hzのラを基準にして行われているようです。

つまり、周波数が倍になると(波形の周期が半分になると)、1オクターブ高い音になります。人間の耳には「あ、1個あがったな」くらいにしかわかりませんが、実際の周波数は音が高くなればなるほど間隔が開いていることになります。ピアノの鍵盤を周波数の数直線だと考えると、それは等差数列ではなく等比数列だったんですね。

ドレミを作ろう!

音の周波数比が一定だと仮定すると、オクターブの上げ下げだけでなく、同じオクターブ内での各音の周波数比も一定のはずです。ドレミファソラシド を CDEFGAB と置き換えると、1オクターブは C C# D D# E F F# G G# A A# B という12個の音で構成されています(D# と Eフラット等の異名同音を除く)。そのそれぞれの比が一定であるということは、C : C# の周波数比を12乗すると2になる(オクターブがあがると周波数が倍になる)というになります。2^(1/12) を計算すると以下の値になります。

$ perl -le 'print 10 ** ((log(2)/log(10))/12)'
1.0594630943593

この比と先ほどのラの周波数 220 Hz を元に各音の周波数を計算してみます。

A  | 220.000
A# | 233.082
B  | 246.942
C  | 261.626
C# | 277.183
D  | 293.665
D# | 311.127
E  | 329.628
F  | 349.228
F# | 369.994
G  | 391.995
G# | 415.305
A  | 440.000
A# | 466.164
B  | 493.883
C  | 523.251
C# | 554.365
D  | 587.330
D# | 622.254
E  | 659.255
F  | 698.456
F# | 739.989
G  | 783.991
G# | 830.609

ここから#の部分を抜いてドレミファソラシドを作ってみましょう。

$ java SinWave 261.626 293.665 329.628 349.228 391.995 440.000 493.883 523.251

.wavファイルはこちら
sin波でドレミファソラシド

おなじみのドレミになりましたね。これでどんな曲でも作れます。

音の調和と周波数比

ちなみに、隣り合った音の周波数比を 2^(1/12) にするというこういった調律(ドレミ -> 周波数 マッピング)を「平均律」といい、現在もっとも普及しています。
#昔は純正律等の別の音律が使われていました。

ちょっと回り道になりますが、C と G の周波数に注目してみます。ドとソですね。これはピアノ等で弾いてみると非常に美しく調和します。周波数は 261.626 と 391.995 です。よく見ると 周波数比が 3/2 に非常に近い事がわかります。このように周波数が簡単な比で表せる音は同時に鳴らした時によく調和することが知られています。

しかし、3/2 に近いものの、ぴったり 3/2 ではありません。実はこれが平均律の弱点なのです。音の間隔が一定じゃないと転調がすごくやりづらくなるため、多少の誤差には目をつぶることにしたのです。実際にメリットの方が大きく、この調律が音楽を一気に合理化しました。ズレに関してもほぼ気にならないものです。

聞き比べてみましょう。

261.626 – 391.995 (平均律)
261.626 – 392.439 (正確な比)

違いがほとんどわかりませんね。ピアノ等の様に打鍵した瞬間から音量が急速に落ちるような楽器ではなおさらわからないと思います。

C の 3/2 は G ということを見てきましたが、もっと他の比に関してはどうでしょうか。例えば C の 4/3 は何かと見て行くと、

$ perl -le 'print ((261.626/3)*4)'
348.834666666667

となり、F の周波数 349.228 と非常に近い事がわかります。実際にピアノ等で弾いてみるとわかりますがC と F もとてもよく調和します。

周波数間が単純な比で表せる時にその音が調和する、ということであればドレミの点を基準にする必要はないはずです。ためしに 240 Hz というドレミには該当する音がない周波数で試してみましょう。

480Hz 240Hz 120Hz (前後のオクターブ)
240Hz 320Hz (周波数比 4/3)
240Hz 360Hz (周波数比 3/2)

妙な感じですが、まとまった音として聞こえますね。

アナログ音楽の世界へ

上記以外にも音色を決める倍音構成や様々な協和に関するトピック等おもしろい話がたくさんあるのですが、この辺は僕自信よく理解出来ていないところがあるので次回に是非。

音楽といわれると多くの人は五線譜に書かれた音符をイメージします。しかし実際には今まで見てきたように、音というのは空気の振動でしかなく、ドレミはその振動が生み出す無限の周波数の中の特定の点でしかありません。ド と ドのシャープ の間には無限の数の音があります。こういった今まで光のあたらなかった音や、それらが生み出す調和から、新しい音楽の可能性が見えるかもしれません。ドレミを全く無視した自分だけの音の世界をつくることだって出来ます。

僕も偉そうな事をいいながら何もわかってないのですが、音楽の世界は思った以上に数学的でエキサイティングな世界のようです。

株式会社ミクシィは、OpenSocial-Japanコミュニティと共に、OpenSocial Hackathonを5月15日に開催いたします。

OpenSocialは誕生から1年で6億人ものユーザを獲得するまでに成長を遂げました。mixiアプリのオープンβ版が4月8日から個人の開発者にも公開され、既に多くの開発者の方々に利用していただいております。

今回のOpenSocial Hackathonでは、OpenSocialを使ったmixiアプリ開発に興味をお持ちの方々を対象に、実際にmixi Platformを利用して、多くのユーザにリーチするmixiアプリの開発を体験して頂けます。mixiアプリ開発の中心はJavaScriptコードの記述となりますので、例えばAjaxによる開発経験をお持ちの方であれば、すぐに開発を始めることが可能です。Hackathonでは、いくつかのチームに分かれ、短時間でアプリケーションの開発を行います。

皆さまの開発をサポートするために、Google API ExpertもHackathonに参加し、皆さまの質問にお答えいたします。

mixiアプリ/OpenSocialアプリケーションの開発経験を得る貴重な機会をぜひご活用ください。

[参加方法] 以下のURLよりお申し込みください。 ※先着順となります。
https://mixi.jp/entry_hackathon.pl

========================================
主催: 株式会社ミクシィ、OpenSocial-Japanコミュニティ
日時: 2009年5月15日(金) 10:00~18:00 (開場:09:30)
場所: 株式会社グーグル
人数: 30名
参加条件: OpenSocialアプリケーションの開発経験のある方
ノートパソコンの持参が可能な方

※参加者の方々は5月8日に事前ミーティングも行いますので、ご参加ください。
日時: 2009年5月8日(金) 18:30~20:30 (開場:18:00)
場所: 株式会社グーグル
内容: Hackathonでのチーム分け等の手続き、アイデアの意見交換
お問合せ: フォーム<http://mixi.jp/entry_platform.pl>よりお問合わせください
========================================

<注意事項>

  • Hackathonではチームでアプリケーションを作成し、コーディング終了後にアプリケーションをプレゼンして  いただきます。その際、コードなどがチームのメンバーや他の応募者に開示されますことを予めご了承ください。
  • 作成したアプリケーションはmixiなどのサイトにてご紹介させていただく場合があります。
  • 当日はチーム内での情報共有のためのツールとして、Google Docsを使用します。その際、他の応募者にお名前とメールアドレスが他の応募者に開示されることを予めご了承ください。
  • 当日はソースコードの共有のためのツールとして、Google Project Hostingをご利用いただけます。その際、Subversionを使用することになるため、何らかのSubversionクライアントを予めご用意ください。

※ OpenSocial-Japanとは・・・OpenSocialアプリケーション開発者をサポートするGoogle準公式コミュニティです。

サボっていた早朝ジョギング@駒沢公園を再開して2週間たち、やっと抜かれる数より抜く数の方が増えてきたmikioです。今回は、PerlやRubyのハッシュの代用としてTokyo Cabinetを使うことでメモリ使用量を激減させられることを説明します。

hashperfgraph

抽象データベースAPI

Tokyo Cabinetには抽象データベースという機構があり、先日、そのPerlとRubyのバインディングをリリースしました。それを使うと、各種言語のハッシュとほぼ同じような共通したインターフェイスで、以下のデータ構造を利用することができます。

  • オンメモリハッシュ:各種言語に標準のハッシュと同じく、メモリ上でkey/valueの関係を表現する。
  • オンメモリツリー:メモリ上の二分探索木としてkey/valueの関係を表現する。
  • ファイルハッシュ:いわゆるDBMとして、ファイル上でkey/valueの関係を表現する。
  • ファイルツリー:ファイル上にB+木としてkey/valueの関係を表現する。

各種言語のハッシュとほぼ同じように使えるということころがポイントで、ハッシュオブジェクトを作る部分だけを書き換えれば、既存のコードをほぼそのままTokyo Cabinet対応にできるのです。

例えばPerlの場合はこんな感じです。ハッシュにタイするところ以外のコードは全く同じですよね。

# 標準のハッシュ
my %hash;
$hash{"foo"} = "bar";                 # レコードを格納
printf("%s\n", $hash{"foo"});         # レコードを参照

# TCのオンメモリハッシュ
my %tcdb;
tie(%tcdb, "TokyoCabinet::ADB", "*"); # TCのオンメモリハッシュに連結
$tcdb{"foo"} = "bar";                 # レコードを格納
printf("%s\n", $tcdb{"foo"});         # レコードを参照

そしてRubyの場合はこんな感じです。クラス名とopenするところ以外のコードは全く同じですよね。

# 標準のハッシュ
hash = Hash.new
hash["foo"] = "bar"                   # レコードを格納
printf("%s\n", hash["foo"])           # レコードを参照

# TCのオンメモリハッシュ
tcdb = TokyoCabinet::ADB::new
tcdb.open("*")                        # TCのオンメモリハッシュに連結
tcdb["foo"] = "bar"                   # レコードを格納
printf("%s\n", tcdb["foo"])           # レコードを参照

性能評価

さて、そうするとメモリ使用量をどれだけ減らせるのかということです。100万件のレコードを格納した場合のメモリ使用量と処理時間を測ってみましょう。Perl(5.8.8)でテストコードを動かしたところ、以下の改善が確認できました。標準のハッシュに比べて、メモリ使用量がTCのオンメモリハッシュだと約61%、TCのオンメモリツリーだと約37%になることがわかります。処理時間に関しては157%ほどになっていますが、まあ許容範囲ですよね。ファイルハッシュやファイルツリーだとさらに省メモリになりますが、もちろん処理時間はその分遅くなります。

メモリ使用量 処理時間
Perl標準のハッシュ 162.402 MB 1.657 sec.
TC:オンメモリハッシュ 99.289 MB 2.602 sec.
TC:オンメモリツリー 61.105 MB 2.393 sec.
TC:ファイルハッシュ 4.156 MB 6.945 sec.
TC:ファイルツリー 6.812 MB 3.163 sec.

Ruby(1.8.6)でテストコードを動かしたところ、以下の改善が確認できました。標準のハッシュに比べて、メモリ使用量がTCのオンメモリハッシュだと約34%、TCのオンメモリツリーだと約18%になることがわかります。処理時間に関しても27%以下になっていて、大変お得です。ファイルハッシュやファイルツリーだとさらに省メモリになり、かつ処理時間も標準ハッシュと同じくらいになっています。

メモリ使用量 処理時間
Ruby標準のハッシュ 354.598 MB 9.254 sec.
TC:オンメモリハッシュ 122.590 MB 2.580 sec.
TC:オンメモリツリー 66.648 MB 2.369 sec.
TC:ファイルハッシュ 4.512 MB 7.271 sec.
TC:ファイルツリー 7.625 MB 3.211 sec.

ということで、ハッシュにデータをたくさん詰め込むようなシーンでは、TCのオンメモリハッシュかオンメモリツリーを使うと幸せになれると思います。文字列以外のクラスのオブジェクトを格納する場合にはデータをシリアライズしてから扱わないといけないという制限はありますが、シリアライズしたとしても大抵の場合メモリ効率は改善することでしょう。さらに、ハッシュが大きくなりすぎて実メモリに乗らなくなってスワップ起きまくりで激烈遅くなっちゃうようなシーンでは、ファイルハッシュやファイルツリーを使うと性能を改善することができます。もちろんプロセスが終了してもデータを永続化させる目的でもファイルハッシュやファイルツリーは活躍します。

カラクリ

なぜTCだとメモリ使用量を節約できるのかというと、データ構造の内部に現れるノードの領域として、メタデータとキーエンティティと値エンティティを連続した領域に押し込んでいるからです。一方でPerlでは、元のレコードのキーエンティティと値エンティティの元来のオブジェクト(スカラ)そのものをコピーして保持するとともに、それとは別途にメタデータを保持します。Rubyでは、キーエンティティと値エンティティのオブジェクトのリファレンスを持つことでガベージコレクタによる回収を抑制するとともに、それとは別途にメタデータを保持します。つまり、TCでは格納したオブジェクトの元来のあり方を忘れてしまう分、そしてメタデータとエンティティを連結して扱う分、メモリ使用量を削減できるのです。

処理時間に関しては、内部でオブジェクトの領域をmallocおよびmemcpyで複製する分、遅くなってしまいます。その辺の理屈はSTLとの比較の記事でも述べました。

オンメモリハッシュよりもオンメモリツリーの方がメモリ使用量が少ないのはなぜでしょう。TCのソースコードに答えが書いてあります。個々のレコードに相当するノードの構造体のサイズがツリーの方が小さいからです。

typedef struct _TCMAPREC {    /* type of structure for an element of a map */
  int ksiz;                   /* size of the region of the key */
  int vsiz;                   /* size of the region of the value */
  unsigned int hash;          /* second hash value */
  struct _TCMAPREC *left;     /* pointer to the left child */
  struct _TCMAPREC *right;    /* pointer to the right child */
  struct _TCMAPREC *prev;     /* pointer to the previous element */
  struct _TCMAPREC *next;     /* pointer to the next element */
} TCMAPREC;

typedef struct _TCTREEREC {   /* type of structure for an element of a tree */
  int ksiz;                   /* size of the region of the key */
  int vsiz;                   /* size of the region of the value */
  struct _TCTREEREC *left;    /* pointer to the left child */
  struct _TCTREEREC *right;   /* pointer to the right child */
} TCTREEREC;

オンメモリハッシュのハッシュバケットのオーバーヘッドも多少影響してますが、それよりは内部ノードのオーバーヘッドの方が支配的です。マップの方はLRU削除キャッシュとして利用するために格納順の双方向リンクリストを内包しているのでオーバーヘッドが大きくなっています。ツリーの方はスプレー木というちょびっと癖のあるアルゴリズムで実装されていて、他の平衡木のように最悪計算量を保証しない代わりに、アクセスパターンに偏りがある場合に高い性能を出すようになっています。汎用コンテナとしてスプレー木なんて普通は採用しないみたいですが、主に全文検索のインデクサのキャッシュとして順序木を利用したかったという事情でこうなっています。

まとめ

Tokyo CabinetのPerlバインディングとRubyバインディングでサポートされた抽象データベースAPIを使うと、標準のハッシュを使っているコードをほとんど変更せずに、メモリ使用量を激減させることができます。これマジで便利なので、ぜひ使ってみてください。Java版とLua版は…、気が向いたら作ります。

はじめまして。ミュージック開発チームのtomと申します。名前はtomですが純日本人です。(本名も”tom”でちゃんと漢字があります。)

今回は、”オンラインコーヒーメーカー萌香たん”を作ったりできることでおなじみのODFをちょっとお得に使うための、「ODF繰り越し制度」の紹介と、その制度を利用して私が作っているTokyo CabinetのHaskellバインディングを紹介させていただければと思います。

ODF繰り越し制度

弊社のエンジニアは、ODF(One Day Free)という制度を使って、毎週金曜日に自分が好きなことに取り組むことができます。このODF制度、四半期ごとに実施する or しないを申告するのですが、このときになんと「繰り越す」という選択肢が用意されているのです。

普通のODFでは、四半期(3ヶ月)の間、週1日を自由な時間として確保することができます。とは言っても忙しい時期は毎週毎週時間を確保することが難しかったりします。また、1日好きなことをやっても、続きをやるまでに1週間も間があいてしまうと、集中が途切れてしまって、熱が冷めてしまったりしがちです。

そこで、ODFを「繰り越す」ということをすると、次の四半期の中のどこか1ヶ月をまるまる自分の自由な時間にすることができるのです。(3ヶ月間毎週1日を積み立てるとおおよそ12日なので、それを2回分で24日ぐらい確保できる。)One Day Freeならぬ、One Month Freeです。しかも、その1ヶ月の間は自分の好きなことに集中できるので、本当に好きなことを集中してやるには最適です。

この制度、まだまだ実験的な試みで実施者の数は多くないですが私も先月実施させていただき、1ヶ月という時間を自分の好きなことに費やすことができました。

Tokyo CabinetのHaskellバインディング

と、いうわけで作ったのがTokyo Cabinet(以下TC)のHaskellバインディングです。

あらかじめ断っておきますが、以下で紹介するライブラリは、TCの作者であるmikioさんになんの断りもなく、私個人が勝手に作ったものです。本家TCが正式にHaskellバインディングをサポートしたという話ではありません。Haskellバインディングに関する質問や不具合・バグ報告はmikioさんではなく、私の方までお願いいたします。(tom.lpsd < at > gmail.com)

インストール

PerlにはCPANという素晴らしい仕組みがありますが、Haskellにもそれに近い仕組みが用意されています。

HackageDB:   http://hackage.haskell.org/packages/hackage.html

上記サイトに、有用なライブラリが集められています。これらは、cabal-installというパッケージに含まれているcabalコマンドを使って簡単にインストールすることができます。TCのバインディングは、tokyocabinet-haskellというパッケージ名で登録してありますので、

$ cabal update
$ cabal install tokyocabinet-haskell

とコマンドを叩けばインストールできるはずです。TCのヘッダやライブラリを特殊な場所にインストールしている場合は、

$ cabal install tokyocabinet-haskell --extra-lib-dirs=/path/to/tc/lib --extra-include-dirs=/path/to/tc/include

としてください。

使い方

TCに関する操作は、ほとんどの場合、入出力や状態変化を伴いますので、基本的にIOモナドの中で行います。
関数名やデータ型は基本的にTCのパッケージに含まれるtokyocabinet.idlに準拠しているはずです。(本家に追いついていないところも多々ありますが。。)
ハッシュデータベースをいじるコード例は以下のようになります。

import Control.Monad (unless)
import Database.TokyoCabinet.HDB

main :: IO ()
main = do hdb <- new
          open hdb "casket.tch" [OWRITER, OCREAT] >>= err hdb
          put hdb "foo" "hop"  >>= err hdb
          put hdb "bar" "step" >>= err hdb
          put hdb "baz" "jump" >>= err hdb
          get hdb "foo" >>= maybe (error "get error") putStrLn
          iterinit hdb
          iternext hdb >>= maybe (error "iternext error") putStrLn
          close hdb
          return ()
    where
      err hdb = flip unless $ ecode hdb >>= print

new→open→ほげほげ→closeという流れは他の言語バインディングを使うのと変わりません。
BDB, FDBについてもほぼ同様に使うことができます。
まだまだ全然記述が不足しているのですが、リファレンスもあります。

keyとvalueの型は?

Haskellは静的な型を持つ言語です。TCでputやgetといった操作をするときのkey-value値にも型が必要です。型はHaskellでプログラムを書く上での主役になるので、その選択も非常に重要です。

候補1: String

はじめはkey-valueどちらもString型でよいかと考えました。String型は、Haskellで文字列を扱う際の選択肢としてもっとも気軽で使いやすいものです。多くの場合、keyとvalueは文字列として扱うことが予想されるため、これでも十分使えそうです。しかし、Stringの実体は文字列のリストです。TCに格納するためにバイト列にシリアライズする必要がありますが、この際にもオーバーヘッドが生じます。

候補2: ByteString

そこで次に考えたのがByteStringを使う方法です。bytestringパッケージに付属している、Data.ByteString.Unsafeというモジュールの関数を使うと、O(1)でCの文字列(バイト列)へシリアライズができます。これはよさそうです。
ということで、しばらくはByteStringをベースにした実装をしていました。しかしTCのtchdbaddintや、tchdnadddoubleといった文字列以外のvalue値を扱う関数を実装し始めた辺りで、ByteStringだけだとつらいかなと思うようになりました。keyやvalueの値として、IntやDoubleといった型も使いたくなったのです。

これを実現するために、いろいろと調べていくとbinaryというパッケージにたどり着きました。これを使えば、任意の型をByteStringに変換するできるため、TCのkey-value値もByteStringにしておけばいろいろうまく行きそうです。ただしこれでも少し気に入らない点がありました。これを使うコードでは、TCに格納したい値をユーザがいちいち明示的にByteStringに変換しなければならないという点です。それぐらいはラッパーを書けばなんとかなるとか、別に些細な問題だろうという見方もあると思いますが、個人的にはちょっとすっきりしないなぁと思ったのです。また、binaryパッケージが、ghcに付属のパッケージではないことも懸念点でした。binaryパッケージにたよって、key-valueの型をByteStringにすると、デフォルトのbinaryパッケージが入っていない状態ではInt型すら気軽にstoreできないからです。

Storable

とここまで悩んで、結局Database.TokyoCabinet.Storableというクラスを導入し、String、ByteString、Int、Doubleなどの型をそのクラスのインスタンスとすることにしました。こうすることで、

val :: Int
val = 100
put hdb "foo" val
-- ↑単純に 'put hdb "foo" 100' とすると100がDouble型と解釈されてしまうようなので、あえて型を明示しています
put hdb "bar" "baz"
put hdb (pack "hoge") (pack "fuga")

というように、これまで候補に挙がった型をそのままputやgetに渡すことができるようになります。

ただしこのやり方も完全ではありません。TCにputした後は、シリアライズする前の型が何だったのか、という情報が失われます。Intの値をputしたのに、getするときにString型で取り出すということができてしまいます。その結果int型をあらわしているバイト列を、無理やり文字列として読んでしまったり、ということが起こります。どちらにせよ、シリアライズされて一旦外の世界に出たデータを読むという性質上、静的な型検査で解決できる問題ではありません。ただ、こういった不整合は、発見しづらいバグを生んでしまう可能性があるので、なんとかしたいなと思っています。何か良い案があればアドバイスいただけると助かります。

TDBは?

リファレンスを見て気づかれた方もいるかも知れませんが、hackageにアップしているバージョンでは、テーブルデータベースをサポートしていません。単純にODF期間中に仕上がらなかったからという理由です。
が、その後も自分の時間を使って継続的に開発をし、githubにあがっている最新バージョンでは、TDBとTDBQRYもサポートしています。
以下のような感じでインストールしてください。(ただし絶賛開発中なので、タイミングによってはビルドに失敗したりバグっていたりするかもしれません。)

 $ git clone git://github.com/tom-lpsd/tokyocabinet-haskell.git
 $ cd tokyocabinet-haskell
 $ runhaskell Setup.hs configure --extra-lib-dirs=/path/to/tc/lib --extra-include-dirs=/path/to/tc/include --user
 $ runhaskell Setup.hs build
 $ runhaskell Setup.hs install

“検索結果に対するアトミックな更新”もサポートしています。少し長いですが以下のような感じで使います。

import Database.TokyoCabinet.TDB
import Database.TokyoCabinet.TDB.Query hiding (new)
import qualified Database.TokyoCabinet.Map as M
import qualified Database.TokyoCabinet.TDB.Query as Q (new)

data Profile = Profile { name :: String
                       , age  :: Int } deriving Show

insertProfile :: TDB -> Profile -> IO Bool
insertProfile tdb profile =
    do m <- M.new
       M.put m "name" (name profile)
       M.put m "age" (show . age $ profile)
       Just pk <- genuid tdb
       put tdb (show pk) m

main :: IO ()
main = do t <- new
          open t "foo.tct" [OWRITER, OCREAT]

          mapM_ (insertProfile t) [ Profile "tom" 23
                                  , Profile "bob" 24
                                  , Profile "alice" 20 ]

          q <- Q.new t
          addcond q "age" QCNUMGE "23"
          setorder q "name" QOSTRASC
          proc q $ \pk cols -> do
            Just name <- M.get cols "name"
            putStrLn name
            M.put cols "name" (name ++ "!")
            return (QPPUT cols)

          close t
          return ()

`proc'を呼んでいる部分で、Haskellの無名関数を使って更新処理の内容を渡しています。
エラー処理などをさぼっているので、完全なサンプルではありませんが、だいたいの利用イメージを持ってもらえるのではないでしょうか。

Haskellらしくない?

ここまでのサンプルを見ると、どれもIOモナドバリバリで、Haskellの純粋関数型言語としての長所が生かされている感じがしません。ライブラリを作り始めた当初、どうせHaskellでやるのだからHaskellらしい抽象的なインターフェースを提供しようとも考えました。ただ、そういう作りにしてしまうと、特殊なシチュエーションでかゆいところに手が届かないみたいなことになってしまったり、アプリケーションによっては、まったく違うインターフェースの方がマッチしたり、といったことが起こり得ると考えて、まずはナイーブにtokyocabinet.idlに準拠したインターフェースにしました。

Haskellを使っているわりにはなにかと面倒、と感じる部分もでてくると思いますが、tokyocabinet.idlに沿うことによって、TCのメインの機能はHaskellをほとんど知らなくても直感的に使えるようになっていると思います。(なってなかったらごめんなさい。)また、これらを土台にして、より抽象的な関数を作ることはいくらでもできると思います。
実用的かどうかはわかりませんが、ReaderモナドやErrorモナドと組み合わせたサンプルもリポジトリにコミットしています。

まとめ

TCのHaskellバインディングを簡単に紹介させていただきました。PerlやRubyから使えるのだからそれで十分だろうという声もあるかと思いますが、私はHaskellが好きでかつTCを使いたかったのでバインディングを書きました。
Haskellというと、まだまだ実用にはならない言語というイメージを持たれがちで、社内でもHaskellというと、"ネタでしょ"みたいな扱いを受けたりもします。
しかし、最近ではReal World Haskellという本が出版されるなど、実用的な言語としてのHaskellも注目を集めています。日本語のものでもshelarcyさんの連載など有益な情報源が増えてきています。
今回作ったTCのバインディングが、実用的なアプリを作る土台になっていけばいいなと思っています。