まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2006/07/08(土)Satsuki-system技術情報

はてな技術者のnaoyaさんのPerl技術記事見てたんですが

adiary関連の技術情報=マニュアルも本格的にまとめ始めたほうかいいなぁーと思ったりしたりしました。数日中にもβ版公開しましすが、ソースみただけではわからないところが多々ありますし。

比較的短期間かつ少人数(一人か)でこれだけのシステムになったのかってのは訳があって、生産性を担保するシステム構成によるところがとても大きいのです。それを担保してくれる Satsuki-system(スケルトンシステム)がなかったらadiaryの開発は不可能でしたし、そもそも「自作のスケルトンシステムがあまりに便利かつ有用なので、これを有効活用する手だて」としてblog(adiary)を開発したという、なんとも本末転倒な話だったりします(笑)*1

*1 : もっともSatsukiシステム自体も、adiary開発の過程で大きく進化したわけですが、基本コンセプトは何ら変わっていません。それぐらいコロンブスの卵的なコンセプトでした(自画自賛

Satsuki-systemとTemplate Toolkitの比較

スケルトンシステムというのは何度も申し上げているとおりで、Perlの変数埋め込みを一歩進めて簡単な命令を書けるようにしたものです。この考え方の有名な例として、Movable Typeにも使われている*2 Template Toolkitがあります。このTemplate Toolkitは変数置換の他、簡単な条件構文やループなどをHTMLテンプレート中に記述できます。

Template Toolkitでは読み込まれたHTMLファイルを、Perlで実行可能なプログラムとしてテンポラリに吐き出します。次以降は、そのコンパイルされたファイルを実行するわけです(mod_perlのときはキャッシュされるのかな?)。Satsuki-systemも紆余曲折の上、現在はその形に落ち着いています。

でも、Satsuki-systemとTemplate Toolkitは決定的に違うところがあります。Template Toolkitのキャッシュはperlプログラムとして実行することでそのまま出力HTMLが得られますが、Satsuki-systemのそれは、そのまま実行することは不可能なプログラムになっています。Satsuki-system本体とコンパイル済のプログラムの2つが合わさって初めて意味を持つわけです。

なんだ「使いにくいだけじゃないか……」と言われるかもしれませんが、実はそこが最大のミソなのです。Satsuki-systemはスケルトンシステムでありフレームワークでもあります。スケルトンシステムから参照する必要なルーチン(ライブラリ)は、システム本体に納められており、そのライブラリ部分を自由に拡張できることこそが最大の特徴です。

以下はスケルトンのコメント表示部分を抜き出したものです。

<p><span class="canchor"><a href="<@t.this_diary_url>#c<@t2.pkey>#">#</a></span>
<span class="commentator"><span<@if(t2.id, " title=<@t2.id>")>><@t2.name></span></span>
『<@t2.text_short>』 <span class="comment-date">(<@tm_printf('%Y/%m/%d %J:%M', t2.tm)>)</span></p>

強調表示のところが埋め込みです。大半はただの変数置換だと思って問題ありません。一番最後の、tm_printf が関数呼び出しとなっていて、この実体はSatsuki-system本体(Base.pm)に含まれます。この関数に相当するものを増やしていくことで、システム全体を構築します。*3

つまり、Template Toolkitキットでは「Perlプログラムの付属品としてのHTMLテンプレート」であったわけですが、Satsuki-systemでは「スケルトン(HTML)の付属品がPerlプログラム」なのです。

*2 : Movable Typeはより簡易なHTML::Template のようです。

*3 : 厳密にはちと違います

Satsuki-systemとPHPの比較

そこまで、スケルトン万歳ならはじめからPHPみたいな埋め込み言語を使えばいいじゃないかという話になります。PHPは使ったことはないのですが、一つそして決定的にPHPを使いたくない理由があります。プログラムとHTMLのソースが一緒になっててメンテしずらい。きちんと書かれた(?)PHPのソースならばそうはならないのかもしれませんが、1画面=1HTML=1プログラム主義のように感じます。

というわけで、もちろんSatsuki-systemだって同じだろうというツッコミもあります。adiaryの日記一覧のような部分は、実際にHTML内にPHPのようにプログラム的動作を記述してページ送りなどを実装していますから、同じといえば同じです。ですが、それは推奨される形ではなく、ほとんどの場合1ページに対して1つのPerlのサブルーチンが付属しています。

RoRのモデル・ビュー・コントローラー的な考えからいえば、Viewがスケルトンで、ControllerがPerlのコードということになります。PHPとの大きな違いはここですが、PHPのプログラム部を外部ファイルとして追い出したとしたら(本体には呼び出しのみ残る)、似たようなものだと思います。

Satsuki-systemとRuby on Railsの比較

比較も何も……似たもの同士です*4。これは別にRoR的思想を取り込んだわけではなくて、Satsuki-systemの「変数埋め込みHTMLの拡張」を突き進めた結果、似たようなものにたどり着いたというわけです*5。もっとも、最初の思想からMVCなRoRとはスタート地点で負けているわけですが、そこを敢えて比較すると

  • Satsuki-systemはPerl用である。開発時点でRubyよりPerlの方が処理が速かったのがPerlになった理由ですが、Ruby 2.0 YARVが登場すると立場逆転しそうです(汗
  • RoRの方が思想的にOO(オブジェクト指向)として綺麗である。そもそもPerlのOOはやっつけなので、これもスタート地点で負けてます。

となります。勝ち目ないですけど(汗) 後ろ向きなアドバンテージは、

  • 今この時点においては、Perlの方が速く動作する。*6
  • Perlの方が普及してるし、ユーザーも多い*7
  • 必ずしも「データ(データベース)」対応でなくてもよい分、Satsuki-systemの方が汎用的かもしれない。
  • 昔ながらのきついルールーより実用主義的なプログラマには…ry(^^;;

*4 : RoRがコードジェネレーターなどの周辺ツールを含む本格的なフレームワークかつ、より徹底されたMVCということを別とすれば、全体的な設計思想としては似てるということです

*5 : RoRの存在を知ったのはつい最近のこと

*6 : でもPerl6はそうそう出そうもないなぁ……

*7 : だからどうした的なモノですね……

まとめると

んー……Perl版フレームワークのないRoRの出来損ない?(ぉぃぉぃ*8

もっとも、

  • 可能な限り高速に動作するよう設計されている*9
  • mod_perl (prefork/worker)でもcgiでもSatuki-systemが差を吸収してくれる(ただし若干気を遣って書く必要はあり)

ですけども。早くマニュアル整備しろってことですね(汗)*10

*8 : 簡単なフレームワークスクリプトを後々作成するといいかなぁ。実際、プログラムの一番最初の書き出しだけが若干面倒で(苦笑)

*9 : そういえば、Template Toolkitのはき出すソースが結果出力をスカラ変数に文字列連結してるのはいかがなものかと思うんだけどなぁ……個人的に

*10 : 機能ありすぎ&内部構造まで書き始めると、いろいろと細かい技がありまして(^^

2006/06/23(金)6/23版スナップショット

スケルトンコンパイラの改良

スケルトンコンパイラをさらに改良しました。今回の変更は多大な労力を割いた割に、テスト環境においてたった1msしか高速化しませんでした*1。コンパイル時間は絶対遅くなっていると思いますが……、1度コンパイルすればキャッシュされるので気にしない方向で(汗)

しかし、スケルトンシステムの仕様は、後々コンパイラ次第である程度性能を改善出来るよう設計していました。今回たまたま気になって、泥沼に入ってしまったのですが、改良に改良を重ねた遍歴で用のない(スマートでない)コンパイル処理を何度も行っていたためスリム化。

とか言いながらパスは7パスになりましたけど……(^^;

newコンパイラ@VIA C3

VIA C3@500 という、とてつもなく環境にやさしい(遅い)CPUで今回の変更を試してみました。非キャッシュ環境(cgi動作)ではそこそこでしたが*2、キャッシュされた環境(mod_perl2 or SpeedyCGI)で実行してみたら、びっくり!!

Total time : 39.3 ms

とても C3@500MHz とは思えないレスポンスをたたき出しております(笑)*3

*1 : 27.1ms → 21.5ms → 20.1msというわけで最初から考えると、約25%の実行速度改善

*2 : 古いsnapshotと比較したら850ms→650msぐらい。バラ付きが大きいのと同じ条件ではないのでうまく比較できない

*3 : 体感速度が明らかに違いますもん(^^;

2006/06/10(土)プログラムの実行速度と形式美

またもデータベースカラム追加

adiary ては、各ユーザーあたり3つのテーブルを使用しています。

  1. 日記本文のテーブル
  2. コメントのテーブル
  3. トラックバックのテーブル

従来から、日記本文のデータの中に、その日記に付いたコメント数やトラックバック数というデータを持たせていました。データベースの正規化の立場からすると、やってはいけないことなのですが、アクセスの度にいちいちSQLで集計とってたら遅くてかなわないということで。

今ではそれを逆に利用して、コメント数やトラックバック数が0のときは、そもそもコメントやトラックバックをロードするSQLを発行しないようにしています。

さらに一歩進めて。今回、コメントの一覧やトラックバックの一覧を機能をつける際*1、コメントごとに元記事のタイトルやら日付やらを引っ張ってくるようにしました*2。ですが、たかだか元記事の「日付」と「タイトル」だけを取得するために、コメント1件につき1回SQLを発行するのはなんとも効率が悪すぎる。

今回の変更で、コメントテーブル、トラックバックテーブル双方に元記事の「日付」と「タイトル」を保存するようにしました。せっかく正規化されているデータを分散させるなんて「なんてことだ!」とか言われそうですし、不整合が起こらないように管理するのが面倒ですが、背(形式美)に腹(処理速度)は代えられません

実はadiaryには、処理速度や見通しのために、プログラムの重複記述*3を敢えて行っている箇所が何カ所かあります。

*1 : 最近のコメント、最近のトラックバックでも同様に使用しています

*2 : つまり、コメントが参照している日記キー(diary_pkey)から日記本文をjoin……なのですが、擬似データベースも使えるようにする都合上、データベースではjoinせずにプログラム側でjoin相当の処理をしていました

*3 : ルーチン化可能だけど敢えて行っていない

よくみるプログラムたち

1.一本道プログラム

要求された処理を満たすようにだけ書かれた、能率も形式美も何もない「のっぺり」としたプログラム。いわゆる「プログラムを書ける人」の7割ぐらいが*4、こういうプログラムを書きます。

必要な処理が順列組み合わせで、なんの抽象化も、構造モデリングもなされずに記述され、大抵の場合はメンテ不可能なまでに*5巨大化し、下手をしたら1/3のソース量で同じ処理が書けるんじゃないか? と思えるものです。

2.形式美プログラム

機能ごとにソースが分離され、処理はきれいなまでに抽象化され、またはクラス化(オブジェクト指向)が徹底されて、非常に美しいプログラム。である反面、メモリの多重確保やメモリ間コピーなどは意図にも介さない。

事務系のプログラムやミッションクリティカルなプログラム(たとえば銀行システムや金銭を扱うような通販システム)では能率なんて求めず安全第一で書くべきだけど、処理速度や処理効率が(極端に)求められるプログラムである場合はときとして失格*6。頭の良い人がプログラムを教科書的に学んだ際に陥りやすい。

3.効率主義プログラム

自分はこのタイプ。限られたリソースや有限な資源を用いて、または計算量がNPであるような大問題を効率よく求めたときなどに書くプログラム。一見するとタイプ1の「一本道プログラム」と似て映るが、きちんと眺めれば効率を求めたものか、何も考えずに書きつづったものかは一目瞭然。

また、形式美をある程度理解した人間が書いた効率主義プログラムと、そうではない効率主義プログラムではメンテ性という意味で大きな差が出てくる。実行効率は形式美とある程度両立可能であるが、たまに調子に乗って必要以上に高効率を求め*7無駄にソースが見づらくなることもある(T-T

4.adiaryは?

  1. perlがあればどこでも動くという汎用性
  2. 抽象化され、差し替え可能な構成と形式美
  3. それでいて限定された環境でも高速に動くこと(cgi動作 & pure perl)
  4. 恵まれた環境ではさらに高速に動くこと(mod_perl2 or SpeedyCGI)

の優先度で作成されています。

*4 : とくにプログラムを学んだのではなくプログラムを教えてもらった人間に多い

*5 : メンテするぐらいなら最初から書き直した方がよっぽど速いし

*6 : 美しく書けば雑然と書くよりは普通に速いので、よっぽど速度重視な場面以外ではあまり構わない

*7 : ワンパスしか処理しないような場所で不必要な最適化をするとか