さて、昨日はSSIとの組み合わせでPageキャッシュの適用範囲を広げる話をした。 なぜSSIかというと、これは組込みの手軽なフィルタ機構だからだ。Apache 1系統ではSSIはハンドラとして実装されているけれども、2系統では新たにフィルタ機構が加わって、SSIはこちらで再実装されている。 フィルタ機構ならmongrelからの出力にも加工できる。Pageキャッシュとキャッシュでないものを透過的に扱えてうれしい訳だ。
ただ、確かにちょっとDRYさに欠ける。どうせならRailsのレイアウトファイルにPHPコード片を直接書きたいではないか。で、これを出力するとPHPとして処理してその結果がクライアントに伝わる、と。 id:yamazさんが「rhtmlで直接phpを吐き出して処理する方法を模索したいのです。」と言ってるのはたぶんそういうことだ。私もそれが理想だと思う。今日はそれに挑戦してみた。
Apache ━ (mod_php5 filter) ┳ (mod_rewrite) ━ (mod_proxy_balancer) ━ mongrel_cluster
│ ┃ │
│ ┗ キャッシュファイル │
│ │
└───→ [Memcached] (session情報) ←──────────────────────┘
要は、PHPコードを受け付けて実行結果を出力するApacheフィルタがあればいいわけですよ。普通のmod_php5の使いかただとPHPプログラムはHandlerで処理されている。フィルタではない。これをちょこっといじってフィルタ版を作ればいいんだろうと思って、ソースを見てみた。
と、sapi/apache2filterというディレクトリがある。中を見てみるとap_register_output_filterを呼んでるから、これってApacheフィルタそのものじゃないのさ。どうやら、私が知らないだけでフィルタ版は存在したらしい。 ただし、いくつか問題があった。
Experimentalでも、私が0から書くよりましでしょう。人柱上等。標準設定の問題は--with-apxs2filterを付けて再コンパイルすればOK。残る問題はProxyに対しては効かない仕様になってること。だからキャッシュに対しては有効だけれども、初回アクセス時は処理してくれない。コードを見てみると、わざわざProxy経由からの出力に対しては無効化するようにしてある。
sapi/apache2filter/sapi_apache2.c:450: php_output_filter
if (f->r->proxyreq) {
zend_try {
zend_ini_deactivate(TSRMLS_C);
} zend_end_try();
return ap_pass_brigade(f->next, bb);
}
まあ、そうかもしれない。一般的には他のサーバーから送出されてきたPHPコードを処理するのは恐すぎるし、使いどころが少なそうだ。ただ、今回は必要なわけなので上のコードをコメントアウトする。
実験環境はUbuntu Linux 7.04で、次のようにした。
できあがったモジュールをインストールしたら、次のように設定する。基本的には昨日のPageキャッシュ版と同じだけれども、フィルタの設定とRailsのlayouts/application.rhtmlだけ違う。
PHPという名前のフィルタが導入されているのでこれを使う。
<Proxy balancer://mongrel>
BalancerMember http://localhost:8000
BalancerMember http://localhost:8001
BalancerMember http://localhost:8002
Allow from all
SetOutputFilter PHP
</Proxy>
NameVirtualHost *
<VirtualHost *>
DocumentRoot /path/to/app/public
# 以下、普通のvirtual hostの設定
# (略)...
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /path/to/app/public>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
RewriteEngine on
RewriteRule ^/?$ index.html [QSA]
RewriteRule ^([^.]+?)/?$ $1.html [QSA]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ balancer://mongrel%{REQUEST_URI} [P,QSA]
AddOutputFilter PHP .html
</VirtualHost>
helperメソッドが出力したダブルクォーテーションがPHPのsyntaxエラーを生じたりして一瞬戸惑った。
<html>
<head>
<title>test</title>
</head>
<body>
<p>
<?php
$memcache = new Memcache;
$memcache->pconnect('localhost', 11211) or die('cannot connect');
$json = $memcache->get("session:" . $_COOKIE['_simple_layout_session_id']);
$hash = json_decode($json);
$user_name = $hash->user_name;
if ($user_name) {
echo '<h1>ようこそ' . $user_name .'さん</h1>';
} else {
echo '<p><%= link_to 'ログイン', :action => 'login' %></p>';
}
?>
</p>
<hr />
<%= @content_for_layout %>
</body>
</html>
昨日とおなじ条件で測ったらこうなった。
Concurrency Level: 100 Time taken for tests: 6.951062 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 23683594 bytes HTML transferred: 21641211 bytes Requests per second: 1438.63 [#/sec] (mean) Time per request: 69.511 [ms] (mean) Time per request: 0.695 [ms] (mean, across all concurrent requests) Transfer rate: 3327.26 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 3 32 11.5 32 69
Processing: 15 36 11.6 36 72
Waiting: 2 31 11.9 31 69
Total: 57 68 7.2 66 133
Percentage of the requests served within a certain time (ms)
50% 66
66% 67
75% 68
80% 69
90% 81
95% 83
98% 85
99% 100
100% 133 (longest request)
理論上はSSIの解析プロセスが無くなった分だけ速くなるはずだけれども、結果は誤差の範囲だ。まぁ、ファイルサイズとリクエスト数がこの程度なら、今時のマシンはSSIぐらい気にすることはないということなんだろうか。 とにかく、設定がシンプルになってview templateがDRYになったのは嬉しい。
私がやりたかったことそのもので,超すごいです!!
> 「可能だから」といってRHTML内PHPを肥大化
> させるのはおすすめしない。何のためにRailsを
> 使ってるのか分からなくなるし、はっきり言って
> きつい。
そこでRJSならぬRPHP構想が出てくるわけです:D