Mongrel2が面白い

クリーンなCで書かれたWebサーバ「Mongrel2」を読んでみた。コードの内外ともにドキュメントはしっかりしているし、関連ブログエントリもあって、非常に勉強になる。何より、毒舌のZed Shawの書く文章は面白くて飽きないのがいい。

例えば、src/adt以下にはユーティリティ関連のライブラリが入っている。伸び縮みするdarray.cや、ディクショナリ、ハッシュテーブル、リストといった車輪の猛発明なところが、Cを使ったデータ構造の基本という感じ。リストの実装で、各関数の先頭にassertを並べて条件を満たしているかをチェックすることで、整合性を取るのを呼び出しコード側の責任にしているアプローチになるほどなぁと思った。クライアントコードが変なことしたら、ごっそり落ちるべきってことだろうか。

/*
 * Insert the node ``new'' into the list immediately before ``this'' node.
 */

void list_ins_before(list_t *list, lnode_t *new, lnode_t *this)
{
    lnode_t *that = this->prev;

    assert (new != NULL);
    assert (!list_contains(list, new));
    assert (!lnode_is_in_a_list(new));
    assert (this == list_nil(list) || list_contains(list, this));
    assert (list->nodecount + 1 > list->nodecount);

    new->next = this;
    new->prev = that;
    that->next = new;
    this->prev = new;
    list->nodecount++;

    assert (list->nodecount <= list->maxcount);
}

と、思ったけど、結構読んだと思っていたCRubyでも、こういうassertマクロの使い方は普通なのだった。

static void
ary_resize_capa(VALUE ary, long capacity)
{
    assert(RARRAY_LEN(ary) <= capacity);
    assert(!OBJ_FROZEN(ary));
    assert(!ARY_SHARED_P(ary));
    if (capacity > RARRAY_EMBED_LEN_MAX) {
        if (ARY_EMBED_P(ary)) {
            long len = ARY_EMBED_LEN(ary);
            VALUE *ptr = ALLOC_N(VALUE, (capacity));
            MEMCPY(ptr, ARY_EMBED_PTR(ary), VALUE, len);
            FL_UNSET_EMBED(ary);
            ARY_SET_PTR(ary, ptr);
            ARY_SET_HEAP_LEN(ary, len);
        }
        else {

うーん、Cのassertに関するこの記述を読んで、なんだ、ぼくは単にassertのことを知らなかっただけだと思い直した。そうか、assertにはおよそ3通りの使い方があって、「#define NDEBUG」で無効化できるのか……。どうもCの勉強不足だっただけらしい。

ともあれ、このリストの実装はZedじゃなくて、Kaz Kylhekuという人が書いたものらしい。Cのライブラリって、こうやって内部に取り込んだ形で使うことが多いから見えづらいけど、Mongrel2って非常に多くのライブラリを使ってるのね、というのが発見だった。

src/bstr以下には、Paul Hsiehという人が書いたbstringというCのstring.hを安全に置き換えるライブラリがあって、これはMongrel2全体で使われている。で、こうしたことについて、Zedは非常に強い意見を持っていて、それを随所で表明している。

まず、bstringじゃなくてC++の文字列のほうがコードがスッキリするんじゃねぇーの? というメーリングリスト上のツッコミに対して、C++Mongrel程度の小さなプロジェクトには役に立たないし、むしろパフォーマンスのロスのほうが深刻だと言ってる。結局デストラクタがあってもメモリ関連のバグのために、Valgrindを使うハメになるし、文字列に数値を埋め込むようなシンプルな interpolation においても、あまりに馬鹿げた処理の塊になってしまうんだと言っている。なんだかC++をクズ呼ばわりしたLinusに通じるところがあるような(http://librelist.com/browser/mongrel2/2010/7/15/c-verses-c++/#770d94bcfc6ddf1d8510199996b607dd)。

文字列の扱いはWebサーバのキモで、それを安全で高速なライブラリを使って提供することには意味があるように思える。この辺、「そんなもん今どきJavaを使うだろ」という意見があるのだろうか。

Mongrel2は言語非依存ということが、いちばんの目標で、そのために最初はSQLite3に設定情報読み書きするクライアントに使っていたPythonまでCで書き直し、「げ、Python使うの?」といって、それだけの理由でMongrel2を使わないかもしれない(Ruby|Lua|Factor|PHP)の人に配慮したという(笑) 確かにそういう感覚ってあるかも。それに、Linux上のPythonは不幸な状態にあるという指摘もあって、システム管理にPythonが深く入り込み過ぎたために、古いバージョンのPythonが多くのLinuxディストリビューションで使い続けられているということもある。

Rubyだと設定ファイルにはYAML使うよね、とか、Node.js系だとJSON以外に人間が読み書きすべきものがあるのとか、いろいろ議論があるのかもしれないけど、SQLite3を使って、後はコマンドラインやWeb画面にしてしまうというのは良いアプローチに思える。言語非依存をうたうなら、設定ファイルに宗教を持ち込んだらイカンということか。でも、設定クライアントはCで書かれてるじゃないかという指摘に対しては、だってC言語はプログラマにとってラテン語だから、だって。なるほど。それに、気に入らないなら、SQliteなんだから自分の言語でなんとでも書けるでしょ、と。

Mongrel2のsrc/adtの下に、tst.{c,h}というソースファイルがあって、これが何だか最初分からなかった。Zed Shawはファイルの先頭には何もコメントを書かないようで(というか、よく考えるとコード中のコメントが豊富なところは他人のライブラリのような)、tstが何の略なのかも見当がつかなかった。

ググッている間に、Zedによる解説ブログがあった。TSTとはTernary Search Treeの略で、これはTrieを改良して空間効率を良くしたデータ構造らしい。このTSTはURLのルーティングに使う。このデザインチョイスについて、ものすごい毒舌で低レベルプログラマを呪詛しつつ語っている。なんでハッシュマップを使わないの、という指摘に辟易した感じでZedがTSTの優位点として語っているのは、

  • ルーティング関連全部でも500行のCコードと短い
  • 初回ツリー作成時にペナルティを払った後はTSTは速い
  • ハッシュだと毎回、ハッシュを取ったり、ハッシュのネストをたぐったり大変
  • TST自体は139行。赤黒木よりもはるかにシンプル
  • そもそも、ルーティングの文字列処理なんて全体からしたら誤差。グダグダ言うのはバイクシェッドな議論だから黙れよ

というところ。

Mongrel2はRubyで書かれていてRails界隈で人気があったMongrelとはコードベースも何も関係がない。単に新しい名前を考えるのが面倒だったという理由でMongrel2としたらしい。

肝心のMongrel2の本質に近い部分は、また改めて読んでみよう。Mongrel2はCっぽいミニマリズムとZeroMQを使ったWebサーバで、Webサーバがやるべき文字の出し入れを上手にやるべく設計した、という印象。言語やプロトコル、Rack、PSGIといったフレームワークとのWeb層のどれにも依存していなくて、後から繋げやすいというのが、既存のWebサーバとの違いかしら。リバースプロキシ的に動くことから、HAProxyやNginxのカバーエリアもカバーしているということらしい。どうもあまり人気がないような雰囲気だけど。